为什么PowerShell不识别带引号的参数?

3

为什么PowerShell在直接调用脚本(在PowerShell控制台或ISE中)或通过另一个PowerShell实例调用时,对带引号的参数进行不同处理?

这是脚本(TestQuotes.ps1):

最初的回答:

param
(
    [string]
    $Config = $null
)

"Config = $Config"

以下是结果:

最初的回答:

PS D:\Scripts> .\TestQuotes.ps1 -Config "A B C"
Config = A B C
PS D:\Scripts> PowerShell .\TestQuotes.ps1 -Config "A B C"
Config = A
PS D:\Scripts> .\TestQuotes.ps1 -Config 'A B C'
Config = A B C
PS D:\Scripts> PowerShell .\TestQuotes.ps1 -Config 'A B C'
Config = A

最初的回答
有什么想法吗?

调用powershell.exe的方式与使用参数启动脚本的方式并不相同。可以查看powershell.exe /?来获取一些想法,主要是关于如何将字符串传递给可执行文件的方式不同。 - Lee_Dailey
2个回答

8
根据PowerShell.exe命令行帮助powershell可执行文件的第一个参数是-Command
PowerShell[.exe]
       [-Command { - | <script-block> [-args <arg-array>]
                     | <string> [<CommandParameters>] } ]
       [-EncodedCommand <Base64EncodedCommand>]
       [-ExecutionPolicy <ExecutionPolicy>]
       [-File <FilePath> [<Args>]]
       [-InputFormat {Text | XML}]
       [-Mta]
       [-NoExit]
       [-NoLogo]
       [-NonInteractive]
       [-NoProfile]
       [-OutputFormat {Text | XML}]
       [-PSConsoleFile <FilePath> | -Version <PowerShell version>]
       [-Sta]
       [-WindowStyle <style>]

PowerShell[.exe] -Help | -? | /?

-Command 后的任何文本都将作为单个命令行发送到 PowerShell。

...

-Command 的值为字符串时,Command 必须是指定的最后一个参数,因为在命令之后键入的任何字符都会被解释为命令参数

使用 echoargs 很容易检查实际接收到的子 PowerShell 实例:

PS > echoargs .\TestQuotes.ps1 -Config "A B C"
Arg 0 is <.\TestQuotes.ps1>
Arg 1 is <-Config>
Arg 2 is <A B C>

这进一步由子实例解析为:

'.\TestQuotes.ps1' '-Config' 'A' 'B' 'C'

这就是您得到“错误”结果的地方:Config = A

如果您指定-File参数,您将获得所需的结果:

PS >  PowerShell -File .\TestQuotes.ps1 -Config 'A B C'
Config = A B C

PS >  PowerShell -Command .\TestQuotes.ps1 -Config 'A B C'
Config = A

5

简而言之

如果你正在从 PowerShell 调用另一个 PowerShell 实例,请使用 脚本块 ({ ... }) 来获得可预测的行为:

Windows PowerShell:

# To pass arguments to the script block, append -args ...
powershell.exe { .\TestQuotes.ps1 -Config "A B C" }

PowerShell 核心:

# To pass arguments to the script block, append -args ...
pwsh { .\TestQuotes.ps1 -Config "A B C" }

这将使参数引用按预期工作,甚至将返回 对象,因为采用了类似于 PowerShell 远程的序列化。

但请注意,当从 PowerShell 外部,例如从 cmd.exebash 中调用时,这不是一个选项

继续阅读以了解在没有脚本块的情况下看到的行为的解释。


PowerShell CLI(调用powershell.exe(Windows PowerShell)/ pwsh.exe(PowerShell Core仅支持一个接受位置参数的参数(即,不带有参数名称的值,如-Command)。

  • Windows PowerShell中,该(隐含的)参数是-Command

  • PowerShell Core中,它是-File

    • 默认值必须更改以支持在Unix shebang行中使用CLI。

如果有的话,第一个位置参数之后的任何参数都被视为:

  • 在 Windows PowerShell 中:PowerShell 源代码片段的一部分传递给(隐含的)
    -Command 参数。

  • 在 PowerShell Core 中:单个参数作为字面值传递到指定在第一个位置的脚本文件中(隐含的-File参数)。


传递给-Command的参数(无论是隐式还是显式)会被PowerShell进行两轮解析,这可能会很棘手:
  • 第一轮中,封装单个参数的"..."(双引号)将被剥离

    • 如果您从PowerShell中调用,甚至应用于最初为'...'(单引号)封装的参数,因为在幕后,PowerShell会将这些参数重新引用为"..."以调用外部程序(包括PowerShell CLI本身)。
  • 第二轮中,剥离的参数将使用空格连接以形成单个字符串,然后被解释为PowerShell源代码


应用于您的调用,这意味着同时PowerShell .\TestQuotes.ps1 -Config "A B C"PowerShell .\TestQuotes.ps1 -Config 'A B C'最终都会解析并执行以下代码:

.\TestQuotes.ps1 -Config A B C

这意味着,由于两轮解析,原始引用被丢失,导致传递了三个不同的参数,这解释了你的症状。
如果你必须让你的命令在没有脚本块的情况下工作,你有两个选择:
  • 使用 -File,它只适用于一轮解析

      powershell.exe -File .\TestQuotes.ps1 -Config "A B C"
    
    • 也就是说,除了去掉封闭的"..."之外,结果参数被视为字面量 - 然而,这通常是你想要的。
  • 使用(隐含的)-Command,应用一个额外的引号层次

      powershell.exe -Command .\TestQuotes.ps1 -Config "'A B C'"
    

命令行解析期间,外部的 "..." 会被剥离,只留下内部的单引号字符串 'A B C' 作为要执行的代码的一部分。
如果您还想在内部使用 " 进行引用(在这里不是必需的),则必须从 PowerShell 的外部使用 "\"A B C\"" 和从 PowerShell 的内部使用 "\`"A B C\`""[1] - 也就是说,PowerShell 要求在传递给其 CLI 的参数中必须将 " 字符转义为 \",而在 PowerShell 内部,则必须使用 `"(或"")。


[1] \ 转义符与 ` 一起使用时本来不应该是必需的,但不幸的是,由于长期存在的错误,至少在 PowerShell 7.2.x 中仍然需要使用。这个问题可能会在 7.3 版本中得到修复 - 参见 this answer


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接