5

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?

mklement0
452k68 gold badges726 silver badges986 bronze badges
asked Nov 27, 2019 at 12:08
1
  • 3
    Try with $command = "a.xml", "b.xml", "c.xml" instead. Commented Nov 27, 2019 at 12:15

2 Answers 2

7
# !! WRONG

$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 a string with verbatim value 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 # or: junit-merge @command (see further below)

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. In this case, even an array literal would work.
  • Or (more typically when calling PowerShell commands):

    • As a hashtable to pass named parameter values, in which case 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 GitHub issue #6280, PowerShell doesn't pass empty arguments through to external programs, in all Windows PowerShell versions and PowerShell (Core) 7 versions up to 7.2.x; the problem is fixed in v7.3+, with selective exceptions on Windows, in conjunction with fixing how arguments with embedded " are passed - see the $PSNativeCommandArgumentPassing preference variable.

E.g., up to PowerShell v7.2.x, foo.exe "" unexpectedly results in just foo.exe being called, without an argument.

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.

The workaround (which also applies in v7.3+ if $PSNativeCommandArgumentPassing = 'Legacy' is set) is to use '""' (sic).


Optional use of @ in array-based splatting with external programs:

As noted, with external programs use of @ in lieu of $ isn't necessary, because passing an array implicitly results in splatting. (By contrast, when calling a PowerShell command to which you want to pass the elements of an array as individual, positional arguments, you must use @)

However, you may choose to use @ with external programs too, and arguably it conveys the splatting intent more clearly:

junit-merge @command

There is a subtle behavioral distinction, however - though it will probably rarely, if ever, surface in practice:

The safer choice is to use $, because it guards against (the however hypothetical) accidental misinterpretation of a array element containing --% that you intend to be passed on as-is.

Only the @ syntax recognizes an array element with verbatim value --% as the special stop-parsing token, --%

Said token 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 PowerShell calls native globbing.

answered Nov 27, 2019 at 13:17
Sign up to request clarification or add additional context in comments.

Comments

-1

I had a similar problem. This technique from powershell worked for me:

Invoke-Expression "junit-merge $command"

I also tried the following (from a powershell script) and it works:

cmd / c "junit-merge $command"
answered Oct 29, 2021 at 0:03

2 Comments

In the case at hand, defining $command as an array - $command = "a.xml", "b.xml", "c.xml" - is simpler and safer.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.