首页 > 解决方案 > 在 PowerShell 中使用变量将多个参数传递给外部程序

问题描述

我下载了用于合并 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

这不起作用。错误是

错误:找不到文件

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

标签: powershelljunitparameter-passingparameter-splatting

解决方案


# 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-like shell bashbash中,变量的值$command- 由于被引用未引用- 将受到分(所谓的shell 扩展之一)的影响,并且确实会导致3 个不同的参数(尽管即使存在基于数组的调用也会更可取)。
PowerShell 不支持bash-like shell 扩展[2];它具有不同的,通常更灵活的构造,例如下面讨论的喷溅技术。

相反,将您的参数定义为array 的单个元素,正如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 技术的应用程序,您可以在其中指定要通过变量传递给命令的参数:

  • 要么(通常仅用于外部程序,如您的情况):

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

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

  • 作为传递命名参数值的哈希表,您必须将变量引用中的符号替换为; 例如,在您的情况下;例如,以下等效于调用:$@@commandGet-ChildItem C:\ -Directory

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


警告基于数组的喷溅

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

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

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


可选使用@基于数组的喷溅

您也可以将@sigil 与arrays一起使用,所以这也可以:

junit-merge @command

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

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

只有@语法将数组元素识别--%为特殊的停止解析符号--%

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

这通常仅在使用 splatting 时才有用,通常是在能够按cmd.exe原样使用从 PowerShell 编写的命令行的上下文中,而不必考虑 PowerShell 的语法差异。

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

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

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


$command[1] 您的调用意味着将变量的值作为单个参数传递的意图,因此当 PowerShell 在幕后构建命令行时,它会双引号a.xml b.xml c.xml包含其中包含的逐字字符串$command以确保这一点。请注意,这些双引号与您最初将值分配给$command. "不幸的是,对于带有嵌入字符的值,这种自动引用被破坏了。- 例如,请参阅此答案

[2] 作为对类 POSIX shell 的致敬,PowerShell确实执行了一种 shell 扩展,但 (a) 仅在类 Unix 平台(macOS、Linux)和 (b) 仅在调用外部程序时:不带引号的通配符模式,例如*.txt当您调用外部程序(例如)时, as确实会扩展为它们匹配的文件名/bin/echo *.txt,这是 PowerShell 调用本机 globbing的功能。


推荐阅读