如何在字符编码、输入和输出流、引用和转义方面可靠地调用 PowerShell CLI?

How do I call the PowerShell CLI robustly, with respect to character encoding, input and output streams, quoting and escaping?

这个自我回答的问题旨在 PowerShell CLI(命令行界面) 的系统概述,两者都适用于 Windows PowerShell (powershell.exe) 和 PowerShell (Core) v6+(Windows 上的 pwsh.exe,Unix 上的 pwsh)。

虽然存在官方帮助主题(请参阅答案中的链接),但它们并没有描绘出完整的画面并且缺乏系统的处理(截至撰写本文时)。

除其他外,还回答了以下问题:

PowerShell CLI 基础知识:

  • PowerShell 版本:遗留的 CLI,bundled-with-WindowsWindows PowerShell 版本是 powershell.exe, whereas that of the cross-platform, install-on-demand PowerShell (Core) 7+ edition is pwsh.exe(只是 pwsh 在 Unix-like平台)。

  • 交互使用:

    • 默认情况下,除非指定要执行的代码(通过-Command-c)或-File-f,见下文),进入互动会话。但是,与 POSIX-compatible shell 不同,例如 bash 您可以使用 -NoExit 仍然进入互动会话 after executing code. 这对于在没有预先存在的控制台 window.

      的情况下调用 CLI 时对命令行进行故障排除特别方便
    • 使用-NoLogo 来抑制进入交互式会话时显示的启动文本(如果传递了要执行的代码则不需要)。 GitHub issue #15644 建议 默认显示此启动文本

    • 选择退出遥测/更新通知,在进入交互式会话之前定义以下环境变量: POWERSHELL_TELEMETRY_OPTOUT=1 / POWERSHELL_UPDATECHECK=Off

  • 参数和默认值:

    • 所有参数名称都是大小写-不区分大小写(PowerShell 通常如此);大多数参数 都有短别名 ,例如 -h-? -Help,其中显示 command-line 帮助,pwsh(但不是 powershell.exe)也列出了这些短别名。

      • 警告:为了 long-term 代码的稳定性,您应该使用完整的参数名称或它们的官方别名。请注意,PowerShell 的“弹性语法”还允许您使用 prefixes 参数名称 ad hoc,只要这样的前缀 明确标识目标参数;例如,-ver 明确地以 -version 当前 为目标,但是 - 至少假设 - 如果名称也以 [ 开头的新参数,这样的调用将来可能会中断=34=] 即将推出。
    • pwshpowershell.exe支持更多个参数,例如-WorkingDirectory-wd)。

    • 两种(互斥)方式将代码传递给执行,在这种情况下,PowerShell 进程会在执行结束时自动退出;传递 -NonInteractive 以防止在代码中使用交互式命令或传递 -NoExit 以在执行后保持会话打开:

      • -Command (-c) 用于 传递 任意 PowerShell 命令,它可以作为单个字符串或作为单独的参数传递,在删除(未转义)后 double-quotes,稍后与空格连接,然后解释为 PowerShell 代码。

      • -File (-f)用于调用脚本文件 (.ps1) with pass-through arguments, which are treated as verbatim values.

      • 这些参数必须在命令行最后出现,因为所有后续参数都被解释为正在传递的命令/script-file 调用。

      • 请参阅 了解何时使用 -Command-File 的指导,以及引用/转义注意事项的底部部分。

      • 建议使用-Command-c)或-File-f显式,因为两个版本有不同的默认值:

        • powershell.exe 默认为 -Command (-c)
        • pwsh 默认为 -File (-f),这是在 Unix-like 平台上支持 shebang 行所必需的更改。
      • 不幸的是,即使有-Command-c)或-File-f),profiles (初始化文件)默认 加载 (不像 POSIX-compatible shell 等 bash,它只在启动时加载互动shells).

        • 因此,建议通常在 -Command (-c) 或 -File (-f) 之前加上 -NoProfile (-nop),为了避免额外的开销和更可预测的执行环境,它抑制了配置文件加载(假设配置文件可以进行影响会话中执行的所有代码的更改).

        • GitHub proposal #8072 讨论引入 单独的 CLI(可执行文件),它 加载组合配置文件使用这些参数,还可以改善现有可执行文件无法为 backward-compatibility.

          更改的其他遗留行为
  • 字符编码(适用于bot输入和输出流):

    • 注意:PowerShell CLI 仅处理 text[1],无论是输入还是输出,都不是原始字节数据;默认情况下 CLI 输出的文本与您在 PowerShell 会话中看到的文本相同,对于复杂对象(具有属性的对象)意味着 human-friendly 格式 而不是 专为编程处理而设计,所以要输出复杂对象,最好以结构化text-based格式发出它们,例如JSON.

      • 请注意,虽然您可以使用 -OutputFormat xml (-of xml) 获取 CLIXML 输出,它使用 XML 进行对象序列化,但这种特殊格式的作用很小在 PowerShell 的 之外 使用;通过 stdin (-InputFormat xml / -if xml).
      • 接受 CLIXML 输入的同上
    • Windows 上,PowerShell CLI 遵循控制台的代码页,如 chcp 的输出所示,并且在内部PowerShell,在 [Console]::InputEncoding 中。控制台的代码页默认为系统的活动 OEM 代码页

      • 警告:US-English 系统上的 437 等 OEM 代码页已修复,single-byte 字符编码受限总共 256 个字符。 要获得完整的 Unicode 支持,您必须切换到代码页 65001 ,然后 调用 PowerShell CLI(来自 cmd.exe,调用 chcp 65001);虽然这在两个 PowerShell 版本中都有效,但不幸的是 powershell.exe 在这种情况下将控制台切换为光栅字体,这导致许多 Unicode 字符无法 正确显示 ;但是,实际数据不受影响。

        • 在 Windows 10 上,您可以切换到 UTF-8 system-wide,这会同时设置OEM 和 ANSI 代码页到 65001;但是请注意,此 具有 far-reaching 后果 ,并且该功能在撰写本文时仍处于测试阶段 - 请参阅 .
    • Unix-like 平台 (pwsh) 上,UTF-8总是使用(即使活动语言环境(由locale报告)是不是基于UTF-8的,但现在这种情况非常罕见).

  • 输入-流(stdin)处理(通过stdin[=555=接收], 通过管道传输到 CLI 调用或通过输入重定向提供 <):

    • 处理标准输入作为数据:

      • 需要明确使用 automatic $input variable

      • 这反过来意味着为了将标准输入传递给脚本文件 (.ps1), -Command ([必须使用 =19=]) 而不是 -File (-f)。请注意,这会使传递给脚本的任何参数(在下面用 ... 符号化)受制于 PowerShell 的解释(而对于 -File,它们将被逐字使用):
        -c "$Input | ./script.ps1 ..."

    • 处理标准输入作为代码(仅pwsh,似乎在powershell.exe中被破坏):

      • 虽然原则上通过标准输入传递 PowerShell 代码以执行(默认情况下,这意味着 -File -,并且 -Command -),但它 表现出不受欢迎的 pseudo-interactive 行为并阻止传递参数:参见GitHub issue #3223;例如:
        echo "Get-Date; 'hello'" | pwsh -nologo -nop
  • 输出-stream (stdout, stderr) handling:

    • (除非您使用脚本块 ({ ... }),它只能在 inside PowerShell 中运行,见下文),all 6 PowerShell 的 output streams 被发送到 stdout,包括 errors(!)(后者通常发送到 stderr)。

      • 但是,当您应用 - 外部 - stderr 重定向 时,您可以有选择地抑制 error-stream 输出(2>NUL 来自 cmd.exe, 2>/dev/null on Unix) 或者将其发送到文件 (2>errs.txt).

      • 有关详细信息,请参阅 this answer 的底部部分。


