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.

2 Answers 2

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).

Sign up to request clarification or add additional context in comments.

3 Comments

So is it better to use the double-quoted approach or ($a + '\' + $f.BaseName)?
@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.
@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 (...)
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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.