Null coalescing in powershell

2019-01-10 16:19发布

问题:

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;

回答1:

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 expression is:

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 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 no where 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.



回答2:

Yes, PowerShell does 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.



回答3:

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.



回答4:

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}


回答5:

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

returns

3



回答6:

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]
 }


回答7:

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)


回答8:

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"