为什么将 null 管道连接到集合变量会导致不同的结果?

Why is piping null to set-variable causing different outcomes?

提问人:Swifting 提问时间:7/7/2023 最后编辑:Mathias R. JessenSwifting 更新时间:8/3/2023 访问量:86

问:

使用 set-variable 时,我在向其输送 null 值时看到不同的行为。

在第一个设置中,我们创建一个变量,并用 null 覆盖它,到目前为止一切顺利。

# Setup 1: Passing a null value
$test1 = "test1" 
$null | set-variable test1
Write-Host "It is: $test1" # "It is: "

# $null.gettype()
# You cannot call a method on a null-valued expression.

然而,在第二个设置中,我相信,我们也用 null 覆盖它,但是变量没有改变。

# Setup 2: Passing a null value by deducting a list
$testList = New-Object -TypeName 'System.Collections.ArrayList'
$testList.Add("test")

$test2 = "test2" 
$testList | Select-Object -SkipLast 1 | set-variable test2
Write-Host "It is: $test2" # "It is: test2"

# $($testList | Select-Object -SkipLast 1).gettype()
# You cannot call a method on a null-valued expression.

为什么第一个设置覆盖变量,而第二个设置没有?

Powershell null

评论


答:

3赞 Santiago Squarzon 7/7/2023 #1

这几乎是 和 之间的区别。请参阅您想知道的有关$null的所有信息,很棒的深度文章。AutomationNull$null

$test2 = 'test2'
& { } | Set-Variable test2 # AutomationNull, Process block does not run!
$test2 # Outputs: test2
$null | Set-Variable test2 # $null is not AutomationNull, it does get piped
$test2 # Outputs: $null

您可以使用此示例来更好地了解正在发生的事情。正如你所看到的,我们函数的进程在通过管道时永远不会被调用。AutomationNull

function Set-Var {
    param(
        [Parameter(ValueFromPipeline)]
        [object] $Value,

        [Parameter(Mandatory, Position = 0)]
        [string] $Name
    )

    process {
        $PSCmdlet.WriteVerbose("Setting value [$Value] to PSVariable [$Name]")
        $psvar = $PSCmdlet.SessionState.PSVariable.Get($Name)
        $psvar.Value = $Value
        $PSCmdlet.SessionState.PSVariable.Set($psvar)
    }
}

$test = 'hello'
$null | Set-Var test -Verbose
# VERBOSE: Setting value [] to PSVariable [test]
$test # Outputs: $null
'foo' | Set-Var test -Verbose
# VERBOSE: Setting value [foo] to PSVariable [test]
$test # Outputs: foo
& { } | Set-Var test -Verbose
# No verbose output, meaning the Process Block was never invoked!
$test # Outputs: foo
2赞 mklement0 7/7/2023 #2

让我在 Santiago Squarzon 的有用回答中添加一些背景信息

事实上,PowerShell 有两种类型的 null 值

  • 标量 null,可以说类似于 C# 和其他语言,例如,它是自动$null变量的值。null

    • 您可以将其视为缺失对象的占位符
    • $null按原样通过管道发送
    • 奇怪的是,用作 foreach 语句的输入并非如此:不产生任何输出,这意味着永远不会进入循环。$nullforeach ($val in $null) { 'here!' }
    • $null不幸的是,尝试访问不存在的变量会评估到什么[1],但它的显式使用(除了在测试中)在 PowerShell 中很少见。$null -eq $value
  • 可以这么说,可枚举的 null 是特定于 PowerShell 的概念:

    • 您可以将其视为一个不枚举任何内容的枚举对象

    • 可悲的是,在撰写本文时,这个特殊值还没有正式名称,人们认为它通常被称为“自动化空”,有时被称为“空空”。
      此外,它没有自动变量,即标量没有自动变量的模拟。
      $null

    • 鉴于可枚举的 null 在技术上是不产生输出的命令的“返回值”,获取它的最简单方法是执行,即执行一个空的脚本块。具体而言,可枚举的 null 是 [System.Management.Automation.Internal.AutomationNull]::Value 单一实例(文档链接未提供任何有意义的信息)。$nullEnum = & {}

    • 管道中(例如,)...$value | Write-Output

      • ...可枚举的 null 的行为类似于没有元素的集合,这意味着,给定集合在管道中枚举(逐个发送其元素),则不会通过管道发送任何数据,这(通常)是空操作:后续管道段中的命令不会接收任何要操作的输入

      • 请注意,自动枚举逻辑也适用于 switch 语句和比较运算符的 LHS(充当具有可枚举 LHS 值的筛选器);提供可枚举的 null 作为输入以有效地跳过语句(例如);可枚举的 null 是否被视为可枚举的比较操作的 LHS 取决于特定的运算符; 将其视为可枚举 ( -> 空数组),不 (switchswitch (& {}) { default { 'never get here' } }-match(& {}) -match ''-eq(& {}) -eq '' -> $false)

    • 表达式中(例如,)...$null -eq $value

      • ...可枚举的 null 的行为类似于 $null
      • 此外,当将可枚举的 null 作为参数(参数值)传递给命令时,总是会发生转换为 $null - 请参阅 GitHub 问题 #9150

