Powershell: Support Named Token String Replacement

Created on 20 Dec 2019  Â·  9Comments  Â·  Source: PowerShell/PowerShell

Support Named Token String Replacement

As a PowerShell developer, I want to allow variable name interpolation, to improve readability and maintainability of strings used as a template.

For example:

$HereString = 
@'
use [{0}]
select * from dbo.table where Databasename like '{0}%'
'@

Right now we can do this via the example above, which means if you begin to pass in 5-6 variables, you have to maintain the 0 index position for them carefully.

More intuitive text similar to python's string formatting, and later c# string replacement might be:

$HereString = 
@'
use [{DatabaseName}]
select * from dbo.table where DatabaseName like '{DatabaseName}%'
'@

I know we can use:

$HereString = 
@"
use [$DatabaseName]
select * from dbo.table where DatabaseName like '$DatabaseName%'
"@

In most cases this works fine. However, if you are generating a text string like this that you want to pass to a parallel pipeline and iterate on then the improved syntax would help things be much more readable and intuitive.

This is a sample of where this syntax would be much more intuitive.
_Sample code, not used for-eachobject paralle, so syntax might be slightly off_

$HereString = 
@'
use [{DatabaseName}]
select * from dbo.table where DatabaseName like '{DatabaseName}%'
'@

@('Database1','Database2','Database3') | ForEach-Object -Parallel { 

$DatabaseName = $_
Invoke-Query -Query $HereString # this or some approach to include the variables by name mean I don't have to do string format replacement in this scope now

}

Note

I couldn't find any issue on supporting named tokens but I have to believe something exists. If so, please just link it and close this as duplicate and I'll add my 👍 to it. Thanks!

Issue-Discussion WG-Language

Most helpful comment

The ${variable} syntax is used for two purposes. One is to allow the use of otherwise forbidden characters in variable names; for example, ${my var} is legal syntax. Almost anything is allowed, except use of curly braces themselves.

The other use is to be explicit about where the variable name ends: "my string is ${var}text" -- this prevents the parser reading the variable name as $vartext when it should just be $var.

All 9 comments

This will works:

@('Database1','Database2','Database3') | ForEach-Object -Parallel { 

$DatabaseName = $_

$HereString = 
@"
use [{DatabaseName}]
select * from dbo.table where DatabaseName like '{DatabaseName}%'
"@

Invoke-Query -Query $HereString # this or some approach to include the variables by name mean I don't have to do string format replacement in this scope now

}

You can use $ExecutionContext.InvokeCommand.ExpandString to expand a verbatim string as if it were an expandable one on demand:

$HereString = 
@'
use [${DatabaseName}]
select * from dbo.table where DatabaseName like '${DatabaseName}%'
'@

$DatabaseName = 'foo'

$ExecutionContext.InvokeCommand.ExpandString($HereString)

The caveat is that you should only do that if you fully control or implicitly trust the content of the string template ($HereString) to not contain malicious commands, since it's possible to embed arbitrary commands with $(...).


It would be nice to surface this functionality as a cmdlet, say Expand-String.
Such a cmdlet could then support a switch, say, -VariablesOnly to prevent execution of embedded commands.
Or, perhaps a safer alternative is to not execute embedded commands _by default_, and require opt-in via an -IncludeCommands switch.

I can't get the code in either of the two follow-up comments to work (7.0.0-rc.2), so I'm not sure if I'm missing something…but I'm pretty sure what @sheldonhull is referring to is these:

