【问题标题】:Unexpected Behaviour with Where-ObjectWhere-Object 的意外行为
【发布时间】:2021-02-28 12:36:21
【问题描述】:

我刚刚遇到了 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

【问题讨论】:

  • $foo 不完全是$null,这可以通过检查$foo.psbase 来验证(真正的$null 不会为.psbase 提供任何信息)。这是运行时用包装对象做一些奇怪的事情。
  • 更清晰的可能是$foo -is [psobject],对于... | Where-Object {$False}$True,对于$null$False。这是一个 PSObject 隐式转换为 $null,但不是完全透明。

标签: powershell


【解决方案1】:

tl;dr

  • 并非所有明显的 $null 值都相同,正如 Jeroen Mostert 的 cmets 所示:PowerShell 有 两种 类型的 null,它们在不同情况下的行为不同 - 请参阅下一节。

  • 此外,您还看到可能令人惊讶的Measure-Object 行为和管道错误 - 请参阅底部部分。

    • 最好从您的测试命令中删除Measure-Object,并简单地在您的阵列上直接调用.Count;例如(在您的问题中创建 null 类型的最简单方法是:$foo = & {}):

      • ($foo, $null, 1).Count 产生 3
      • ($null, $foo, $null, $foo, 1).Count 产生 5
    • 如您所见,这两种类型的 null(在下面讨论)都正确地成为数组的 元素


在 PowerShell 中有 两种不同的空值

  • 有真正的 scalar null(例如对应于 C# 中的 null)。

    • 此空值包含在自动$null 变量中。
    • .NET 方法可能会返回它。 (虽然 PowerShell 代码也可能输出它,但最好避免这样做)。
  • 还有 enumerable“collection null”(也称为“AutomationNull”,基于其类名),在技术上是 System.Management.Automation.Internal.AutomationNull.Value 单例,即本身就是一个[psobject] 实例。

    • PowerShell 命令(二进制 cmdlet 和 PowerShell 脚本/函数)产生无输出时,此值在技术上由管道输出。
    • 获取该值的最简单方法是使用& {},即执行一个空的脚本块;当然,你也可以显式使用[System.Management.Automation.Internal.AutomationNull]::Value)。

不幸的是,从 PowerShell 7.2 开始,集合 null 值很难与标量 null 区分开来

  • GitHub issue #13465 提议允许通过$var -is [AutomationNull] 检测集合 null 在未来的 PowerShell 版本中

  • 目前,有几个解决方法用于测试给定值 $var 是否包含集合 null;也许最简单(但不明显)是:

    • 仅当 $var 包含集合 null 值时,$null -eq $var -and $var -is [psobject] 才是 $true,因为从技术上讲,只有集合 null 是一个对象。

行为差异

  • 表达式上下文和参数绑定中,collection null被隐式转换为$null没有区别

    • 请注意,这意味着您不能将集合 null 作为 参数传递 - 请参阅 GitHub issue #9150 中的讨论。

    • 表达式上下文中的异常是支持集合作为其LHS的运算符的LHS:它们将集合null视为空集合,因此评估为 空数组 (@()) 而不是 $null:

      • 例如,$var -replace 'foo' | ForEach-Object { 'hi' } 仅当 $var 是标量 $null 时才打印 'hi',而不是集合 null,因为 -replace 操作然后输出一个空数组,它不会通过管道发送任何内容。李>
      • GitHub issue #3866
  • 管道中

    • 标量$null 通过管道发送的 - 它的行为就像一个单独的对象$null | ForEach-Object { '$_ is $null? ' + ($null -eq $_) } 打印 '$_ is $null? True';

    • Collection null 通过管道发送 - 它的行为类似于 没有元素的集合;也就是说,就像@() | ForEach-Object { 'hi' }(发送一个空数组)一样,& {} | ForEach-Object { 'hi' } 不会通过管道发送任何内容,因为没有要枚举的内容,因此永远不会输出'hi'

    • 奇怪的是,相比之下,foreach loop statement(相对于ForEach-Object cmdlet)标量$null不是 枚举并且循环体从不输入以下(集合 null 同上):
      foreach ($i in $null) { 'hi' }


Measure-Object 和管道问题:

  • Measure-Object 通常忽略 $null,大​​概是设计

    • 这在GitHub issue #10905 中进行了讨论,它建议引入-IncludeNull 开关以支持在选择加入 的基础上考虑$null 值。 (默认行为不会改变,以免破坏向后兼容性。)
  • 但是,您在 PowerShell 的 管道 中发现了一个关于 涉及 collection nulls 的多对象输入的彻底 错误 em>(从 PowerShell 7.1.2 开始),Measure-Object表面,正如您自己所指出的:

    • 在多对象输入中遇到 second 集合 null 时,通过管道意外发送对象停止

      • 例如,(1, (& {}), 2, (& {}), 3, 4, 5 | Measure-Object).Count 仅产生 2:仅计算 12(集合 null 本身通过管道发送),因为第二个集合 null 意外停止枚举,以便剩余的对象 - 345 - 甚至不会发送到 Measure-Object
    • GitHub issue #14920

【讨论】:

    【解决方案2】:

    要添加到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 {$_}
    > 
    

    ... 将变量 names 传递给 ForEach-Object 并使用 Get-Variable 来获得所需的结果:

    'no1', 'no2', 'no3' | ForEach-Object {(Get-Variable $_).Value}
    > 3
    

    【讨论】:

    • 谢谢;一个更简单但也晦涩难懂的解决方法是:[array] $no1 + $no2 + $no3 | ForEach-Object {$_}。通过使用数组连接,可以有效地消除collection-null 值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-02
    • 2018-11-02
    • 2017-07-11
    • 2019-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多