Null coalescing in powershell

82k Views Asked by At

Is there a null coalescing operator in powershell?

I'd like to be able to do these c# commands in powershell:

var s = myval ?? "new value";
var x = myval == null ? "" : otherval;
16

There are 16 best solutions below

6
On BEST ANSWER

Powershell 7+

Powershell 7 introduces native null coalescing, null conditional assignment, and ternary operators in Powershell.

Null Coalescing

$null ?? 100    # Result is 100

"Evaluated" ?? (Expensive-Operation "Not Evaluated")    # Right side here is not evaluated

Null Conditional Assignment

$x = $null
$x ??= 100    # $x is now 100
$x ??= 200    # $x remains 100

Ternary Operator

$true  ? "this value returned" : "this expression not evaluated"
$false ? "this expression not evaluated" : "this value returned"

Previous Versions:

No need for the Powershell Community Extensions, you can use the standard Powershell if statements as an expression:

variable = if (condition) { expr1 } else { expr2 }

So to the replacements for your first C# expression of:

var s = myval ?? "new value";

becomes one of the following (depending on preference):

$s = if ($myval -eq $null) { "new value" } else { $myval }
$s = if ($myval -ne $null) { $myval } else { "new value" }

or depending on what $myval might contain you could use:

$s = if ($myval) { $myval } else { "new value" }

and the second C# expression maps in a similar way:

var x = myval == null ? "" : otherval;

becomes

$x = if ($myval -eq $null) { "" } else { $otherval }

Now to be fair, these aren't very snappy, and nowhere near as comfortable to use as the C# forms.

You might also consider wrapping it in a very simple function to make things more readable:

function Coalesce($a, $b) { if ($a -ne $null) { $a } else { $b } }

$s = Coalesce $myval "new value"

or possibly as, IfNull:

function IfNull($a, $b, $c) { if ($a -eq $null) { $b } else { $c } }

$s = IfNull $myval "new value" $myval
$x = IfNull $myval "" $otherval

As you can see a very simple function can give you quite a bit of freedom of syntax.

UPDATE: One extra option to consider in the mix is a more generic IsTrue function:

function IfTrue($a, $b, $c) { if ($a) { $b } else { $c } }

$x = IfTrue ($myval -eq $null) "" $otherval

Then combine that is Powershell's ability to declare aliases that look a bit like operators, you end up with:

New-Alias "??" Coalesce

$s = ?? $myval "new value"

New-Alias "?:" IfTrue

$ans = ?: ($q -eq "meaning of life") 42 $otherval

Clearly this isn't going to be to everyone's taste, but may be what you're looking for.

As Thomas notes, one other subtle difference between the C# version and the above is that C# performs short-circuiting of the arguments, but the Powershell versions involving functions/aliases will always evaluate all arguments. If this is a problem, use the if expression form.

1
On

This is only half an answer to the first half of the question, so a quarter answer if you will, but there is a much simpler alternative to the null coalescing operator provided the default value you want to use is actually the default value for the type:

string s = myval ?? "";

Can be written in Powershell as:

([string]myval)

Or

int d = myval ?? 0;

translates to Powershell:

([int]myval)

I found the first of these useful when processing an xml element that might not exist and which if it did exist might have unwanted whitespace round it:

$name = ([string]$row.td[0]).Trim()

The cast to string protects against the element being null and prevents any risk of Trim() failing.

0
On

I use this constraction I think it's work on overversion

$myval=$null
$myval2 = "b"
if ($s = $myval) {} elseif ($s = $myval2 ) {} else {$s = "***"}
Write-Host $s
3
On

If you install the Powershell Community Extensions Module then you can use:

?? is the alias for Invoke-NullCoalescing.

$s = ?? {$myval}  {"New Value"}

?: is the alias for Invoke-Ternary.

$x = ?: {$myval -eq $null} {""} {$otherval}
0
On

Finally, PowerShell 7 got Null Coalescing assignment operators!

PS > $null ?? "a"
a
PS > "x" ?? "y"
x
PS > $x = $null
PS > $x ??= "abc"
PS > $x
abc
PS > $x ??= "xyz"
PS > $x
abc
0
On

If you really want null coalescing that short-circuits in PowerShell 5.1, one way of doing it is through the use of script blocks.

In this example, $var is the variable we want to set.

foreach ($var in {$null},{'foo'},{'bar'}){ if ($var = &$var){ break } }