上面解释了 (variable 设置为通过管道接收的值) 和 (变量从不创建或更新,因为不接收任何输入;对 1 元素输入集合的调用不产生任何输出,因此发出可枚举的 null) 之间的区别。$null | Set-Variable test1test$null& {} | Set-Variable test2test2Set-VariableSelect-Object -SkipLast 1

另请参阅:

  • 这个答案还触及了 vs. 的历史方面。 处理,涵盖从 v2 过渡到 v3+ 时发生的行为变化。$null[System.Management.Automation.Null]

  • 这篇关于 GitHub 问题 #9150 的评论总结了如果不考虑向后兼容性,如何以一致的方式处理 null 二分法。


鉴于基本的行为差异,重要的是:

  • 正确记录这两个 null 类型,并为可枚举的 null 指定一个正式名称

  • 以便以编程方式区分这两种类型。

在撰写本文时 (PowerShell 7.3.6),这两个要求均未得到满足。

检测可枚举的 null 目前既繁琐又晦涩难懂:

$value = & {} # Obtain the enumerable null.

# Without the `-and $value.psobject` part, you couldn't distinguish 
# $null from the enumerable null.
$isNullEnumerable = 
  $null -eq $value -and $value.psobject

虽然同时返回 true 和可枚举的 null,但仅返回可枚举 null 的值(当强制为布尔值时,其计算结果为 )。原因是,与 不同,可枚举的 null 在技术上是一个对象,因此返回内部 psobject 属性的值。$null -eq $value$true$null$value.psobject$true$null

由于 GitHub 问题 #13465 中的讨论,以下改进已获得批准,但尚未实施

# NOT YET IMPLEMENTED as of PowerShell 7.3.6
$isNullEnumerable = 
  $value -is [System.Management.Automation.Null]

也就是说,您将能够将 type(-inheritance) / interface test 运算符与尚未引入的类型一起使用,这将取代 “pubternal” 类型。[2]-is[System.Management.Automation.Null][System.Management.Automation.Internal.AutomationNull]

不幸的是,还决定引入类型加速器 - 这将简化测试 - 被决定$value -is [AutomationNull]


[1] 这个默认值很不幸,因为它意味着$noSuchVariable | ... 通过管道发送$null。如果默认值为“可枚举 null”(“Automation null”, [System.Management.Automation.Null]::Value),则不会发送任何数据,就像 foreach 语句已经(但令人惊讶)处理 $null 的方式一样。将可枚举的 null 作为默认值时,管道和 foreach 行为之间不需要不对称,并且可以始终如一地保留$null

[2] 此更改涉及的不仅仅是一个新类型名称:新类型的单例 ([System.Management.Automation.Null]::Value),即实际可枚举的 null,将属于同一类型,而当前单例 ([System.Management.Automation.Internal.AutomationNull]::Value) 属于不同的类型,即只是 [psobject] - 有关详细信息,请参阅此 GitHub 评论