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

时间:2019-04-08 14:15:49

标签: arrays powershell enumeration powershell-core convertfrom-json

如果我在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

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

2 个答案:

答案 0 :(得分:4)

  

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

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

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

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

ConvertFrom-Json括在括号中时,powershell会将其解释为执行并在之前结束的单独管道。任何输出都可以发送到{{1} },因此Where-Object不知道Where-Object希望它像这样对待数组。


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

-NoEnumerate

# 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,这反过来导致运行时 not 枚举与下游cmdlet进行参数绑定期间的Cmdlet.WriteObject(arg, false)值(在您的情况下) arg


  

为什么这是可取的?

在解析JSON的特定上下文中,此行为确实可能是理想的:

Where-Object

既然我已经向其传递了5个有效的JSON文档,那么我不应该期望$data = '[]', '[]', '[]', '[]' |ConvertFrom-Json 中有5个对象吗? :-)

答案 1 :(得分: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 by设计不会枚举其输出的数组的元素;等效于:

# 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输入({ {1}}。

这种紧张关系在this GitHub issue中进行了讨论。

共识是,两个用例都是合法的,并且用户应通过开关在两个行为之间(枚举与否)选择选择 ;从PowerShell Core 6.2.0开始,尚未做出正式决定,但是如果要保留向后兼容性,则必须是 opt-in 的枚举行为(例如,{{1 }}。

如果需要枚举,则目前的-晦涩-解决方法是通过将"" | ConvertFrom-Json调用简单地包含在-Enumerate 中来强制枚举(将其转换为 expression ,并且表达式在管道中使用时始终会枚举命令的输出):

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 调用之后,您的总体命令将返回“ nothing”:松散地说,ConvertFrom-Json,但更准确地说,是一个“数组值null”,它是(...)单例,表示命令没有输出。 (在大多数情况下,后者与$null的处理方式相同,但在用作管道输入时尤其明显。)

[System.Management.Automation.Internal.AutomationNull]::Value没有$null属性,这就是为什么[System.Management.Automation.Internal.AutomationNull]::Value或更高版本会导致.Count错误的原因。

通过将整个管道包装在数组子表达式运算符Set-StrictMode -Version 2中,可以确保将输出视为 array ,并与[array-valued null输出一起创建一个空数组-确实具有The property 'count' cannot be found on this object.属性。

请注意,假设PowerShell添加了一个{{},您应该@(...).Count 上呼叫.Count 1}}属性(每个)(如果尚不存在)(包括标量),以表扬一下,以统一对集合和标量的处理。

也就是说,将$null设置为[System.Management.Automation.Internal.AutomationNull]::Value(默认设置)或.Count时,以下起作用,并且-明智地-返回{{1} }:

Set-StrictMode

上述当前 不适用于-Off或更高版本(自PowerShell Core 6.2.0起),应被视为 bug ,如this GitHub issue中所述(杰弗里·斯诺弗(Jeffrey Snover),不少)。