$ - string interpolation (C# reference) | Microsoft Docs
PEP 498 -- Literal String Interpolation | Python.org

It might or might not need its own signifier (like C#'s $ or Python's f), but while it's always been pretty painful doing string formatting in Powershell (-f is about the best there is, but has the same readability issues as formatting methods) it's extra-painful since f-strings dropped in Python. Put simply, given $then = [datetime]::Now, it would be good to be able to do:

$"the time was {then:HH:mm:ss} on {then:yyyy-MM-dd} and the day was {then.DayOfWeek}" ($ is just an example character to denote that it's an interpolated string, it could be anything)`

or even simpler

"the time was {$then:HH:mm:ss} on {$then:yyyy-MM-dd} and the day was {$then.DayOfWeek}"

rather than

"the time was {0:HH:mm:ss} on {0:yyyy-MM-dd} and the day was $($then.DayOfWeek)" -f $then

or

"the time was {0:HH:mm:ss} on {0:yyyy-MM-dd} and the day was {1}" -f $then, $then.DayOfWeek

This is an extremely basic example and doesn't appear to offer much of a saving in terms of characters typed (in fact when referencing the same variable multiple times in the same string the numeric index method can easily use less characters in total), but you can already see that you have to mix up two different formatting/expansion methods in PS as it currently stands (or pass multiple objects to -f — and as pointed out, dealing with several items in the formatting list means that the index positions become crucial.

Aside from anything else, as soon as the list has more than a few items in it readability can be heavily compromised (to the point that I honestly don't know how Python and C# devs put up with not having interpolation for so long — it's still relatively new to both of them). It's the difference between reading a line once, from left to right, and flicking your eyes across it right to left and back as many times as there are references in the string (and potentially miscounting the variables in the list and introducing a bug as a result).

That said I'm aware that there's a desire to have Powershell not ape C# at every turn and become a pale imitation of a .NET programming language — it's a shell foremost, so if this kind of thing is overkill for a shell that makes sense to me. :)

I can't get the code in either of the two follow-up comments to work (7.0.0-rc.2)

@iSazonov's code simply shows _regular_ PowerShell string interpolation, the way that it has always worked (and long predates the analogous interpolated strings ($"...") in C#); e.g.:

"Honey, I'm $HOME; tomorrow is $((Get-Date).AddDays(1).Date)."

See below re embedded formatting instructions.

The initial post shows that @sheldonhull is aware of this option, but his use case is a different one: define a string _template_ once, and then _expand it on demand_ using _then-current_ variable values; analogous to C#'s FormattableString.

This is what my code demonstrates, and it does work, both in Windows PowerShell and PowerShell Core.
Again, it would be nice to have an Expand-String cmdlet for that, with an option to evaluate only _variable_ references, not also expressions.


You're discussing a different issue, namely the desire for embedded _formatting instructions_:

While PowerShell doesn't support embedded formatting instructions the way C# does, they are often not needed and, when they are, can be embedded:

$then = [datetime]::Now
"the time was $('{0:HH:mm:ss}' -f $then) on $('{0:yyyy-MM-dd}' -f $then) and the day was $($then.DayOfWeek)"

If you feel string strongly that support for embedded format strings is needed, please create a _new_ issue.

[Update: @stinos has since created #12259]

I do apologise, I completely missed the $ preceding the { in your example. I've never seen that before — but ${variablename} doesn't seem to behave any differently to $variablename in a string for me, what's the significance of the curly braces there?

I wasn't aware of FormattableString and the C# documentation's lacking but I see what you mean now. I shouldn't be doing things like this on Friday afternoons 🙂

I'll search again and raise an issue if I think it's worth it. It's about readability, simplicity and consistency for me, so you're correct, I am mostly after the embedded formatting aspect of it (though to be honest I find $() slows me down reading code, especially as it's so often seen combined with a $($variable)!).

Thanks for your patience.

The ${variable} syntax is used for two purposes. One is to allow the use of otherwise forbidden characters in variable names; for example, ${my var} is legal syntax. Almost anything is allowed, except use of curly braces themselves.

The other use is to be explicit about where the variable name ends: "my string is ${var}text" -- this prevents the parser reading the variable name as $vartext when it should just be $var.

No worries, @logicalextreme.

$() slows me down reading code, especially as it's so often seen combined with a $($variable)!).

Yes, $($variable) isn't pretty; just $variable will often do, or ${variable}, as @vexx32 explained (which can nowadays cause confusion for people used to C# interpolated strings).
However, it's understandable that people use $($variable), for conceptual simplicity, as it isn't readily obvious what you can and cannot embed _without_ the $(...) enclosure.

C#'s unified {...} approach is nicer, but I don't think we'll ever see a change in the long-established syntax for expandable strings in PowerShell.

Yeah, I meant using $() for variable properties and methods rather than just variables themselves, though I do expect that some people insist on it as a coding standard for consistency.

Finally figured out what had thrown me about the ${} syntax — I hadn't realised it and $() were constructs that can be used outside strings!

@sheldonhull, I've fleshed out my previous suggestion (an Expand-String cmdlet based on the regular expandable-string syntax) in #11693

If this would meet your needs, please consider closing this issue.

Was this page helpful?
0 / 5 - 0 ratings