如何转义字符串以将其传递给另一个命令?

How to escape a string to pass it to another command?

我有一组参数$p = "a", "b c", "d`"e"要传递给命令。 我可以这样做:

& my_command $p

现在,我需要在一些外部包装器(二进制)中调用相同的命令,它使用固定参数像这样工作:

& my_wrapper /Command=my_command "/CommandParameters=a 'b c' 'd`"e'"

但是如何从数组 $p 传递 /CommandParameters


一种适用于某些输入的方法是像这样预处理数组:

$p = $p | Foreach {"\`"$_\`""}
& my_wrapper /Command=my_command "/CommandParameters=$p"

但这似乎很脆弱。 $p 中的值包含空格和引号。


在 Bash 中,我可以使用 printf %q 来正确转义参数。

以下 将数组中的所有字符串包含在嵌入的 "..." 中,并另外转义 预先存在的 嵌入的 " 字符。作为 \",这是外部程序通常期望的:

注意:Windows PowerShellPowerShell(核心)中,至少 v7.1附加\转义是 - 不幸的是 - 在将带有嵌入式"的参数传递给外部程序[=211时需要=]:

$p = "a", "b c", "d`"e"
$pEscaped = $p.ForEach({ '\"{0}\"' -f ($_ -replace '"', '\\"') })
& my_wrapper /Command=my_command "/CommandParameters=$pEscaped"

注意:虽然 $pEscaped 是字符串的 集合 (数组),但在 expandable string"...") 自动创建一个 space 分隔的单行表示;例如"$('foo', 'bar')" 逐字输出 foo bar

这个长期存在的问题 - 意外需要手动 \- 转义嵌入的 " 字符。向 外部程序 传递参数时 - 在 this answer.

中进行了总结

预览 v7.2 版本现在带有 experimental feature PSNativeCommandArgumentPassing, which is an attempted fix, but, unfortunately, it looks like it will lack important accommodations for high-profile CLIs on Windows - see this summary from GitHub issue #15143。但是,修复 对于期望 " 被转义为 \" 的可执行文件有效,因此解决方案简化为(使用字符串连接(+ ) 在表达式 ((...)) 中构造内联参数):

# Note: Requires experimental feature PSNativeCommandArgumentPassing
#       to be turned on, available in preview versions of v7.2
& my_wrapper /Command=my_command ('/CommandParameters=' + $p.ForEach({ '"{0}"' -f ($_ -replace '"', '\"') }))

注:

  • 假设此实验性功能成为官方功能(通常无法保证),更正后的行为很可能是选择加入[=211] =],通过偏好变量$PSNativeCommandArgumentPassing = 'Standard''Legacy' 选择旧行为)。

如果您不介意安装第三方模块(由我编写),Native module (Install-Module Native) 附带 向后和向前兼容辅助函数,ie,它也避免了额外转义的需要,同时还包含实验功能中缺少的 Windows 上高调 CLI 的重要调整:

# Note: Assumes `Install-Module Native` was called.
# Just use `ie` instead of `&`
ie my_wrapper /Command=my_command ('/CommandParameters=' + $p.ForEach({ '"{0}"' -f ($_ -replace '"', '\"') }))

至于你试过的:

[Enclosing the value in embedded "] seems pretty fragile

如果你另外将任何预先存在的"转义为\"\\",如果必须补偿参数传递错误) ,如上所示,它运行稳健。

最终,必须执行以下命令行,PowerShell 在幕后构造:

my_wrapper /Command=my_command "/CommandParameters=\"a\" \"b c\" \"d\\"e\""

my_wrapper 解析其命令行时,它最终看到以下逐字字符串作为最后一个参数:

/CommandParameters="a" "b c" "d\"e"

In Bash, I could use printf %q to properly escape the parameters.

不幸的是,PowerShell 没有 等效功能,但不难临时提供它:


Powershell 中的元引用字符串:

