Where对象的意外行为



我刚刚遇到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代码也可能输出它,但最好避免这样做)
  • 还有可枚举";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-Objectcmdlet相反)中,标量$null也不是枚举的,并且循环体从未在以下中输入(集合为null时也是如此):
      foreach ($i in $null) { 'hi' }


Measure-Object和管道问题:

  • Measure-Object通常忽略$null,可能是设计

    • 这在GitHub第10905期中进行了讨论,该期建议引入-IncludeNull开关,以支持在选择加入的基础上考虑$null值。(默认行为不会更改,以免破坏向后兼容性。)
  • 但是,您在PowerShell的管道中发现了一个关于多对象输入的完全错误,该错误涉及集合null(截至PowerShell 7.1.2),Measure-Object出现。正如您自己所指出的:

    • 在多对象输入中遇到第二个集合为null时,通过管道发送对象会意外停止:

      • 例如,(1, (& {}), 2, (& {}), 3, 4, 5 | Measure-Object).Count只产生2:只计算12(集合null本身是,而不是通过管道发送),因为第二个集合null意外停止枚举,因此剩余的对象345甚至都没有发送到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

相关内容

最新更新