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

Ale*_*vis 2 windows parameters quotes powershell

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

这是脚本(TestQuotes.ps1):

param
(
    [string]
    $Config = $null
)

"Config = $Config"
Run Code Online (Sandbox Code Playgroud)

结果如下:

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
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

bea*_*ker 6

根据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 | -? | /?
Run Code Online (Sandbox Code Playgroud)

之后的所有文本-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>

Run Code Online (Sandbox Code Playgroud)

子实例进一步将其解析为:

'.\TestQuotes.ps1' '-Config' 'A' 'B' 'C'
Run Code Online (Sandbox Code Playgroud)

这是您得到“错误”结果的地方: 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
Run Code Online (Sandbox Code Playgroud)


mkl*_*nt0 5

太长了;博士

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

Windows PowerShell:

# To pass arguments to the script block, append -args ...
powershell.exe { .\TestQuotes.ps1 -Config "A B C" }
Run Code Online (Sandbox Code Playgroud)

PowerShell核心

# To pass arguments to the script block, append -args ...
pwsh { .\TestQuotes.ps1 -Config "A B C" }
Run Code Online (Sandbox Code Playgroud)

将使参数的引用按预期工作- 它甚至会从调用中返回具有接近类型保真度的对象,因为采用了类似于 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'导致 PowerShell 最终解析并执行以下代码:

.\TestQuotes.ps1 -Config A B C
Run Code Online (Sandbox Code Playgroud)

也就是说,由于两轮解析,原始引用丢失了导致传递了三个不同的参数,这解释了您的症状。


如果您必须让命令在没有脚本块的情况下工作,您有两个选择:

  • Use-File仅应用一轮解析

      powershell.exe -File .\TestQuotes.ps1 -Config "A B C"
    
    Run Code Online (Sandbox Code Playgroud)
    • 也就是说,除了剥离封闭的 之外"...",结果参数还被视为文字- 然而,这通常是您想要的。
  • 使用 (implied) -Command,应用额外的引用层

      powershell.exe -Command .\TestQuotes.ps1 -Config "'A B C'"
    
    Run Code Online (Sandbox Code Playgroud)

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


[1]\转义应该不是`必需的,但不幸的是,这是由于至少在 PowerShell 7.2.x 之前存在的长期错误,该错误可能会在 7.3 中得到修复 - 请参阅此答案