【问题标题】:How to escape a string to pass it to another command?如何转义字符串以将其传递给另一个命令?
【发布时间】:2021-08-30 16:21:59
【问题描述】:

我有一组参数$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 正确转义参数。

【问题讨论】:

  • my_wrapper 是二进制可执行文件还是 PowerShell 函数?
  • @MathiasR.Jessen 二进制可执行文件。

标签: powershell parameter-passing quoting


【解决方案1】:

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

注意:Windows PowerShellPowerShell (Core) 至少 v7.1 中,额外的\-escaping - 不幸的是 - 在将带有嵌入式 " 的参数传递给外部程序时需要:

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

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

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

Preview 版本的 v7.2 现在附带 experimental featurePSNativeCommandArgumentPassing,这是一个尝试修复,但不幸的是,看起来它对于 Windows 上的高调 CLI 缺少重要的调整 - 请参阅来自 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 '"', '\"') }))

注意:

  • 假设此实验性功能成为官方功能(通常无法保证),更正后的行为很可能是选择加入,通过 偏好变量$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 '"', '\"') }))

至于你尝试了什么

[将值包含在嵌入的"] 似乎很脆弱

如果您另外将任何预先存在的" 转义为\"\\\",如果必须补偿参数传递错误),如上所示,它可以正常工作。

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

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

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

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

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

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


Powershell 中的元引用字符串:

注意:

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

  • 所需的方法取决于元引用字符串是用于 PowerShell 命令的字符串表示(例如 cmdlet 调用)还是用于调用 外部程序,考虑到他们不同的转义需求。此外,虽然 Windows 上的大多数外部程序(也)将\" 理解为转义的",但有些——尤其是批处理文件和msiexec.exe——只理解""

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

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

以下解决方案使用 -f operator 来合成结果字符串,这不仅是为了概念清晰,也是为了解决字符串插值错误 这可能导致嵌入在expandable strings 中的子表达式产生不正确的结果(例如,"a$('""')b""a$('`"')b" 都逐字产生a"b - 一个" / ` 缺失);另一种方法是使用简单的字符串 concatenation+

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

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

元引用外部程序

注意:

  • 在 Windows 上,控制台应用程序通常仅将 引号识别为命令行中的字符串分隔符,因此以下解决方案重点关注这一点。 要创建一个 引用的表示,它可以被 POSIX 兼容的 shell(例如 bash)理解,请使用以下命令:
    "'{0}'" -f ($str -replace "'", "'\''"),它会产生逐字的 @ 987654386@(原文如此)。

  • 在 Windows 的边缘情况下,您可能必须完全绕过 PowerShell 的命令行解析,以便完全控制用于在幕后创建实际进程的命令行,或者通过 @ 987654387@,stop-parsing symbol,它有严格的限制,或者通过将调用委托给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):

# 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 命令

注意:

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

创建一个 双引号 表示 ("...")

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

创建一个引用的表示 ('...')

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

【讨论】:

    最近更新 更多