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

mkl*_*nt0 6 powershell quoting command-line-interface character-encoding io-redirection

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

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

其中,回答了以下问题:

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

  • 如何将要执行的 PowerShell 代码传递给 CLI?怎么办-Command-c)和-File-f)有什么区别?

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

  • 哪些字符编码问题会起作用?

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

mkl*_*nt0 6

PowerShell CLI 基础知识:

  • PowerShell的版本:传统的CLI,捆扎带窗口的Windows PowerShell版本是powershell.exe,而在于跨平台的,安装点播PowerShell的(核心)7+版本是pwsh.exe(只是pwsh在类Unix平台)。

  • 互动使用

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

    • 使用-NoLogo以抑制进入交互式会话(不需要,如果代码来执行传递)时所显示的文本启动。GitHub 问题 #15644建议默认情况下显示此启动文本。

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

  • 参数和默认值

    • 所有参数名称都不区分大小写(就像 PowerShell 通常一样);大多数参数都有短别名,例如-h-?for -Help,它显示命令行帮助,其中 with pwsh(但不是powershell.exe)也列出了这些短别名。

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

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

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

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

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

      • 请参阅此答案以获取有关何时使用-Commandvs. 的指导-File,以及用于引用/转义注意事项的底部部分。

      • 它是明智的使用-Command-c)或-File-f明确,因为这两个版本有不同的默认值

        • powershell.exe默认为-Command( -c)
        • pwsh默认为-File( -f),这是在类 Unix 平台上支持 shebang 行所必需的更改。
      • 不幸的是,即使使用-Command( -c) 或-File( -f),默认情况下也会加载配置文件(初始化文件)(与 POSIX 兼容的 shell 不同bash,后者仅在启动交互式shell时才这样做)。

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

        • GitHub的提案#8072讨论引入独立的CLI(可执行),它与这些参数的组合加载配置文件和也可以提高其他遗留行为,现有的可执行文件不能向后兼容的需要而改变。

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

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

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

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

        • 在 Windows 10 上,您可以切换到 UTF-8 system-wide,这会将OEM 和 ANSI 代码页设置为65001; 但是请注意,这会产生深远的影响,并且在撰写本文时该功能仍处于测试阶段 - 请参阅此答案
    • Unix的样平台(pwsh),UTF-8总是使用(即使当前的区域设置(如报告locale)是不是UTF-8为基础的,但是这是非常罕见的,这些天)。

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

    • 将 stdin 输入处理为数据

      • 需要显式使用自动$input变量

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

    • 将 stdin 输入作为代码处理pwsh仅,似乎被破坏了powershell.exe):

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

    • (除非您使用脚本块 ( { ... }),它只能PowerShell内部工作,见下文),所有6 个 PowerShell 的输出流都被发送到stdout,包括错误(!)(后者通常被发送到stderr)。

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

      • 有关更多信息,请参阅此答案的底部。


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

从 PowerShell调用(很少需要):

  • 有很少需要调用PowerShell的CLIPowerShell的,因为任何命令或脚本可以被简单地称为直接,相反,调用CLI介绍开销由于创造型的保真度的损失一个子进程和结果。

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

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

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

笔记:

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

  • -Command( -c) 参数可以作为多个参数传递,但在将结果字符串解释为 PowerShell 代码之前(就像您已在PowerShell 会话)。

    • 为了健壮性和概念清晰,最好将命令作为单个参数传递给-Command( -c),这在 Windows 上需要一个引号字符串 ( "...")(尽管整体"..."外壳并不是严格必要的健壮性shell 调用环境,如任务调度程序和一些 CI/CD 和配置管理环境,即在它不是cmd.exe首先处理命令​​行的情况下)。
  • 同样,请参阅此答案以获取有关何时使用-File( -f) 与何时使用-Command( -c) 的指导。

  • 测试驱动命令行,请从cmd.exe控制台窗口调用它,或者,为了模拟无外壳调用,使用WinKey-RRun对话框)和-NoExit用作第一个参数以保持生成的控制台窗口打开。

    • 难道不是从测试的PowerShell,因为PowerShell的自己的分析规则将导致不同的呼叫的解释,尤其是关于认识'...'的(单引号)和潜在的前期扩张$-prefixed令牌。

Unix 上没有特殊考虑(这包括 Unix-on-Windows 环境,例如 WSL 和 Git Bash):

  • 您只需要满足调用shell 的语法要求。通常,PowerShell CLI 的编程调用在 Unix 上使用与 POSIX 兼容的系统默认 shell,/bin/sh),这意味着在"..."字符串内部,嵌入的 "必须转义为\",而$应该传递给 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" '
    
    Run Code Online (Sandbox Code Playgroud)

Windows 上,事情更复杂:

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

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

    • 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\" "
        
        Run Code Online (Sandbox Code Playgroud)
    • 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\" "
        
        Run Code Online (Sandbox Code Playgroud)
    • 无shell调用场景下两个版本\"可以放心使用;例如,从 WindowsRun对话框 ( WinKey-R); 注意第一个命令将打破cmd.exe&会被解释成cmd.exe的语句分隔符,它会试图执行一个名为程序Roll在退出PowerShell会话;尝试没有-noexit立即看到这个问题):

        pwsh.exe -noexit -nop -c " Write-Output \"Rock  &  Roll\" "
      
        pwsh.exe -noexit -nop -c " Write-Output \"The king of `\"Rock  &  Roll`\".\" "
      
      Run Code Online (Sandbox Code Playgroud)

也可以看看:

  • 引用令人头疼的问题也适用于相反的场景:PowerShell 会话调用外部程序:请参阅此答案

  • 从打电话时cmd.exe%...%-enclosed令牌如%USERNAME%解释为(环境)变量引用cmd.exe本身,前面,既使用无引号和内部时,"..."字符串(和cmd.exe不具有的概念'...'串与开始)。虽然通常需要,但有时需要防止这种情况发生,不幸的是,解决方案取决于命令是以交互方式调用还是从批处理文件( .cmd, .bat)调用:请参阅此答案


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

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

[3]使用""半断; 尝试powershell.exe -nop -c "Write-Output 'Nat ""King"" Cole'"cmd.exe.