引用和转义 -Command (-c) 和 -File (-f) 参数:

从 PowerShell 调用 时(很少需要):

  • 很少需要从PowerShell调用PowerShell CLI ,因为任何命令或脚本都可以只需直接调用 ,相反地,调用 CLI 会因创建子进程而引入开销并导致类型保真度丢失。

  • 如果您仍然需要,最可靠的方法是 使用 script block ({ ... }), which avoids all quoting headaches, because you can use PowerShell's own syntax, as usual. Note that using script blocks only works from inside PowerShell, and that you cannot refer to the caller's variables in the script block; however, you can use the -args parameter to pass arguments (based on the caller's variables) to the script block, e.g., pwsh -c { "args passed: $args" } -args foo, $PID; using script blocks has additional benefits with respect to output streams and supporting data types other than strings; see .

    # From PowerShell ONLY
    PS> pwsh -nop -c { "Caller PID: $($args[0]); Callee PID: $PID" } -args $PID
    

外部调用时 PowerShell(典型情况):

注:

  • -File (-f) 参数 必须 作为单个参数:script-file 路径,后跟要传递给脚本的参数(如果有)。 script-file 路径和 pass-through 参数都被 PowerShell verbatim 使用,在 Window[=489= 上去除(未转义的)双引号之后][2].

  • -Command (-c) arguments may 作为多个参数传递,但最后 PowerShell 只是将它们连接在一起空格,在 Windows 上去除(未转义的)双引号之后,在将结果字符串 解释为 PowerShell 代码 之前(就像您在 PowerShell 会话中提交它一样)。

    • 为了健壮性和概念清晰,最好将命令作为单个参数传递给-Command-c),其中 on Windows 需要 double-quoted string ("...") (尽管整个 "..." 外壳对于 no-shell 调用环境(例如 Task Scheduler 和某些 CI/CD 和 configuration-management 环境中的稳健性并不是​​绝对必要的,即在它不是的情况下t cmd.exe 先处理命令​​行)。
  • 同样,请参阅 了解何时使用 -File (-f) 与何时使用 -Command (-c).

  • test-drive命令行,从cmd.exe控制台window调用它,或者,按顺序要模拟 no-shell 调用,请使用 WinKey-RRun 对话框)并使用 -NoExit 作为第一个参数以保持结果控制台 window 打开。

    • 不要内部PowerShell进行测试,因为PowerShell自己的解析规则会导致不同 调用的解释,特别是关于识别 '...' (single-quoting) 和潜在 up-front 扩展 $-前缀标记。

