Powershell中的空合并


115

Powershell中是否有空合并运算符?

我希望能够在powershell中执行以下c#命令:

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

Answers:


133

Powershell 7以上

Powershell 7在Powershell中引入了本机空合并,空条件赋值和三元运算符。

空合并

$null ?? 100    # Result is 100

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

空条件赋值

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

三元运算符

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

之前的版本:

不需要Powershell社区扩展,可以使用标准的Powershell if语句作为表达式:

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

因此,替换您的第一个C#表达式:

var s = myval ?? "new value";

变为以下之一(取决于首选项):

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

或取决于$ myval可能包含的内容,您可以使用:

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

第二个C#表达式以类似的方式映射:

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

变成

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

公平地说,它们并不十分敏捷,并且使用起来还不如C#表单舒适。

您也可以考虑将其包装在一个非常简单的函数中,以使内容更具可读性:

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

$s = Coalesce $myval "new value"

或可能为IfNull:

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

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

如您所见,一个非常简单的函数可以为您提供很大的语法自由度。

更新:混合中要考虑的另一个选项是更通用的IsTrue函数:

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

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

然后结合使用Powershell声明看起来有点像运算符的别名的能力,最终得到:

New-Alias "??" Coalesce

$s = ?? $myval "new value"

New-Alias "?:" IfTrue

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

显然,这并不符合所有人的喜好,但可能正是您想要的。

正如Thomas所指出的那样,C#版本与上述版本之间的另一个细微差别是C#执行参数的短路,但是涉及函数/别名的Powershell版本将始终评估所有参数。如果这是一个问题,请使用if表达式形式。


6
合并运算符的唯一真实等同项是使用if语句;问题在于,任何其他方法都会评估所有操作数,而不是短路。即使$ myval不为null,“ ?? $ myval SomeReallyExpenisveFunction()”也会调用该函数。我想可能会延迟使用脚本块进行评估,但是请注意,脚本块不是闭包,事情开始变得笨拙。
Thomas S. Trias

在严格模式下不起作用-它抛出The variable '$myval' cannot be retrieved because it has not been set.
BrainSlugs83 '19

1
@ BrainSlugs83在严格模式下看到的错误与显示的空合并选项无关。这只是标准,Powershell检查是否首先定义了变量。如果您$myval = $null在进行测试之前进行设置,则该错误将消失。
StephenD

1
警告,powershell怪癖,null应该总是比较(笨拙)放在首位,即,$null -ne $a 现在找不到合适的参考,但这是长期的实践。
dudeNumber4

90

PowerShell 7及更高版本

PowerShell 7引入了许多新功能,并从.NET Framework迁移到.NET Core。截至2020年中,由于对.NET Core的依赖,它尚未完全替代PowerShell的旧版本,但微软表示,他们打算让Core系列最终替代旧的Framework系列。在您阅读本文时,系统上可能已预安装了兼容版本的PowerShell。如果没有,请参阅https://github.com/powershell/powershell

根据文档,PowerShell 7.0开箱即用地支持以下运算符:

  1. 空合并??
  2. 空销售任务??=
  3. 三元... ? ... : ...

正如您期望的那样,这些工作可以实现空合并:

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

由于引入了三元运算符,因此现在可以实现以下操作,但是由于添加了空合并运算符,因此以下操作是不必要的:

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

如为7.0,以下也可,如果PSNullConditionalOperators是可选特征启用,如在该文档(说明12):

  1. 成员的空条件成员访问: ?.
  2. 数组的空条件成员访问等: ?[]

