PowerShell Hacks: Ternarys and Null-coalescing operators
It took me a long time to actually start using PowerShell for my daily scripting tasks, mainly beacuse I was so damn good at CMD shell scripts, and it was such a hassle to learn to do everything differently.
However, as I worked more with PowerShell, I got to like it a lot, and now use it for virtually all my automation needs.
Daily dose of what’s wrong
A couple of big gripes come from the lack of a decent ternary operator in the language–which is a very terse way of cramming a whole if/else statement into a single expression:
A C# Example:
// The ugly, bloated mess
string dude;
if( age > 50 ) {
dude = "Old Man"
} else {
dude = "Young Punk"
}
// Using a ternary, cleans up your code!
var dude = age > 50 ? "Old Man" : "Young Punk";
That time when someone tried to fix it with some duck-tape
Sadly, there exists no comparable feature in PowerShell. Searching the internets, I found an attempt to make something that is kinda the same:
From a blog post by Jeffrey Snover:
# ---------------------------------------------------------------------------
# Name: Invoke-Ternary
# Alias: ?:
# Author: Karl Prosser
# Desc: Similar to the C# ? : operator e.g.
# _name = (value != null) ? String.Empty : value;
# Usage: 1..10 | ?: {$_ -gt 5} {"Greater than 5;$_} {"Not greater than 5";$_}
# ---------------------------------------------------------------------------
set-alias ?: Invoke-Ternary -Option AllScope -Description "PSCX filter alias"
filter Invoke-Ternary ([scriptblock]$decider, [scriptblock]$ifTrue, [scriptblock]$ifFalse)
{
if (&$decider) {
&$ifTrue
} else {
&$ifFalse
}
}
Which lets one use a construct that looks like this:
dude = (?: {$age -gt 50} {"Old Man"} {"Young Punk"})
sigh … I’ll give high marks for terse, but … not really the same readability as a C-style ternary.
Hack like nobody is watching
I have made (in my ever-so-humble opinion) a far smarter way to accomplish the support of a Ternary in PowerShell.
Let’s take a look at some examples, and I’ll show the code to accomplish this at the end.
Simple, straightforward ternary
$x == ( 'a' -eq 'a' ) ? "yes" : "no"
echo "Result: $x"
Result: yes
How about if it’s false
$x == ('a' -eq 'b' ) ? "that would be not correct" : "of course they are not equal"
echo "Result: $x"
Result: of course they are not equal
More fun
$x == ('a' -lt 'b' ) ? ('a' -ne 'b') : ('a' -eq 'b' )
echo "Result: $x"
Result: True
The other thing that’s missing from a PowerShell is a null-coalescing operator. In a c# example:
// An ugly, bloated mess
var answer = SomeFunction();
if( answer == null ) {
answer = "not found";
}
// Using the null-coalescing operator:
var answer = SomeFunction() ?? "not found";
Which offers a clean, tight and simple way of saying if the answer is null, then use this answer instead. Maybe we can do the samething in PowerShell?
How much would you pay for a null-coalescing operator like C# ?
$z == $null ?? "This works!"
echo "Result: $z"
Result: This works!
Of course, it still thinks like powershell so 0, false and $null are all still ‘negative’
$b == (1 +2 -3) ?? 100
echo "Result: $b"
Result: 100
And regular numbers work nice:
$b == (1 +2 +3) ?? 100
echo "Result: $b"
Result : 6
Let’s try some more complicated examples
function get-null { return $null }
function get-somevalue { return "SomeValue" }
function invoke-sample {
# this is what I've wanted for years!
return = { get-null } ?? { get-somevalue }
}
echo "Result: $(invoke-sample)"
Result: SomeValue
A slight variation
function invoke-sample {
# this is what I've wanted for years!
return = ( get-null ) ?? ( get-somevalue )
}
echo "Result: $(invoke-sample)"
Result: SomeValue
What does this one do?
function invoke-sample {
# this is what I've wanted for years!
return = get-null ?? ( get-somevalue )
}
echo "Result: $(invoke-sample)"
# perhaps not so surpising...
get-null
We can drop the pretenses; you should have a clue by now.
= $null ?? { "still" + "right" }
stillright
A couple more bits of fun:
echo (= 0 ? 100 : 200 )
echo (= 1 ? 100 : 200 )
200
100
Taking a peek behind the curtain
The ever-so-clever PowerShell enthusiasts will have probably guessed why this works.
It turns out that the power of PowerShell’s aliases is actually quite amazing, and when combined with a means
of evaluating a parameter-regardless if it’s an scriptblock or just a value
made it pretty simple to ‘extend’ assignment with an extra equal sign =
# ---------------------------------------------------------------------------
# Name: Invoke-Assignment
# Alias: =
# Author: Garrett Serack (@FearTheCowboy)
# Desc: Enables expressions like the C# operators:
# Ternary:
# <condition> ? <trueresult> : <falseresult>
# e.g.
# status = (age > 50) ? "old" : "young";
# Null-Coalescing
# <value> ?? <value-if-value-is-null>
# e.g.
# name = GetName() ?? "No Name";
#
# Ternary Usage:
# $status == ($age > 50) ? "old" : "young"
#
# Null Coalescing Usage:
# $name == (get-name) ?? "No Name"
# ---------------------------------------------------------------------------
# returns the evaluated value of the parameter passed in,
# executing it, if it is a scriptblock
function eval($item) {
if( $item -ne $null ) {
if( $item -is "ScriptBlock" ) {
return & $item
}
return $item
}
return $null
}
# an extended assignment function; implements logic for Ternarys and Null-Coalescing expressions
function Invoke-Assignment {
if( $args ) {
# ternary
if ($p = [array]::IndexOf($args,'?' )+1) {
if (eval($args[0])) {
return eval($args[$p])
}
return eval($args[([array]::IndexOf($args,':',$p))+1])
}
# null-coalescing
if ($p = ([array]::IndexOf($args,'??',$p)+1)) {
if ($result = eval($args[0])) {
return $result
}
return eval($args[$p])
}
# neither ternary or null-coalescing, just a value
return eval($args[0])
}
return $null
}
# alias the function to the equals sign (which doesn't impede the normal use of = )
set-alias = Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment."
Now, go forth, and bring terseness and compaction to your scripts!