【问题标题】:Start-Process with PowerShell.exe exhibits different behavior with embedded single quotes and double quotesStart-Process with PowerShell.exe 表现出嵌入单引号和双引号的不同行为
【发布时间】:2020-07-21 08:41:50
【问题描述】:

首先,如果有人想知道我们为什么要以这种方式调用 PowerShell,我在构建一个更复杂的命令时遇到了这种行为,但可以使用如下所示的更简单的示例来展示这种行为。在实践中,我们以管理员身份在 32 位 PowerShell 下运行命令,并在字符串中呈现附加变量(因此我不简单地使用单引号作为外部部分),但这似乎并没有考虑到下面的行为。


当我通过 Start-Process 调用 PowerShell 时,如果我在 PowerShell 可执行文件的 -Command 参数周围使用单引号,我会得到一些奇怪的行为。例如:

Start-Process -FilePath Powershell.exe -ArgumentList "-Command 'ping google.com'"

只是将ping google.com 渲染为输出并退出。但是,如果我使用嵌套的双引号而不是单引号,如下所示:

Start-Process -FilePath Powershell.exe -ArgumentList "-Command `"ping google.com`""

ping 运行并产生预期的输出:

使用 32 字节数据 ping google.com [173.194.78.113]:

来自 173.194.78.113 的回复:bytes=32 time=34ms TTL=45

来自 173.194.78.113 的回复:bytes=32 time=33ms TTL=45

来自 173.194.78.113 的回复:bytes=32 time=35ms TTL=45

来自 173.194.78.113 的回复:bytes=32 time=32ms TTL=45

173.194.78.113 的 Ping 统计数据:

数据包:发送 = 4,接收 = 4,丢失 = 0(0% 丢失),

大约以毫秒为单位的往返时间:

最小值 = 32 毫秒,最大值 = 35 毫秒,平均值 = 33 毫秒

如果我对-Command 参数使用单引号而不是双引号,为什么命令字符串只是按原样呈现而不是执行?

【问题讨论】:

    标签: powershell arguments command-line-interface quoting


    【解决方案1】:

    js2010's helpful answer 是正确的,因为 Start-Process 的使用是您的问题所附带的,并且 行为特定于 PowerShell 的 CLIpowershell.exe 表示 Windows PowerShell,pwsh 用于 PowerShell [Core] 6+):

    在 Windows 上[1],需要考虑层评估

    • (a) 命令行初始解析成参数。

    • (b) 由于使用了 -Command (-c) CLI 参数。

    回复(a):

    与 Windows 上的大多数控制台程序一样,PowerShell 仅识别 " 字符。 (双引号)- 不也是 '(单引号)- 具有 syntactic 功能。[2]

    • 也就是说,除非" 字符。被转义,它们是字符串分隔符,它们在解析过程中被删除

    • 如上所述,' 字符。 没有被删除。

    无论结果是什么参数 - 可能是"-stripped 标记的数组 - 连接,它们之间有一个单个空格,它成为 (b )。


    在你的例子中解释这一点,Start-Process 从图片中取出:

    注意:以下适用于从cmd.exe 或任何涉及 shell 的上下文(包括Start-Process 和Windows 运行 (WinKey-R) 对话框)。相比之下,如果需要,PowerShell重新引用后台命令行始终使用"
    换一种说法:以下适用于命令行如 PowerShell 所见

    单引号-引用命令:

    # Note: This *would* work for calling ping if run from 
    #       (a) PowerShell itself or (b) from a POSIX-like shell such as Bash.
    #       However, via cmd.exe or any context where *no* shell is involved,
    #       notably Start-Process and the Windows Run dialog, it does not.
    powershell -Command 'ping google.com'
    
    • (a) 导致 PowerShell 找到以下两个逐字参数:'pinggoogle.com'

    • (b) 将这些逐字参数连接起来形成 'ping google.com'[2] 并执行 作为 PowerShell 代码,因此 输出这个字符串字面量ping google.com

    双引号-引用命令:

    powershell -Command "ping google.com"
    
    • (a) 导致 PowerShell 剥离 句法 " 字符,找到以下单个逐字参数:ping google.com

    • (b) 然后导致这个逐字参数 - ping google.com - 作为 PowerShell 代码执行,因此导致 命令调用,即带有参数的 ping 可执行文件google.com


    [1] 在类 Unix 平台上,第一层不适用,因为被调用的程序只会看到 verbatim 参数的 array,不是他们自己必须解析为参数的命令行。并不是说,如果您在类 Unix 平台上从类似 POSIX 的 shell(例如 bash)调用 PowerShell CLI,那么 那个 shell 会将单引号识别为字符串分隔符,并将它们剥离 PowerShell 看到它们之前。

    [2] 令人惊讶的是,在 Windows 上,最终由 每个单独的程序 来解释命令行,并且一些 确实 选择也识别 @987654350 @ 作为字符串分隔符(例如,Ruby)。但是,Windows 上的许多程序(包括 PowerShell 本身)都是基于 C 运行时的,它只能识别 "

    [3] 顺便说一句,请注意这意味着 空白规范化 正在发生:也就是说,
    powershell -Command 'ping google.com' 将同样导致 'ping google.com'。支持>

    【讨论】:

    • 感谢您的详尽解释。我没有意识到命令提示符的单引号行为延续到 PowerShell,或者更确切地说,它是命令调用如何处理 Windows 本身中的单引号字符串,并影响大多数入口点 - 我一直认为这只是一个令人讨厌的怪癖命令。
    • 实际上我的示例是在 powershell 本身中运行的。单引号内的双引号似乎具有运行命令的神奇属性,而双引号内的单引号将其视为要回显的字符串。
    • 很高兴听到它有帮助,@BendertheGreatest。它实际上不是 Windows - 由每个程序来解析原始命令行,但许多程序是基于 C 运行时构建的,它只能识别 " - 请参阅我刚刚添加的脚注 [2]。
    • @js2010,请参阅我从cmd.exe 或从涉及 no shell 的上下文中调用的澄清。 PowerShell 通过在幕后使用" 执行重新引用 来混淆图片。在非 Windows 平台上,从类似 POSIX 的 shell(如 bash)调用时,正是该 shell 识别单引号。最好在图片之外保留一个附加 shell(cmd.exe 除外),以避免混淆。
    【解决方案2】:

    我们可以从这里删除 start-process。 powershell 可执行文件对嵌入的双引号和单引号的处理方式似乎不同。 Start-process 与此行为无关。这是 powershell 在命令行上的行为方式。我没有办法改变它。您也可以执行“start-job -runas32”,但它不会被提升。另一个选项是 powershell 的“-file”选项而不是“-command”。这些示例在 Osx (unix) powershell 内核中运行:

    pwsh -c "'ping -c 1 google.com'"
    
    ping -c 1 google.com
    
    
    pwsh -c "`"ping -c 1 google.com`""
    
    PING google.com (172.217.10.110): 56 data bytes
    64 bytes from 172.217.10.110: icmp_seq=0 ttl=52 time=20.020 ms
    
    --- google.com ping statistics ---
    1 packets transmitted, 1 packets received, 0.0% packet loss
    round-trip min/avg/max/stddev = 20.020/20.020/20.020/0.000 ms
    
    
    pwsh -c '"ping -c 1 google.com"'        
    PING google.com (172.217.6.206): 56 data bytes
    64 bytes from 172.217.6.206: icmp_seq=0 ttl=52 time=22.786 ms
    
    --- google.com ping statistics ---
    1 packets transmitted, 1 packets received, 0.0% packet loss
    round-trip min/avg/max/stddev = 22.786/22.786/22.786/0.000 ms
    

    【讨论】:

    • 所以,请阅读我的第一段。这是一个简化的示例,但实际上我们需要为此运行提升的 32 位 PowerShell,因此我们使用Start-Process-Verb RunAs 参数调用PowerShell 的SysWOW64 变体。否则我同意使用 Start-Process 将是多余的。
    • 为了解决您的更新问题,我已经通过 powershell 入口点确定单引号与双引号之间存在差异。但我正在寻找一个更具体的答案,而不是“就是这样”。考虑到除了 PowerShell 中双引号字符串和单引号字符串之间的一些细微差别之外,这种行为似乎是错误的,两者都是字符串,所以我希望任何一个变体都能执行,无论它是字符串还是字符串文字。
    【解决方案3】:

    powershell.exe 的帮助中没有提到支持单引号......你为什么要使用它们?

    -Command
        Executes the specified commands (and any parameters) as though they were
        typed at the Windows PowerShell command prompt, and then exits, unless
        NoExit is specified. The value of Command can be "-", a string. or a
        script block.
    
        If the value of Command is "-", the command text is read from standard
        input.
    
        If the value of Command is a script block, the script block must be enclosed
        in braces ({}). You can specify a script block only when running PowerShell.exe
        in Windows PowerShell. The results of the script block are returned to the
        parent shell as deserialized XML objects, not live objects.
    
        If the value of Command is a string, Command must be the last parameter
        in the command , because any characters typed after the command are
        interpreted as the command arguments.
    
        To write a string that runs a Windows PowerShell command, use the format:
            "& {<command>}"
        where the quotation marks indicate a string and the invoke operator (&)
        causes the command to be executed.
    

    【讨论】:

    • 它并没有说它 也支持它们:The value of Command can be "-", a string. or a script block. 单引号和双引号都会产生字符串,只要我不期望变量在执行时在单引号内呈现,或者要转义的字符,这里不应该有记录的行为差异(显然在实践中我要问的是不同的东西)。
    • 是的,我不能为你回答这个问题。但是由于单引号总是被解释为文字,这似乎是合乎逻辑的,这将是一个自然的排除情况。如果您不喜欢反引号,我很确定双引号也可以加倍“”“”
    • 我同意单引号被解释为文字字符串......但如果 -Command 预计会执行字符串,我希望字符串呈现为文字字符串然后执行。对我来说,这似乎不是一种自然行为。
    • 我一直对 PowerShell 从命令行执行第 3 方二进制文件的怪癖感到困惑。 &{}, '', .\,似乎有一些非常不一致的行为,或者至少没有明确的指导将参数传递给其他可执行文件——尤其是当它们本身需要某种程度的引用时......