注:

  • meta-quoting 我的意思是 Bash 的 printf %q 提供的功能:它将给定的字符串值格式化为在源代码中可用作字符串文字。例如(这个例子说明了一般原则,而不是 printf %q 的实际行为),逐字字符串值 a b 被转换为逐字字符串值 "a b",后者可以用作构造存储在字符串中的命令行时的参数。

  • 所需的方法取决于是否要在 PowerShell 命令(例如 cmdlet 调用)的字符串表示中使用元引号字符串或调用 外部程序 ,因为它们有不同的转义需求。此外,虽然 Windows 上的大多数外部程序(也)将 \" 理解为转义的 ",但一些 - 特别是批处理文件和 msiexec.exe - 只理解 "" .

下面的命令使用以下示例输入字符串,其中包含 '"(为了引用方便,通过逐字 here-string 构造):

$str = @'
6'1" tall
'@

下面的解决方案使用 -f operator to synthesize the result strings, not just for conceptual clarity, but also to work around string interpolation bugs that can cause subexpressions embedded in expandable strings 来产生不正确的结果(例如,"a$('""')b""a$('`"')b" 都逐字产生 a"b - 一个 " / ` 缺失);另一种方法是使用简单的字符串 concatenation+.

  • 不幸的是,看起来这些错误不会被修复,以保持向后兼容性;见 GitHub issue #3039

结果字符串的逐字内容显示在源代码注释中,包含在 «...» 中;例如«"6'1\""»(此表示仅供说明;PowerShell 支持此类分隔符)。

元引用外部程序:

注:

  • 在 Windows 上,控制台应用程序通常只在其命令中将 引号识别为字符串定界符线,所以这就是下面的解决方案所关注的。 要创建一个 单引号 引号表示,可以被 POSIX 兼容的 shell(例如 bash 理解,请使用以下命令:
    "'{0}'" -f ($str -replace "'", "'\''"),逐字生成 '6'\''1" tall'(原文如此)。

  • 在Windows的边缘情况下,您可能不得不绕过PowerShell的命令行解析,从而完全控制命令行用于在幕后创建实际进程,通过 --%、具有严格限制的 停止解析符号 或将调用委托给 cmd.exe , 通过将整个命令行传递给 /c - 再次参见 this answer.

对于需要\"-escaping(典型值)的外部程序:

# With the experimental, pre-v7.2 PSNativeCommandArgumentPassing
# feature turned on, you can directly pass the result
# as an external-program argument.
# Ditto with the `ie` helper function.
'"{0}"' -f ($str -replace '"', '\"') # -> «"6'1\" tall"»

# With additional \-escaping to compensate for PowerShell's
# argument-passing bug, required up to at least v7.1
'\"{0}\"' -f ($str -replace '"', '\\"') # -> «\"6'1\\" tall\"»

对于需要 ""-escaping 的外部程序(例如,批处理文件,msiexec - Windows-only):

# CAVEAT: With the experimental, pre-v7.2 PSNativeCommandArgumentPassing
#         feature turned on, passing the result as an external-program argument 
#         will NOT work as intended, because \" rather than "" is invariably used.
#         By contrast, the `ie` helper function automatically
#         switches to "" for batch files, msiexec.exe and msdeploy.exe
#         and WSH scripts.
'"{0}"' -f ($str -replace '"', '""') # -> «"6'1"" tall"»

# With additional escaping to compensate for PowerShell's
# argument-passing bug, required up to at least v7.1
'""{0}""' -f ($str -replace '"', '""""') # -> «""6'1"""" tall""»

元引用 PowerShell 命令:

注:

  • 幸运的是,在这种情况下不需要额外的转义;上面讨论的参数传递错误只影响对外部程序.
  • 的调用

创建一个 double-quoted 表示 ("..."):

'"{0}"' -f ($str -replace '"', '`"') # -> «"6'1`" tall"»

创建-引号表示('...'):

"'{0}'" -f ($str -replace "'", "''") # -> «'6''1" tall'»