我刚刚遇到Where-Object
的一个意外行为,我找不到任何解释:
$foo = $null | Where-Object {$false}
$foo -eq $null
> True
($null, 1 | Measure-Object).Count
> 1
($foo, 1 | Measure-Object).Count
> 1
($null, $null, 1 | Measure-Object).Count
> 1
($foo, $foo, 1 | Measure-Object).Count
> 0
如果Where-Object
的条件为假,则$foo
应为$null
(看起来是正确的)。
然而,在任何值进入管道之前,管道$foo
至少两次似乎会破坏它
是什么原因造成的?
其他不一致:
($foo, $null, 1 | Measure-Object).Count
> 1
($foo, $null, $foo, 1 | Measure-Object).Count
> 0
($null, $foo, $null, 1 | Measure-Object).Count
> 1
($foo, 1, $foo, $foo | Measure-Object).Count
> 1
($null, $foo, $null, $foo, 1 | Measure-Object).Count
> 0
tl;dr:
-
并非所有明显的
$null
值都是相同的,正如Jeroen Mostert的评论所示:PowerShell有两种类型的null,它们在不同情况下表现不同-请参阅下一节。 -
此外,您还看到可能令人惊讶的
Measure-Object
行为和管道错误-请参阅底部部分。-
最好从测试命令中删除
Measure-Object
,只需在数组上直接调用.Count
;例如(在您的问题中创建null类型的最简单方法是:$foo = & {}
):($foo, $null, 1).Count
产生3
($null, $foo, $null, $foo, 1).Count
产生5
-
正如您所看到的,两种类型的null(下面讨论)都正确地成为数组的元素。
-
PowerShell中有两种不同的空值:
-
存在真正的标量null(例如,对应于C#中的
null
)。- 此null包含在自动
$null
变量中 - .NET方法可能会返回它。(虽然PowerShell代码也可能输出它,但最好避免这样做)
- 此null包含在自动
-
还有可枚举";collection null">(根据其类名,也称为"AutomationNull"),从技术上讲,它是
System.Management.Automation.Internal.AutomationNull.Value
单例,本身就是[psobject]
实例。- 从技术上讲,当PowerShell命令(二进制cmdlet和PowerShell脚本/函数)产生无输出时,该值由管道输出
-
获取此值的最简单方法是使用
& {}
,即执行一个空脚本块;当然,您也可以显式地使用[System.Management.Automation.Internal.AutomationNull]::Value
)
不幸的是,集合null值对于区分标量null来说是不平凡的,从PowerShell 7.2:开始
-
GitHub问题#13465建议在未来的PowerShell版本中允许通过
$var -is [AutomationNull]
检测集合null。 -
目前,有几个解决方案用于测试给定值
只有当$var
是否包含集合null;也许最简单(但不明显)的是:$var
包含集合null值时,$null -eq $var -and $var -is [psobject]
才是$true
,因为从技术上讲,只有集合null才是对象
行为差异:
-
在表达式上下文和参数绑定中,集合null隐式转换为
$null
没有区别。-
请注意,这意味着您不能将集合null作为参数传递-请参阅GitHub问题#9150中的讨论。
-
表达式上下文中的异常是支持集合作为其LHS的运算符的LHS:它们将集合null视为空集合,因此计算为空数组(
@()
),而不是$null
:- 例如,仅当
$var
是标量$null
时,$var -replace 'foo' | ForEach-Object { 'hi' }
才打印'hi'
,而不打印集合为null的,因为-replace
操作随后输出一个空数组,该数组不通过管道发送任何内容 - 请参阅GitHub第3866期
- 例如,仅当
-
-
在管道中:
-
标量
$null
通过管道发送-其行为类似于单个对象:$null | ForEach-Object { '$_ is $null? ' + ($null -eq $_) }
打印'$_ is $null? True'
; -
集合null是通过管道发送的而不是-它的行为类似于没有元素的集合;也就是说,就像
@() | ForEach-Object { 'hi' }
(发送一个空数组)一样,& {} | ForEach-Object { 'hi' }
不通过管道发送任何内容,因为没有任何内容可枚举,因此永远不会输出'hi'
。 -
奇怪的是,相比之下,在
foreach
循环语句(与ForEach-Object
cmdlet相反)中,标量$null
也不是枚举的,并且循环体从未在以下中输入(集合为null时也是如此):foreach ($i in $null) { 'hi' }
-
Measure-Object
和管道问题:
-
Measure-Object
通常忽略$null
值,可能是设计。- 这在GitHub第10905期中进行了讨论,该期建议引入
-IncludeNull
开关,以支持在选择加入的基础上考虑$null
值。(默认行为不会更改,以免破坏向后兼容性。)
- 这在GitHub第10905期中进行了讨论,该期建议引入
-
但是,您在PowerShell的管道中发现了一个关于多对象输入的完全错误,该错误涉及集合null(截至PowerShell 7.1.2),
Measure-Object
仅出现。正如您自己所指出的:-
在多对象输入中遇到第二个集合为null时,通过管道发送对象会意外停止:
- 例如,
(1, (& {}), 2, (& {}), 3, 4, 5 | Measure-Object).Count
只产生2
:只计算1
和2
(集合null本身是,而不是通过管道发送),因为第二个集合null意外停止枚举,因此剩余的对象3
、4
和5
甚至都没有发送到Measure-Object
- 例如,
-
请参阅GitHub第14920期。
-
为了补充mklement0非常详细和非常感谢的答案,我想分享我使用的解决方法:
$numbers = 3, 42, 7, 69, 13
$no1 = $numbers | Where-Object {$_ -eq 1}
$no2 = $numbers | Where-Object {$_ -eq 2}
$no3 = $numbers | Where-Object {$_ -eq 3}
不是将变量直接管道传输到ForEach-Object
,这不会产生输出…:
$no1, $no2, $no3 | ForEach-Object {$_}
>
将变量名称管道传输到ForEach-Object
,并使用Get-Variable
获得所需结果:
'no1', 'no2', 'no3' | ForEach-Object {(Get-Variable $_).Value}
> 3