2

I'm trying to understand how Powershell processes variable expressions on a command line (as a parameter to a cmdlet, for instance). I can't seem to understand exactly how it parses expressions involving multiple variables (and/or properties on variables). Here are some example based on the following predefined variables:

$a = 'abc'
$b = 'def'
$f = Get-ChildItem .\Test.txt   # assuming such a file exists in the current directory

Example 1:

echo $a$b

Output: abcdef

Example 2:

echo $a\$b

Output: abc\def

Example 3:

echo $f.BaseName

Output: Test

Example 4:

echo $a\$f.BaseName

Output: abc\C:\Test.txt.BaseName

Basically, I don't understand why I can combine two variables (examples 1 and 2), and I can use variable properties (example 3), but I can't combine variables with other variable properties (example 4). I've tried various escape sequences (using the backtick) to no avail.

I realize I can accomplish this using $() style expressions, like:

echo $($a + '\' + $f.BaseName)

I just don't understand why the other form (example 4) is invalid--it reads cleaner in my opinion.

glancep
  • 356
  • 5
  • 18

2 Answers2

3

tl;dr:

The safest option is to always use explicit double-quoting for compound arguments, so that the usual string expansion (interpolation) rules apply.

Applied to your examples (I'm using Write-Output, because that's what echo is an alias for in PowerShell):

Write-Output "$a$b" # example 1
Write-Output "$a\$b" # example 2
Write-Output "$a\$($f.BaseName)" # example 4 - note the required $(...)

The only exception is example 3, because there you're not dealing with a compound argument, but rather a single expression:

Write-Output $f.BaseName # example 3: OK without quoting
Write-Output "$($f.BaseName)" # equivalent with double-quoting

PowerShell mostly treats compound arguments as if they were double-quoted (i.e., expandable) strings, but there are ultimately too many exceptions for this behavior to be useful.

This GitHub issue summarizes all the surprising behaviors.


As for your specific questions:

I can't combine variables with other variable properties (example 4).

Actually, echo $a\$f.BaseName is a case where the compound token is implicitly treated as if it were enclosed in "...", and it is precisely because of that that you need to enclose $f.BaseName in $(...), because that's what the string-expansion rules require.

echo $a\$($f.BaseName)

I realize I can accomplish this using $() style expressions, like: echo $($a + '\' + $f.BaseName)

Actually, it's better and more efficient to simply use (...) in this case, because what you want to evaluate is a single expression:

echo ($a + '\' + $f.BaseName)

briantist's helpful answer has more on the difference between (...) (single-statement only) and $(...) (multiple statements, with pipeline logic).

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • So is it better to use the double-quoted approach or `($a + '\' + $f.BaseName)`? – glancep Jun 06 '18 at 21:10
  • @glancep: I'd say that's a matter of taste, though `(...)` is technically probably more efficient (that will rarely matter in real life, though). However, if your expression must evaluate to something other than a _string_, use of `(...)` is a must. – mklement0 Jun 06 '18 at 21:20
  • 1
    @glancep: To pick up on EBGreen's tip, though: your specific example is more robustly written as `(Join-Path $a $f.BaseName)` - note how it is another _command_ that's nested inside `(...)` – mklement0 Jun 06 '18 at 21:23
2

There are some subtle differences between $() and (). I would say that in your example, and in most, you should use ().

$() can be used when you need something more complex, or expressions that don't work in (). Note that the output of $() is basically like the output a pipeline, so some things you might not expect will happen. For example look at the output of these two:

(1..10 -as [string[]]).GetType()
$(1..10 -as [string[]]).GetType()

In the second case, the [string[]] array was unrolled and then re-grouped as PowerShell's default array output type of [object[]].

See this GitHub issue for more information about the vagaries of how arguments are treated and parsed when unquoted and ungrouped.

briantist
  • 45,546
  • 6
  • 82
  • 127