问题描述
我有一个参数数组 $p = "a","b c","d`"e"
要传递给命令。
我可以这样做:
& my_command $p
现在,我需要在一些外部包装器(二进制)中调用相同的命令,它使用固定参数的工作方式如下:
& my_wrapper /Command=my_command "/CommandParameters=a 'b c' 'd`"e'"
但是如何从数组 /CommandParameters
中传递 $p
?
一种适用于某些输入的方法是像这样预处理数组:
$p = $p | Foreach {"\`"$_\`""}
& my_wrapper /Command=my_command "/CommandParameters=$p"
但这似乎很脆弱。 $p
中的值确实包含空格和引号。
在 Bash 中,我可以使用 printf %q
来正确地转义参数。
解决方法
以下将数组中的所有字符串包含在嵌入的 "..."
中,并额外转义预先存在的嵌入的 "
字符。如\"
,这是外部程序通常所期望的:
注意:在Windows PowerShell 和PowerShell (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 中总结。
预览 版本现在带有 experimental feature PSNativeCommandArgumentPassing
,这是一个尝试修复,但是,不幸的是,它似乎缺少 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
) 带有向后和向前兼容的帮助函数,{ {1}},这也避免了额外转义的需要,同时还包含实验性功能中缺少的 Windows 上高调 CLI 的重要调整:
ie
至于你尝试了什么:
[将值包含在嵌入的 # 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
在 Bash 中,我可以使用 /CommandParameters="a" "b c" "d\"e"
来正确地转义参数。
不幸的是,PowerShell 没有等效功能,但不难提供它:
Powershell 中的元引用字符串:
注意:
-
元引用我的意思是 Bash 的
printf %q
提供的功能:它格式化给定的字符串值以使其可用作字符串文字在源代码中。例如(这个例子说明了一般原理,而不是printf %q
的实际行为),逐字字符串值printf %q
被转换为逐字字符串值a b
,后者可以用作构造存储在字符串中的命令行时的参数。 -
所需的方法取决于元引用的字符串是在 PowerShell 命令(例如 cmdlet 调用)的字符串表示中使用,还是在调用外部程序,鉴于他们不同的逃逸需求。此外,虽然 Windows 上的大多数外部程序(也)将
"a b"
理解为转义的\"
,但某些 - 特别是批处理文件和"
- 仅理解msiexec.exe
。
以下命令使用以下示例输入输入字符串,其中包含 ""
和 '
(为了引用方便,通过逐字 here-string 构造):
"
以下解决方案使用 -f
operator 来合成结果字符串,不仅是为了概念清晰,而且是为了解决字符串插值错误 可能导致嵌入在 expandable strings 中的子表达式产生不正确的结果(例如,$str = @'
6'1" tall
'@
和 "a$('""')b"
都产生逐字 "a$('`"')b"
- 一个 a"b
/ "
缺失);另一种方法是使用带有 `
的简单字符串连接。
- 不幸的是,看起来这些错误不会被修复,以保持向后兼容性;见GitHub issue #3039
结果字符串的逐字内容显示在源代码注释中,括在 +
中;例如«...»
(此表示仅用于说明;PowerShell 不支持此类分隔符)。
元引用外部程序:
注意:
-
在 Windows 上,控制台应用程序通常只在其命令行中将双引号识别为字符串分隔符,因此这就是以下解决方案的重点。 要创建一个单引用的表示,它可以被 POSIX 兼容的 shell(例如
«"6'1\""»
)理解,请使用以下内容:bash
,产生逐字"'{0}'" -f ($str -replace "'","'\''")
(原文如此)。 -
在 Windows 的边缘情况下,您可能必须绕过 PowerShell 的命令行解析,以便完全控制用于幕后实际进程创建的命令行,通过
'6'\''1" tall'
,stop-parsing symbol,它有严格的限制,或者通过将调用委托给--%
,通过将整个命令行传递给cmd.exe
- 再次,参见 {{3 }}。
对于需要/c
-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 的外部程序(例如,批处理文件,""
- 仅限 Windows):
msiexec
元引用用于 PowerShell 命令:
注意:
- 幸运的是,在这种情况下不需要额外转义;上面讨论的参数传递错误只影响对外部程序的调用。
创建双引号表示(# 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""»
):
"..."
创建一个单引用表示('"{0}"' -f ($str -replace '"','`"') # -> «"6'1`" tall"»
):
'...'