使用 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

这行不通。错误是

错误:找不到文件

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

# 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那样bash做:在bash中,变量$command的值 - 由于被引用不加引号- 将受到单词拆分(所谓的shell扩展之一)的影响,并且确实会产生3个不同的参数(尽管即使在那里,基于数组的调用也会更好bash><)。;它具有不同的,通常更灵活的结构,例如下面讨论的喷溅技术。

相反,将您的参数定义为数组的各个元素,正如 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 命令时):

    • 作为传递命名参数值的哈希表,在这种情况下,您必须将变量引用中的$符号替换为@;例如,在您的情况下@command;例如,以下内容相当于调用Get-ChildItem C: -Directory

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


Caveat re-based Array-splatting

由于GitHub问题#6280中详述的错误,PowerShell不会将参数传递给外部程序(适用于所有Windows PowerShell版本/最高为PowerShell(核心)7.2.x。此问题已在 7.3 中修复,Windows 上有选择性例外,同时修复了具有嵌入式"的参数的传递方式 - 请参阅$PSNativeCommandArgumentPassing首选项变量。

例如,在PowerShell v7.2.x之前,foo.exe ""意外地导致只调用foo.exe

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

解决方法(如果设置了$PSNativeCommandArgumentPassing = 'Legacy',则也适用于 v7.3+)是使用'""'(原文如此)。


在基于阵列的外部程序拼接中可选使用@

如前所述,对于外部程序,不需要使用@代替$,因为传递数组会隐式导致拼接。(相比之下,当调用要将数组元素作为单个位置参数传递到的PowerShell命令时,必须使用@)

但是,您也可以选择将@与外部程序一起使用,可以说它更清楚地传达了拼接意图:

junit-merge @command

然而,有一个微妙的行为区别 - 尽管在实践中可能很少出现:

更安全的选择是使用$,因为它可以防止(无论假设的)对包含您打算按原样传递--%的数组元素的意外误解。

只有@语法将具有逐字值--%的数组元素识别为特殊的停止分析标记--%

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

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

然而,在溅射的情况下,--%导致的行为并不明显,最好避免:

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

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


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

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

我也有类似的问题。Powershell的这种技术对我有用:

Invoke-Expression "junit-merge $command"

我还尝试了以下内容(来自powershell脚本)并且它可以工作:

cmd / c "junit-merge $command"

相关内容

最新更新