为什么PowerShell将`Where`的谓词应用于空列表

问题描述 投票:2回答:2

如果我在PowerShell中运行它,我希望看到输出0(零):

Set-StrictMode -Version Latest

$x = "[]" | ConvertFrom-Json | Where { $_.name -eq "Baz" }
Write-Host $x.Count

相反,我收到此错误:

The property 'name' cannot be found on this object. Verify that the     property exists and can be set.
At line:1 char:44
+     $x = "[]" | ConvertFrom-Json | Where { $_.name -eq "Baz" }
+                                            ~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException

如果我在"[]" | ConvertFrom-Json周围放置括号,它就变成了:

$y = ("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" }
Write-Host $y.Count

然后它“有效”。

在介绍括号之前有什么问题?

要解释“作品”周围的引号 - 设置严格模式Set-StrictMode -Version Latest表示我在.Count对象上调用$null。这是通过包装在@()解决的:

$z = @(("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" })
Write-Host $z.Count

我觉得这很不满意,但这是实际问题的一小部分。

arrays powershell enumeration powershell-core convertfrom-json
2个回答
4
投票

为什么PowerShell将Where的谓词应用于空列表?

因为ConvertFrom-Json告诉Where-Object不要试图列举其输出。

因此,PowerShell尝试访问空数组本身的name属性,就像我们要做的那样:

$emptyArray = New-Object object[] 0
$emptyArray.name

当你将ConvertFrom-Json括在括号中时,powershell会将它解释为一个单独的管道,在任何输出发送到Where-Object之前执行并结束,因此Where-Object可能不知道ConvertFrom-Json希望它像这样处理数组。


我们可以通过使用Write-Output开关参数集显式调用-NoEnumerate来在powershell中重新创建此行为:

# create a function that outputs an empty array with -NoEnumerate
function Convert-Stuff 
{
  Write-Output @() -NoEnumerate
}

# Invoke with `Where-Object` as the downstream cmdlet in its pipeline
Convert-Stuff | Where-Object {
  # this fails
  $_.nonexistingproperty = 'fail'
}

# Invoke in separate pipeline, pass result to `Where-Object` subsequently
$stuff = Convert-Stuff
$stuff | Where-Object { 
  # nothing happens
  $_.nonexistingproperty = 'meh'
}

Write-Output -NoEnumerate内部调用Cmdlet.WriteObject(arg, false),这反过来导致运行时在参数绑定下游cmdlet期间不枚举arg值(在您的情况下为Where-Object


为什么这是可取的?

在解析JSON的特定上下文中,这种行为可能确实是可取的:

$data = '[]', '[]', '[]', '[]' |ConvertFrom-Json

我现在不应该期待来自ConvertFrom-Json的5个对象,因为我传递了5个有效的JSON文档吗? :-)


2
投票

使用空数组作为直接管道输入,没有任何内容通过管道发送,因为数组是枚举的,因为没有什么可枚举 - 空数组没有元素 - 从不执行Where脚本块:

# The empty array is enumerated, and since there's nothing to enumerate,
# the Where[-Object] script block is never invoked.
@() | Where { $_.name -eq "Baz" } 

相比之下,"[]" | ConvertFrom-Json生成一个空数组作为单个输出对象而不是枚举其(不存在)元素,因为ConvertFrom-Json的设计不会枚举它输出的数组的元素;它相当于:

# Empty array is sent as a single object through the pipeline.
# The Where script block is invoked once and sees $_ as that empty array.
Write-Output -NoEnumerate @() | Where { $_.name -eq "Baz" }

ConvertFrom-Json的行为在PowerShell的上下文中是令人惊讶的 - cmdlet通常枚举多个输出 - 但在JSON解析的上下文中是有意义的;毕竟,如果ConvertFrom-Json枚举空数组,信息就会丢失,因为你不能将它与空JSON输入("" | ConvertFrom-Json)区分开来。

这种紧张关系在this GitHub issue中讨论过。

共识是两个用例都是合法的,用户应该通过交换机在两种行为之间做出选择 - 枚举与否。从PowerShell Core 6.2.0开始,尚未做出正式决定,但如果要保留向后兼容性,则必须是选择加入的枚举行为(例如,-Enumerate)。

如果需要枚举,现在的 - 模糊 - 解决方法是通过简单地将ConvertFrom-Json调用括在(...)(将其转换为表达式,并且在管道中使用时表达式始终枚举命令的输出)来强制枚举:

# (...) around the ConvertFrom-Json call forces enumeration of its output.
# The empty array has nothing to enumerate, so the Where script block is never invoked.
("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" }

至于你试过的:你试图访问.Count属性和你使用@(...)

$y = ("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" }
$y.Count # Fails with Set-StrictMode -Version 2 or higher

使用ConvertFrom-Json中的(...)调用,你的整体命令返回“无”:松散地说,$null,但更准确地说,是一个“数组值null”,这是[System.Management.Automation.Internal.AutomationNull]::Value单例,表示命令输出不存在。 (在大多数情况下,后者被视为与$null相同,但特别是当用作管道输入时。)

[System.Management.Automation.Internal.AutomationNull]::Value没有.Count属性,这就是为什么Set-StrictMode -Version 2或更高的效果,你会得到一个The property 'count' cannot be found on this object.错误。

通过将整个管道包装在@(...)(数组子表达式运算符)中,可以确保将输出处理为数组,使用[数组值null输出创建一个空数组 - 它具有.Count属性。

请注意,您应该能够在.Count$null上调用[System.Management.Automation.Internal.AutomationNull]::Value,因为PowerShell将.Count属性添加到每个对象(如果尚未存在) - 包括标量,以统一处理集合和标量的值得赞扬的努力。

也就是说,将Set-StrictMode设置为-Off(默认值)或-Version 1,以下确实有效,并且 - 明智地 - 返回0

# With Set-StrictMode set to -Off (the default) or -Version 1:

# $null sensibly has a count of 0.
PS> $null.Count
0

# So does the "array-valued null", [System.Management.Automation.Internal.AutomationNull]::Value 
# `. {}` is a simple way to produce it.
PS> (. {}).Count # `. {}` outputs 
0

上面目前不适用于Set-StrictMode -Version 2或更高版本(从PowerShell Core 6.2.0开始),应该被认为是一个错误,正如this GitHub issue报道的那样(Jeffrey Snover,不会少)。

© www.soinside.com 2019 - 2024. All rights reserved.