# OR

$var = foreach ($_ in {$null},{'foo'},{'bar'}){ if($_ = &$_){ $_; break } }

# OR

$var = &{ foreach ($_ in {$null},{'foo'},{'bar'}){ if ($_ = &$_){ return $_ } } }

The syntax is very messy though. So, I decided to turn it into a function that handles regular values and scriptblocks as well as accepting both stdin and arguments. The idea is that the first object from left-to-right that doesn't evaluate as $null is returned.

function NullCoalesce {
  param(
    [Parameter(ValueFromPipeline,ParameterSetName='Stdin')]
    $InputObject,
    [Parameter(ValueFromRemainingArguments)]
    $Args
  )
  Begin   
  { 
    $List = [Collections.Generic.List[PSObject]]::new() 
  }
  Process
  {
    if ('Stdin' -eq $PSCmdlet.ParameterSetName){
      $List.Add($_)
    }
  }
  End
  {
    if ($Args.Count -ne 0){
      $Args | &{process{ $List.Add($_) }}
    }
    foreach ($o in $List){
      if ('System.Management.Automation.ScriptBlock' -in $o.PSObject.TypeNames){
        $Value = & $o
      } else {
        $Value = $o
      }
      if ($null -ne $Value){
        return $Value
      }
    }
  }
}

Examples:

$null, { 'foo' }, 1 | NullCoalesce
# returns: foo

1..10 | NullCoalesce 'foo' 'bar'
# returns: 1

NullCoalesce { 1 } 2 { Write-Host 'This will not evaluate' }
# returns: 1

The reason I designed it like this is, if you want to null-coalesce a bunch of pipeline objects but you also want a fallback value in case they all evaluate to $null, you simply put this function at the end of the pipeline with the fallback value as an argument. This makes it useful for PowerShell 7+ as well.

0
On
function coalesce {
   Param ([string[]]$list)
   #$default = $list[-1]
   $coalesced = ($list -ne $null)
   $coalesced[0]
 }
 function coalesce_empty { #COALESCE for empty_strings

   Param ([string[]]$list)
   #$default = $list[-1]
   $coalesced = (($list -ne $null) -ne '')[0]
   $coalesced[0]
 }
0
On

The classic answers for Powershell 6 and earlier use functions which are inherently non-short-circuiting. Meaning that they only accept two values and will unconditionally evaluate both sides before evaluating the condition.

You can instead use a fully short-circuiting function, by accepting a ScriptBlock. You can also make this an InputObject so that you can just shove it into a pipeline

function ?? {
    param (
        [Parameter(ValueFromPipeline)]
        $InputObject,

        [Parameter(Mandatory, Position=1)]
        [ScriptBlock]
        $If,

        [Parameter(Position=2)]
        [ScriptBlock]
        $Else
    )
    if ($PSBoundParameters.ContainsKey("Else")) {
        if ($InputObject) {
            return (Invoke-Command $If);
        }
        return (Invoke-Command $Else);
    }
    else {
        if ($InputObject -ne $null) {
            return $InputObject;
        }
        return (Invoke-Command $If);
    }
}

You use it like this

$s = $myval | ?? { "new value" };
$x = $myval | ?? { "" } { $otherval };

Not quite as snappy as Powershell 7, but only a few extra characters.

Note the following:

  • In your example, you can remove the check on $myval -eq null and just pass $myval directly, because that is implicit in the if in Powershell.
  • The input parameter comes straight from the pipeline, and ScriptBlock parameters are positional, so that cuts down the verbosity.

A similar option is something similar to SQL's COALESCE, allowing an array of ScriptBlock to be passed in, and the first non-null result is returned.

function Coalesce {
    param (
        [Parameter(Mandatory, Position=0)]
        [ScriptBlock[]]
        $Scripts
    )
    return (
        $Scripts |
        % { Invoke-Command $_ } |
        where { $_ } |
        select -First 1);
}

You can use it like this

$x = Coalesce {Some-Calculation},{Other-Function};

Note that select -First 1 is clever enough to end the iteration after the first result, and % and where are likewise executed iteratively.

0
On

Closest I can get is: $Val = $MyVal |?? "Default Value"

I implemented the null coalescing operator for the above like this:

function NullCoalesc {
    param (
        [Parameter(ValueFromPipeline=$true)]$Value,
        [Parameter(Position=0)]$Default
    )

    if ($Value) { $Value } else { $Default }
}

