【问题标题】:How do I call the PowerShell CLI robustly, with respect to character encoding, input and output streams, quoting and escaping?如何在字符编码、输入和输出流、引用和转义方面稳健地调用 PowerShell CLI?
【发布时间】:2024-06-11 04:10:01
【问题描述】:

这个自我回答的问题旨在为 Windows PowerShell (powershell.exe) 和 @ 提供 PowerShell CLI(命令行界面)的系统概述 987654321@(Windows 上为pwsh.exe,Unix 上为pwsh)。

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

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

  • 特定于版本的 CLI 有何不同?

  • 如何将要执行的 PowerShell 代码传递给 CLI? -Command (-c) 和 -File (-f) 有何不同?

  • 传递给这些参数的参数如何需要被引用和转义?

  • 什么字符编码问题起作用?

  • PowerShell CLI 如何处理 stdin 输入,它们产生什么 stdout / stderr 以及以什么格式?

【问题讨论】:

    标签: powershell character-encoding command-line-interface io-redirection quoting


    【解决方案1】:

    PowerShell CLI 基础知识:

    • PowerShell 版本:与 Windows 捆绑的旧版 Windows PowerShell 版本的 CLI 是 powershell.exe strong>,而跨平台按需安装的 PowerShell (Core) 7+ 版本是 pwsh.exe(只是 pwsh在类 Unix 平台上)。

    • 互动使用

      • 默认情况下,除非指定要执行的代码(通过-Command (-c) 或-File-f,见下文),否则会进入交互式会话。但是,与 POSIX 兼容的 shell 不同,例如如bash您可以使用-NoExit 在执行代码后 进入交互式会话。当调用 CLI 时,这对于排除命令行问题特别方便预先存在的控制台窗口。

      • 使用 -NoLogo 禁止在进入交互式会话时显示启动文本(如果传递了要执行的代码,则不需要)。 GitHub issue #15644 建议显示此启动文本默认情况下

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

    • 参数和默认值

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

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

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

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

        • -File (-f) 用于调用脚本文件 (.ps1) 传递参数,这些参数是被视为逐字值。

        • 这些参数必须在命令行最后出现,因为所有后续参数都被解释为正在传递的命令/脚本文件调用的一部分。

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

        • 建议使用-Command (-c) 或-File (-f) 明确,因为两个版本有不同默认值

          • powershell.exe 默认为 -Command (-c)
          • pwsh 默认为 -File (-f),这是在类 Unix 平台上支持 shebang 行所必需的更改。
        • 不幸的是,即使使用-Command (-c) 或-File (-f),profiles(初始化文件)也会默认加载 (不像 POSIX 兼容的 shell,例如 bash,它只在启动 interactive shell 时这样做)。

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

          • GitHub proposal #8072 讨论引入一个单独的 CLI(可执行文件),它结合这些参数加载配置文件,并且还可以改进现有可执行文件不能的其他遗留行为为了向后兼容而改变。

    • 字符编码(适用于输入和输出流)

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

        • 请注意,虽然您可以使用 -OutputFormat xml (-of xml) 来获取 CLIXML 输出,该输出使用 XML 进行对象序列化,但这种特殊格式在 PowerShell之外几乎没有用处;通过标准输入 (-InputFormat xml / -if xml) 接受 CLIXML 输入也是如此。
      • Windows 上,PowerShell CLI 尊重控制台的代码页,这反映在 chcp 和 PowerShell 内部 [Console]::InputEncoding 的输出中。控制台的代码页默认为系统的活动 OEM 代码页

        • 警告:美国英语系统上的 OEM 代码页(例如 437)是固定的,单字节字符编码总共限制为 256 个字符。 要获得完整的 Unicode 支持,您必须切换到代码页 65001 调用 PowerShell CLI(从 cmd.exe 调用 chcp 65001);虽然这在两个 PowerShell 版本中都有效,但不幸的是,powershell.exe 在这种情况下会将控制台切换为光栅字体,这会导致许多 Unicode 字符无法正确显示;但实际数据不受影响。

          • 在 Windows 10 上,您可以切换到 UTF-8系统范围,它将 OEM 和 ANSI 代码页设置为 65001;但是请注意,这会产生深远的影响,并且在撰写本文时该功能仍处于测试阶段 - 请参阅 this answer
      • Unix 类平台 (pwsh) 上,始终使用 UTF-8(即使活动语言环境(如报告locale不是基于 UTF-8 的,但现在这种情况非常罕见)。

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

      • 将标准输入输入作为数据

        • 需要明确使用automatic $input variable

        • 这反过来意味着为了将标准输入输入传递给 脚本文件 (.ps1),-Command (-c) 而不是 -File (-f) 必须使用。请注意,这会使传递给脚本的任何参数(下面用 ... 表示)受 PowerShell 解释(而 -File 将逐字使用):
          -c "$Input | ./script.ps1 ..."

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

        • 虽然通过标准输入传递 PowerShell 代码以执行原则上是有效的(默认情况下,这意味着 -File -,也适用于 -Command -),但它表现出不受欢迎的伪交互行为并阻止传递参数:见GitHub issue #3223;例如:
          echo "Get-Date; 'hello'" | pwsh -nologo -nop
    • 输出-流(stdout、stderr)处理

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

        • 但是,当您应用-external-stderr 重定向时,您可以选择性地抑制错误流输出(2>NUL 来自cmd.exe,Unix 上的2>/dev/null)或将其发送到文件(2>errs.txt)。

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


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

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

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

    • 如果您仍然需要,最可靠的方法是使用script block ({ ... }),这样可以避免所有引用问题,因为您可以像往常一样使用 PowerShell 自己的语法。请注意,only 在 PowerShell 内部使用脚本块,并且您不能在脚本块中引用 caller's 变量;但是,您可以使用-args 参数将参数(基于调用者的变量)传递给脚本块,例如pwsh -c { "args passed: $args" } -args foo, $PID;使用脚本块对于输出流和支持字符串以外的数据类型有额外的好处;见this answer

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

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

    注意:

    • -File (-f) 参数必须作为单个参数传递:脚本文件路径,后跟传递给脚本的参数(如果有) .在 Window[2] 上剥离(非转义)双引号后,PowerShell 会逐字使用脚本文件路径和传递参数。

    • -Command (-c) 参数可以作为多个参数传递,但最后 PowerShell 只是将它们与空格连接在一起,在 Windows 上剥离(非转义)双引号之后,之前将生成的字符串解释为 PowerShell 代码(就像您在 PowerShell 会话中提交它一样)。

      • 为了稳健性和概念清晰,最好将命令作为单个参数传递给-Command (-c),这在 Windows 上需要一个 引号字符串 ("...")(尽管整个 "..." 外壳对于无 shell 调用环境(如任务计划程序和某些 CI)的健壮性并不是绝对必要的/CD 和配置管理环境,即在不是 cmd.exe 首先处理命令​​行的情况下)。
    • 再次,请参阅 this answer,了解何时使用 -File (-f) 与何时使用 -Command (-c) 的指导。

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

      • 不要inside PowerShell 进行测试,因为 PowerShell 自己的解析规则会导致对调用的不同解释,特别是在识别方面'...'(单引号)和$-前缀标记的潜在前期扩展。

    Unix 上,没有需要特殊注意事项(这包括 Unix-on-Windows 环境,例如 WSL 和 Git Bash):

    • 您只需要满足调用 shell 的语法要求。通常,PowerShell CLI 的编程调用在 Unix 上使用与 POSIX 兼容的系统默认 shell,/bin/sh),这意味着在 "..." 字符串中,嵌入 " 必须转义为 @987654458 @ 和 $ 应作为 \$ 传递到 到 PowerShell 的字符;这同样适用于来自 POSIX 兼容 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 上,情况更复杂:

    • '...'(单引号)只能与-Command-c)一起使用,并且在PowerShell CLI 命令行上永远不会有语法功能;也就是说,当从命令行解析的参数稍后被解释为 PowerShell 代码时,单引号始终被保留并解释为逐字字符串文字;请参阅this answer 了解更多信息。

    • "..."(双引号)确实有语法命令行功能,未转义双引号被去掉,在-Command (-c) 的情况意味着它们被视为 PowerShell Ultimate 执行的代码的一部分。 "保留的字符必须转义 - 即使您将命令作为单独的参数而不是作为单个字符串的一部分传递.

      • powershell.exe 要求 " 转义为 \"[3](原文如此) - 即使 PowerShell 中,`(反引号)充当转义字符;但是\" 是最广泛使用的转义" 字符的约定。在 Windows 命令行上。

        • 不幸的是,如果两个\" 实例之间的字符碰巧包含cmd.exe 元字符(例如&|),则从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 调用时的可靠选择("^"" 确实 正常工作,因为它标准化了空白;同样,\"通常 >,但并不总是有效)。

          :: 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);请注意,第一个命令将 breakcmd.exe& 将被解释为 cmd.exe 的语句分隔符,它会在退出 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会话:见@ 987654339@.

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


    [1] 这也适用于 PowerShell 与外部程序的会话中通信。

    [2] 在不存在进程级命令行的 Unix 上,PowerShell 只接收 verbatim 参数数组,这些参数是调用 shell 对其命令的解析结果行。

    [3] "" 的使用一半损坏;从cmd.exe 尝试powershell.exe -nop -c "Write-Output 'Nat ""King"" Cole'"

    【讨论】:

      最近更新 更多