Unix 上,不适用 特殊注意事项(这包括 Unix-on-Windows 环境,例如 WSL 和 Git Bash):

  • 只需要满足调用shell的语法要求即可。通常,PowerShell CLI 的编程调用在 Unix 上使用 POSIX-compatible 系统默认值 shell,/bin/sh),这意味着在 "..." 字符串中, 嵌入 " 必须转义为 \"$ 字符应作为 $ 通过 传递给 PowerShell ;这同样适用于来自 POSIX-compatible shell 的交互式调用,例如 bash;例如:

    # From Bash: $$ is interpreted by Bash, (escaped) $PID by PowerShell.
    $ pwsh -nop -c " Write-Output \"Caller PID: $$; PowerShell PID: $PID \" "
    
    # Use single-quoting if the command string need not include values from the caller:
    $ pwsh -nop -c ' Write-Output "PowerShell PID: $PID" '
    

关于Windows,事情比较复杂:

  • '...' (single-quoting) 只能与 -Command (-c) 一起使用,永远不会有 syntactic[= PowerShell CLI 命令行 上的 555=] 函数;也就是说,当 parsed-from-the-command-line 参数稍后被解释为 PowerShell 代码时,单引号总是被保留并被解释为逐字字符串文字;有关详细信息,请参阅

  • "..." (double-quoting) 有句法 command-line 功能,并且 未转义 双引号被 剥离 ,在 -Command (-c) 的情况下意味着它们是 而不是 被视为 PowerShell 最终执行的代码的一部分。 " 字符你想 retain 必须 escaped - 即使你通过你的命令作为单独的参数而不是作为单个字符串的一部分。

    • powershell.exe 需要将 " 转义为 \" [3] (sic) - 尽管 PowerShell 中,它是 ` (反引号)作为转义字符;然而 \" 是最广泛建立的转义 " 字符的约定。在 Windows 命令行 .

      • 不幸的是,如果两个 \" 实例之间的字符恰好包含 cmd.exe 元字符,例如 &|稳健但笨重且晦涩的选择是"^"";但是,\" 通常 可以工作。

        :: powershell.exe: from cmd.exe, use "^"" for full robustness (\" often, but not always works)
        powershell.exe -nop -c " Write-Output "^""Rock  &  Roll"^"" "
        
        :: With double nesting (note the ` (backticks) needed for PowerShell's syntax).
        powershell.exe -nop -c " Write-Output "^""The king of `"^""Rock  &  Roll`"^""."^"" "
        
        :: \" is OK here, because there's no & or similar char. involved.
        powershell.exe -nop -c " Write-Output \"Rock  and  Roll\" "
        
    • pwsh.exe 接受 \""".

      • "" 是从 cmd.exe 调用时的稳健选择("^"" 确实 not 工作稳健,因为它规范化空白;同样,\" 通常 ,但并不总是有效)。

        :: pwsh.exe: from cmd.exe, use "" for full robustness
        pwsh.exe -nop -c " Write-Output ""Rock  &  Roll"" "
        
        :: With double nesting (note the ` (backticks)).
        pwsh.exe -nop -c " Write-Output ""The king of `""Rock  &  Roll`""."" "
        
        :: \" is OK here, because there's no & or similar char. involved.
        pwsh.exe -nop -c " Write-Output \"Rock  and  Roll\" "
        
    • no-shell调用场景,\"可以在两个版本中安全使用;例如,从 Windows Run 对话框 (WinKey-R);请注意,第一个命令将从 cmd.exe break& 将被解释为 cmd.exe 的语句分隔符,并且它会尝试 e在退出 PowerShell 会话时执行名为 Roll 的程序;尝试不使用 -noexit 以立即查看问题):

        pwsh.exe -noexit -nop -c " Write-Output \"Rock  &  Roll\" "
      
        pwsh.exe -noexit -nop -c " Write-Output \"The king of `\"Rock  &  Roll`\".\" "
      

另请参阅:

  • 引用头疼同样适用于场景:调用外部程序来自一个PowerShell会话:参见this answer

  • 当从 cmd.exe 调用时,%...%-封闭的标记如 %USERNAME%cmd.exe本身解释为(环境)变量引用,在前面,无论是在不加引号的情况下还是在"..."字符串中使用(并且cmd.exe没有[=126=的概念] 字符串开头)。虽然通常需要,有时需要避免这种情况,不幸的是,解决方案取决于命令是交互式还是来自批处理文件.cmd.bat):参见this answer.


[1] 这也适用于 PowerShell in-session 与外部程序的通信。

[2] 在 Unix 上,不存在 process-level 命令行,PowerShell 只会接收一个 verbatim 参数数组,这些参数是调用 shell 对其命令行的解析结果。

[3] ""的使用半断;从 cmd.exe.

尝试 powershell.exe -nop -c "Write-Output 'Nat ""King"" Cole'"