Set-Alias -Name "??" -Value NullCoalesc

The conditional ternary operator could be implemented in a similary way.

function ConditionalTernary {
    param (
        [Parameter(ValueFromPipeline=$true)]$Value,
        [Parameter(Position=0)]$First,
        [Parameter(Position=1)]$Second
    )

    if ($Value) { $First } else { $Second }
}

Set-Alias -Name "?:" -Value ConditionalTernary

And used like: $Val = $MyVal |?: $MyVal "Default Value"

0
On
@($null,$null,"val1","val2",5) | select -First 1
11
On

PowerShell 7 and later

PowerShell 7 introduces many new features and migrates from .NET Framework to .NET Core. As of mid-2020, it hasn't completely replaced legacy versions of PowerShell due to the reliance on .NET Core, but Microsoft has indicated that they intend for the Core family to eventually replace the legacy Framework family. By the time you read this, a compatible version of PowerShell may come pre-installed on your system; if not, see https://github.com/powershell/powershell.

Per the documentation, the following operators are supported out-of-the-box in PowerShell 7.0:

  1. Null-coalescing: ??
  2. Null-coalescing assignment: ??=
  3. Ternary: ... ? ... : ...

These work as you would expect for null coalescing:

$x = $a ?? $b ?? $c ?? 'default value'
$y ??= 'default value'

Since a ternary operator has been introduced, the following is now possible, though it's unnecessary given the addition of a null coalescing operator:

$x = $a -eq $null ? $b : $a

As of 7.0, the following are also available if the PSNullConditionalOperators optional feature is enabled, as explained in the docs (1, 2):

  1. Null-conditional member access for members: ?.
  2. Null-conditional member access for arrays et al: ?[]