这些有一些警告:

  1. 由于这些都是实验性的,因此可能会发生变化。在您阅读本文时,它们可能不再被视为实验性的,并且警告列表可能已更改。
  2. ${}如果后面有实验运算符之一,则必须将变量括在其中,因为变量名中允许使用问号。目前尚不清楚是否/当功能从实验状态毕业时会出现这种情况(请参阅问题#11379)。例如,${x}?.Test()使用new运算符,但在名为的变量上$x?.Test()运行Test()$x?
  3. ?(如果您来自TypeScript,则没有您可能期望的运算符。以下内容无效:$x.Test?()

PowerShell 6及更早版本

7之前的PowerShell版本确实具有实际的空合并运算符,或者至少具有能够实现这种行为的运算符。该运算符是-ne

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

# Output:
alpha

它比null合并运算符更具通用性,因为它可以构成所有非null项的数组:

$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 工作原理类似,这对计数空条目很有用:

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

# Result:
2

但是无论如何,这是反映C#??运算符的典型情况:

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

说明

该说明基于匿名用户的编辑建议。谢谢,无论您是谁!

根据操作顺序,其工作顺序如下:

  1. ,运算符创建值的阵列来进行测试。
  2. -ne操作者过滤出从匹配指定值,所述阵列中的任何项目-在此情况下,空。结果是非空值数组,其顺序与在步骤1中创建的数组相同。
  3. [0] 用于选择过滤后的数组的第一个元素。

简化为:

  1. 按首选顺序创建可能值的数组
  2. 从数组中排除所有空值
  3. 从结果数组中获取第一项

注意事项

与C#的null合并运算符不同,由于第一步是创建数组,因此将评估每个可能的表达式。


我最终在mine中使用了答案的修改版本,因为我需要允许合并中的所有实例为空,而不会引发异常。很好的做事方法使我学到了很多东西。:)
Johny Skovdal

该方法防止短路。定义function DidItRun($a) { Write-Host "It ran with $a"; return $a }并运行((DidItRun $null), (DidItRun 'alpha'), 1 -ne $null)[0]以查看此情况。
jpmc26

@ jpmc26是的,这是设计使然。
Zenexer

任何定义合并函数的尝试都可能消除短路,不是吗?
克里斯·卡罗尔

8
PowerShell的运算符优先级使我丧命!我总是忘记了逗号运算符,具有如此高的优先级。对于任何对此工作原理感到困惑的人,($null, 'alpha', 1 -ne $null)[0]与相同(($null, 'alpha', 1) -ne $null)[0]。实际上,只有两个具有较高优先级的“破折号”运算符是-split-join(和一元破折号?即-4or -'56')。
冥王星

15

这只是问题前半部分的一半答案,如果可以的话,则为四分之一答案,但是如果您要使用的默认值实际上是该类型的默认值,则空合并运算符还有一个更简单的选择:

string s = myval ?? "";

可以用Powershell编写为:

([string]myval)

要么

int d = myval ?? 0;

转换为Powershell:

([int]myval)

我发现在处理一个可能不存在的xml元素时,其中的第一个很有用,如果确实存在,则可能在其周围带有多余的空格:

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

强制转换为字符串可防止元素为null并防止任何Trim()失败的风险。


([string]$null)-> ""似乎是“有用的,但不幸的副作用” :(
user2864740


9

如果安装Powershell社区扩展模块则可以使用:

?? 是Invoke-NullCoalescing的别名。

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

?:是Invoke-Ternary的别名。

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

实际上,这不是PowerShell命令。您将它们与pscx结合在一起:?:-> Invoke-Ternary
BartekB'5

...错过了实际代码和结果。.;)Get-Command-模块pscx -CommandType别名| 其中{$ _。Name -match'\ ?.' } | foreach {“ {0}:{1}” -f $ _。Name,$ _。Definition}?::调用三进制?:Invoke-NullCoalescing
BartekB'5

糟糕...您是完全正确的。我经常忘记甚至加载了该模块。
EBGreen 2012年

5

最后,PowerShell 7获得了空合并分配运算符

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


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

2

通常,我发现使用合并时还需要将空字符串视为null。我最终为此编写了一个函数,该函数使用Zenexer解决方案对简单的空合并进行合并,然后使用Keith Hill的方法进行null或空检查,并将其添加为标志,以便我的函数可以同时执行这两个操作。

该函数的优点之一是,它还可以处理所有元素为空(或为空)而不会引发异常的情况。由于PowerShell如何处理数组输入,它也可以用于任意多个输入变量。

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

这将产生以下测试结果:

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

测试代码:

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)

1

我能得到的最接近的是: $Val = $MyVal |?? "Default Value"

我为上述实现了空合并运算符,如下所示:

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

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

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

有条件的三元运算符可以在一个与之相似的方式来实现。

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

和使用像: $Val = $MyVal |?: $MyVal "Default Value"

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.