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

我觉得这很不满意,但这是实际问题的一个旁白。

4

2 回答 2

4

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

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

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

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

当您ConvertFrom-Json用括号括起来时,powershell 将其解释为一个单独的管道,该管道在任何输出可以发送到之前执行和结束Where-ObjectWhere-Object因此不知道ConvertFrom-Json希望它这样对待数组。


Write-Output我们可以通过使用-NoEnumerateswitch 参数集显式调用在 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(在您的情况下)argWhere-Object


为什么这是可取的?

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

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

我不应该期望从ConvertFrom-Json现在起我将 5 个有效的 JSON 文档传递给它的 5 个对象吗?:-)

于 2019-04-08T14:51:30.897 回答
2

使用空数组作为直接管道输入,不会通过管道发送任何内容,因为数组是枚举的,并且由于没有要枚举的内容 - 因为空数组没有元素 - Where( Where-Object) 脚本块永远不会执行:

Set-StrictMode -Version Latest

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

相比之下,在 PowerShell 版本中,直到 v6.x "[]" | ConvertFrom-Json生成一个空数组作为单个输出对象,而不是枚举其(不存在的)元素,因为ConvertFrom-Json在这些版本中不枚举它输出的数组的元素;它相当于:

Set-StrictMode -Version Latest

# Empty array is sent as a single object through the pipeline.
# The Where script block is invoked once and sees $_ as that empty array.
# Since strict mode is in effect and arrays have no .name property
# an error occurs.
Write-Output -NoEnumerate @() | Where { $_.name -eq "Baz" }

ConvertFrom-Json的行为在 PowerShell 的上下文中令人惊讶- cmdlet 通常枚举多个输出 - 但在 JSON 解析的上下文中是可防御的;毕竟,如果枚举空数组,信息将会丢失ConvertFrom-Json,因为您将无法将其与空 JSON 输入( "" | ConvertFrom-Json) 区分开来。

共识是这两个用例都是合法的,并且用户应该通过切换在两种行为(枚举或不枚举)之间进行选择(有关相关讨论,请参阅此 GitHub 问题)。

因此,从 PowerShell [Core] 7.0 开始

  • 现在默认执行枚举。

  • 可以通过新开关选择加入行为。-NoEnumerate

PowerShell 6.x-中,如果需要枚举,则 -obscure- 解决方法是通过简单地将调用包含在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属性,这就是为什么 withSet-StrictMode -Version 2或更高的效果,你会得到一个The property 'count' cannot be found on this object.错误。

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

请注意,应该能够调用.Countand $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] 7.0 开始),应被视为错误,如本 GitHub 问题中所报告的(由 Jeffrey Snover 撰写,不少于)。

于 2019-04-08T17:15:44.063 回答