提问人:Swifting 提问时间:7/7/2023 最后编辑:Mathias R. JessenSwifting 更新时间:8/3/2023 访问量:86
为什么将 null 管道连接到集合变量会导致不同的结果?
Why is piping null to set-variable causing different outcomes?
问:
使用 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.
为什么第一个设置覆盖变量,而第二个设置没有?
答:
这几乎是 和 之间的区别。请参阅您想知道的有关$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
让我在 Santiago Squarzon 的有用回答中添加一些背景信息:
事实上,PowerShell 有两种类型的 null 值:
标量 null,可以说类似于 C# 和其他语言,例如,它是自动
$null
变量的值。null
- 您可以将其视为缺失对象的占位符。
$null
按原样通过管道发送。- 奇怪的是,用作
foreach
语句的输入并非如此:不产生任何输出,这意味着永远不会进入循环。$null
foreach ($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 取决于特定的运算符; 将其视为可枚举 ( -> 空数组),不 (switch
switch (& {}) { default { 'never get here' } }
-match
(& {}) -match ''
-eq
(& {}) -eq ''
->$false
)
在表达式中(例如,)...
$null -eq $value
- ...可枚举的 null 的行为类似于
$null
- 此外,当将可枚举的 null 作为参数(参数值)传递给命令时,总是会发生转换为
$null
- 请参阅 GitHub 问题 #9150
- ...可枚举的 null 的行为类似于
上面解释了 (variable 设置为通过管道接收的值) 和 (变量从不创建或更新,因为不接收任何输入;对 1 元素输入集合的调用不产生任何输出,因此发出可枚举的 null) 之间的区别。$null | Set-Variable test1
test
$null
& {} | Set-Variable test2
test2
Set-Variable
Select-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 评论。
评论