These have a few caveats:

  1. Since these are experimental, they're subject to change. They may no longer be considered experimental by the time you read this, and the list of caveats may have changed.
  2. Variables must be enclosed in ${} if followed by one of the experimental operators because question marks are permitted in variable names. It's unclear if this will be the case if/when the features graduate from experimental status (see issue #11379). For example, ${x}?.Test() uses the new operator, but $x?.Test() runs Test() on a variable named $x?.
  3. There is no ?( operator as you might expect if you're coming from TypeScript. The following won't work: $x.Test?()

PowerShell 6 and earlier

PowerShell versions prior to 7 do have an actual null coalescing operator, or at least an operator that is capable of such behavior. That operator is -ne:

# Format:
# ($a, $b, $c -ne $null)[0]
($null, 'alpha', 1 -ne $null)[0]

# Output:
alpha

It's a bit more versatile than a null coalescing operator, since it makes an array of all non-null items:

$items = $null, 'alpha', 5, 0, '', @(), $null, $true, $false
$instances = $items -ne $null
[string]::Join(', ', ($instances | ForEach-Object -Process { $_.GetType() }))

# Result:
System.String, System.Int32, System.Int32, System.String, System.Object[],
System.Boolean, System.Boolean

-eq works similarly, which is useful for counting null entries:

($null, 'a', $null -eq $null).Length

# Result:
2

But anyway, here's a typical case to mirror C#'s ?? operator:

'Filename: {0}' -f ($filename, 'Unknown' -ne $null)[0] | Write-Output

Explanation

This explanation is based on an edit suggestion from an anonymous user. Thanks, whoever you are!

Based on the order of operations, this works in following order:

  1. The , operator creates an array of values to be tested.
  2. The -ne operator filters out any items from the array that match the specified value--in this case, null. The result is an array of non-null values in the same order as the array created in Step 1.
  3. [0] is used to select the first element of the filtered array.

Simplifying that:

  1. Create an array of possible values, in preferred order
  2. Exclude all null values from the array
  3. Take the first item from the resulting array

Caveats

Unlike C#'s null coalescing operator, every possible expression will be evaluated, since the first step is to create an array.

1
On

$null, $null, 3 | Select -First 1

returns

3

0
On

I had a similar problem...

that was far harder to simplify when you are dealing with array indexes that do not exist...

or even weirder (but in Powershell can happen) if you are trying to assign from another variable that is not defined/created yet...

Examples:

    $x = $y         # and $y id not defined/created yet
       -or-
    $x = $args[2]   # and args has less than 3 elements

oddly enough I 'guesstimated' a few alternatives, and one of them became very handy...
I think it has to do with the funcional-programming approximation by Powershell
and the fact of 'ungrabed' stack values stay in the pipe for the next consumer...

so this works flawlessly:
    $x = try{ $y }      catch { 1 }    # $x becomes 1
    $x = try{ $a[ 2 ] } catch { 2 }    #$ x becomes 2
    $x = try{ $a[ 2 ] } catch {   }    #$ x becomes $null
  • it simplified lots of code to me...
  • and even help for advanced processing of optional parameters*

hope it helps!

0
On

Often I find that I also need to treat empty string as null when using coalesce. I ended up writing a function for this, which uses Zenexer's solution for coalescing for the simple null coalesce, and then used Keith Hill's for null or empty checking, and added that as a flag so my function could do both.

One of the advantages of this function is, that it also handles having all elements null (or empty), without throwing an exception. It can also be used for arbitrary many input variables, thanks to how PowerShell handles array inputs.

function Coalesce([string[]] $StringsToLookThrough, [switch]$EmptyStringAsNull) {
  if ($EmptyStringAsNull.IsPresent) {
    return ($StringsToLookThrough | Where-Object { $_ } | Select-Object -first 1)
  } else {
    return (($StringsToLookThrough -ne $null) | Select-Object -first 1)
  }  
}

This produces the following test results:

Null coallesce tests:
1 (w/o flag)  - empty/null/'end'                 : 
1 (with flag) - empty/null/'end'                 : end
2 (w/o flag)  - empty/null                       : 
2 (with flag) - empty/null                       : 
3 (w/o flag)  - empty/null/$false/'end'          : 
3 (with flag) - empty/null/$false/'end'          : False
4 (w/o flag)  - empty/null/"$false"/'end'        : 
4 (with flag) - empty/null/"$false"/'end'        : False
5 (w/o flag)  - empty/'false'/null/"$false"/'end': 
5 (with flag) - empty/'false'/null/"$false"/'end': false

Test code:

Write-Host "Null coalesce tests:"
Write-Host "1 (w/o flag)  - empty/null/'end'                 :" (Coalesce '', $null, 'end')
Write-Host "1 (with flag) - empty/null/'end'                 :" (Coalesce '', $null, 'end' -EmptyStringAsNull)
Write-Host "2 (w/o flag)  - empty/null                       :" (Coalesce('', $null))
Write-Host "2 (with flag) - empty/null                       :" (Coalesce('', $null) -EmptyStringAsNull)
Write-Host "3 (w/o flag)  - empty/null/`$false/'end'          :" (Coalesce '', $null, $false, 'end')
Write-Host "3 (with flag) - empty/null/`$false/'end'          :" (Coalesce '', $null, $false, 'end' -EmptyStringAsNull)
Write-Host "4 (w/o flag)  - empty/null/`"`$false`"/'end'        :" (Coalesce '', $null, "$false", 'end')
Write-Host "4 (with flag) - empty/null/`"`$false`"/'end'        :" (Coalesce '', $null, "$false", 'end' -EmptyStringAsNull)
Write-Host "5 (w/o flag)  - empty/'false'/null/`"`$false`"/'end':" (Coalesce '', 'false', $null, "$false", 'end')
Write-Host "5 (with flag) - empty/'false'/null/`"`$false`"/'end':" (Coalesce '', 'false', $null, "$false", 'end' -EmptyStringAsNull)
0
On

This array-based solution for pre-Powershell 7 was simplest for me:

    function Coalesce ($list=@()){ 
        # Return first non-null entry in array
        <#
        Coalesce "a","b","c"
        Coalesce $null,"b","c"
        Coalesce $null,$null,"c"
        #>
        ForEach ($list_i in $list){
            if ($null -ne $list_i) {Return $list_i}
        }
    }

I really liked Zenexer's simple answer (use -ne), but VSCode complains about $null being on the right, and we can't have that:

    ($null, 'alpha', 1 -ne $null)[0]
1
On

This is likewise a function for pre Powershell 7, intending to emulate the emulating the SQL colesce

function Get-FirstNonNull {
    [Alias('Coalesce','Get-Coalesed')]
    param()

    end {
        $Things = @($input).ForEach{

            if ([string]::IsNullOrEmpty($_)) {
                $null
            } else { $_ }

        }


        $Things | Select-Object -First 1
    }

}