在PowerShell中使用变量将多个参数传递给外部程序 [英] Use a variable in PowerShell to pass multiple arguments to an external program

查看:505
本文介绍了在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

这不起作用.错误是

错误:找不到文件

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

解决方案

$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不会像bash这样的POSIX类shell起作用,在bash中,变量$command的值-由于被引用为未引用-将进行分词(所谓的 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命令时使用):

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

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


注意基于数组的展开:

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

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

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


@与基于数组的外观一起使用:

您还可以将@标记与 arrays 一起使用,因此这也可以使用:

junit-merge @command

但是,有一个细微的区别.

尽管在实践中它几乎没有关系, 更安全的选择是使用$ ,因为它可以防止(假设的)偶然误读要成为 literal --%数组元素. /p>

@语法将数组元素--%识别为特殊的放置的单个数组元素foo bar被放置为foo bar,即有效作为 2 自变量.


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

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

I downloaded the npm package for merge junit reports - https://www.npmjs.com/package/junit-merge.

The problem is that I have multiple files to merge and I am trying to use string variable to hold file names to merge.

When I write the script myslef like:

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

This works, the merged file is being created, but when I do it like

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

This does not work. The error is

Error: File not found

Has anyone faced similar issues?

解决方案

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

results in command line junit-merge "a.xml b.xml c.xml"[1], i.e. it passes string a.xml b.xml c.xml as a single argument to junit-merge, which is not the intent.

PowerShell does not act like POSIX-like shells such as bash do in this regard: In bash, the value of variable $command - due to being referenced unquoted - would be subject to word splitting (one of the so-called shell expansions) and would indeed result in 3 distinct arguments (though even there an array-based invocation would be preferable).
PowerShell supports no bash-like shell expansions[2]; it has different, generally more flexible constructs, such as the splatting technique discussed below.

Instead, define your arguments as individual elements of an array, as justnotme advises:

# 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

This is an application of a PowerShell technique called splatting, where you specify arguments to pass to a command via a variable:

  • Either (typically only used for external programs, as in your case):

    • As an array of arguments to pass individually as positional arguments, as shown above.
  • Or (more typically when calling PowerShell commands):

    • As a hashtable to pass named parameter values, in which you must replace the $ sigil in the variable reference with @; e.g., in your case @command; e.g., the following is the equivalent of calling Get-ChildItem C:\ -Directory:

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


Caveat re array-based splatting:

Due to a bug detailed in this GitHub issue, PowerShell doesn't pass empty arguments through to external programs (still applies as of Windows PowerShell 5.1 / PowerShell [Core] 7.0, and may never change in order to preserve backward compatibility).

E.g., foo.exe "" unexpectedly results in just foo.exe being called.

This problem equally affects array-based splatting, so that
$cmdArgs = "", "other"; foo.exe $cmdArgs results in foo.exe other rather than the expected foo.exe "" other.


Optional use of @ with array-based splatting:

You can use the @ sigil also with arrays, so this would work too:

junit-merge @command

There is a subtle distinction, however.

While it will rarely matter in practice, the safer choice is to use $, because it guards against (the however hypothetical) accidental misinterpretation of a --% array element you intend to be a literal.

Only the @ syntax recognizes an array element --% as the special stop-parsing symbol, --%

Said symbol tells PowerShell not to parse the remaining arguments as it normally would and instead pass them through as-is - unexpanded, except for expanding cmd.exe-style variable references such as %USERNAME%.

This is normally only useful when not using splatting, typically in the context of being able to use command lines that were written for cmd.exe from PowerShell as-is, without having to account for PowerShell's syntactical differences.

In the context of splatting, however, the behavior resulting from --% is non-obvious and best avoided:

  • As in direct argument passing, the --% is removed from the resulting command line.

  • Argument boundaries are lost, so that a single array element foo bar, which normally gets placed as "foo bar" on the command line, is placed as foo bar, i.e. effectively as 2 arguments.


[1] Your call implies the intent to pass the value of variable $command as a single argument, so when PowerShell builds the command line behind the scenes, it double-quotes the verbatim a.xml b.xml c.xml string contained in $command to ensure that. Note that these double quotes are unrelated to how you originally assigned a value to $command. Unfortunately, this automatic quoting is broken for values with embedded " chars. - see this answer, for instance.

[2] As a nod to POSIX-like shells, PowerShell does perform one kind of shell expansion, but (a) only on Unix-like platforms (macOS, Linux) and (b) only when calling external programs: Unquoted wildcard patterns such as *.txt are indeed expanded to their matching filenames when you call an external program (e.g., /bin/echo *.txt), which is feature that PowerShell calls native globbing.

这篇关于在PowerShell中使用变量将多个参数传递给外部程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