在 PowerShell 中使用变量将多个参数传递给外部程序

Use a variable in PowerShell to pass multiple arguments to an external program

我下载了用于合并 junit 报告的 npm 包 - https://www.npmjs.com/package/junit-merge

问题是我有多个文件要合并,我正在尝试使用字符串变量来保存要合并的文件名。

当我像这样编写脚本 myslef 时:

junit-merge a.xml b.xml c.xml 

这有效,正在创建合并文件,但是当我这样做时喜欢

$command = "a.xml b.xml c.xml"
junit-merge $command

这不起作用。错误是

Error: File not found

有没有人遇到过类似的问题?

# WRONG

$command = "a.xml b.xml c.xml"; junit-merge $command

命令行结果 junit-merge "a.xml b.xml c.xml"[1],即它传递字符串 a.xml b.xml c.xml 作为 单个参数 junit-merge,这不是本意。

PowerShell 在这方面不像那样表现得像POSIX-像shell如bash那样:在bash,变量 $command 的值 - 由于被引用 未引用 - 将受到 分词 (其中之一 -称为 shell expansions) 并且确实会产生 3 个不同的参数(尽管即使有基于数组的调用也是可取的)。
PowerShell 不支持 bash-like shell 扩展[2];它具有不同的、通常更灵活的构造,例如下面讨论的溅射技术。

相反,将您的参数定义为数组的单个 元素,正如 justnotme 建议的那样:

# Define the *array* of *individual* arguments.
$command = "a.xml", "b.xml", "c.xml"

# Pass the array to junit-merge, which causes PowerShell
# to pass its elements as *individual arguments*; it is the equivalent of:
#     junit-merge  a.xml  b.xml  c.xml
junit-merge $command

这是 一种称为 splatting 的 PowerShell 技术的应用,您可以在其中指定要通过 变量[=162] 传递给命令的参数=]:

  • 两者之一(通常仅用于外部程序,如您的情况):

  • 作为一个 数组 参数作为 位置参数单独传递 ,如上图

  • 或者(在调用 PowerShell 命令 时更常见):

  • 作为hashtable传递named参数值,在您必须 将变量引用中的 $ 标记替换为 @;例如,在您的情况下 @command;例如,以下等效于调用 Get-ChildItem C:\ -Directory:

  • $paramVals = @{ LiteralPath = 'C:\'; Directory = $true }; Get-ChildItem @paramVals


警告基于数组的展开:

由于 this GitHub issue 中详述的错误,PowerShell 不会将 参数传递给外部程序(仍然适用于 Windows PowerShell 5.1 / PowerShell [Core] 7.0,并且可能永远不会更改以保持向后兼容性)。

例如,foo.exe "" 意外导致仅 foo.exe 被调用。

这个问题同样会影响基于数组的展开,因此
$cmdArgs = "", "other"; foo.exe $cmdArgs 结果是 foo.exe other 而不是预期的 foo.exe "" other.


可选择使用 @ 和基于数组的展开:

您也可以将 @ 印记与 数组 一起使用,所以这也可以工作:

junit-merge @command

然而,有一个微妙的区别。

虽然在实践中很少有影响, 更安全的选择是使用 $,因为它可以防止(尽管是假设的)意外误解您打算成为 [=110] 的 --% 数组元素=]文字.

只有 @ 语法将数组元素 --% 识别为特殊的 stop-parsing symbol, --%

所述符号告诉 PowerShell 不要像通常那样解析剩余的参数,而是按原样传递它们 - 未扩展,除了扩展 cmd.exe 样式的变量引用,例如 %USERNAME%

这通常仅在 使用 splatting 时有用,通常是在能够使用从 PowerShell 原样为 cmd.exe 编写的命令行的情况下,无需考虑 PowerShell 的语法差异。

然而,在 splatting 的上下文中,--% 导致的行为并不明显,最好避免:

  • 与直接参数传递一样,--% 从生成的命令行中 删除

  • 参数边界丢失,因此单个数组元素foo bar,通常在命令中放置为"foo bar"行,被放置为 foo bar,即有效地作为 2 个参数。


[1] 您的调用暗示了将变量 $command 的值作为 单个参数 传递的意图,因此当 PowerShell 构建命令时在幕后,它逐字双引号 a.xml b.xml c.xml 包含在 $command 中的字符串以确保这一点。请注意,这些双引号与您最初为 $command 赋值的方式无关。 不幸的是,对于嵌入 " 个字符的值,这种自动引用被破坏了。 - 例如参见 [​​=59=]。

[2] 作为对 POSIX-like shells 的点头,PowerShell 确实 执行一种 shell 扩展,但 (a) 仅在类 Unix 平台上(macOS,Linux)和 (b) 仅在调用外部程序时:不带引号的通配符模式如 *.txt 确实被扩展为其匹配的文件名当您调用外部程序(例如 /bin/echo *.txt)时,这是 PowerShell 调用 native globbing.

的功能

我遇到了类似的问题。 powershell 的这项技术对我有用:

Invoke-Expression "junit-merge $command"

我还尝试了以下方法(来自 powershell 脚本)并且有效:

cmd / c "junit-merge $command"