为什么 PowerShell 将“Where”的谓词应用于空列表
Why is PowerShell applying the predicate of a `Where` to an empty list
如果我在 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
然后 "works".
引入括号之前有什么问题?
解释 "works" 周围的引号 - 设置严格模式 Set-StrictMode -Version Latest
表示我在 $null
对象上调用 .Count
。这是通过包装 @()
:
来解决的
$z = @(("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" })
Write-Host $z.Count
我觉得这很不满意,但这是实际问题的旁白。
Why is PowerShell applying the predicate of a Where
to an empty list?
因为 ConvertFrom-Json
告诉 Where-Object
不要尝试枚举它的输出。
因此,PowerShell 会尝试访问空数组本身的 name
属性,就像我们要执行的操作一样:
$emptyArray = New-Object object[] 0
$emptyArray.name
当您将 ConvertFrom-Json
括在括号中时,powershell 将其解释为 单独的 管道,该管道在 之前执行并结束任何输出可以发送到 Where-Object
,因此 Where-Object
不知道 ConvertFrom-Json
希望它这样处理数组。
我们可以通过使用 -NoEnumerate
开关参数集显式调用 Write-Output
在 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)
,这反过来导致运行时 不 在针对下游的参数绑定期间枚举 arg
值cmdlet(在您的情况下 Where-Object
)
Why would this be desireable?
在解析 JSON 的特定上下文中,这种行为可能确实是可取的:
$data = '[]', '[]', '[]', '[]' |ConvertFrom-Json
既然我向它传递了 5 个有效的 JSON 文档,我难道不应该期望 ConvertFrom-Json
恰好有 5 个对象吗? :-)
使用空数组作为直接管道输入,没有通过管道发送,因为数组是enumerated,因为没有什么可枚举的 - 因为空数组没有元素 - 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" }
相比之下,在高达 v6.x"[]" | ConvertFrom-Json
的 PowerShell 版本中生成空数组作为单个输出对象 而不是枚举其(不存在的)元素,因为 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 通常 enumerate 多个输出 - 但 在 JSON 解析 的上下文中是可防御的;毕竟,如果 ConvertFrom-Json
枚举空数组,信息将 丢失 ,因为你无法将其与 empty [=162] 区分开来=] 输入 ("" | ConvertFrom-Json
).
共识是两个用例都是合法的,用户应该在两种行为之间选择——枚举与否——通过开关(有关相关讨论,请参阅this GitHub issue)。
因此,从 PowerShell [Core] 7.0 开始:
枚举是现在默认执行。
可以通过新的 -NoEnumerate
开关选择加入 旧 行为。
在 PowerShell 6.x- 中,如果需要枚举,模糊的解决方法是 强制枚举 通过简单地将 ConvertFrom-Json
调用包含在 (...)
中,grouping operator(将其转换为 表达式 ,以及表达式在管道中使用时总是枚举命令的输出):
# (...) 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
调用包含在 (...)
中,您的总体命令 returns “无”:松散地说,$null
,但更准确地说,一个“数组-valued 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.
错误。
通过将整个管道包装在 @(...)
和 array subexpression operator 中,您可以确保将输出处理为 数组 ,其中,数组值空输出,创建一个空数组 - 有一个 .Count
属性.
请注意,您应该能够在$null
和[System.Management.Automation.Internal.AutomationNull]::Value
上调用.Count
,考虑到 PowerShell 将 .Count
属性 添加到 每个 对象(如果尚未存在) - 包括标量,这是值得称赞的统一处理集合和标量。
也就是说,将 Set-StrictMode
设置为 -Off
(默认值)或 -Version 1
,以下 确实 有效并且 - 明智地 - returns 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开始),应该被认为是bug,如 this GitHub issue 所报告(Jeffrey Snover,不少)。
如果我在 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
然后 "works".
引入括号之前有什么问题?
解释 "works" 周围的引号 - 设置严格模式 Set-StrictMode -Version Latest
表示我在 $null
对象上调用 .Count
。这是通过包装 @()
:
$z = @(("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" })
Write-Host $z.Count
我觉得这很不满意,但这是实际问题的旁白。
Why is PowerShell applying the predicate of a
Where
to an empty list?
因为 ConvertFrom-Json
告诉 Where-Object
不要尝试枚举它的输出。
因此,PowerShell 会尝试访问空数组本身的 name
属性,就像我们要执行的操作一样:
$emptyArray = New-Object object[] 0
$emptyArray.name
当您将 ConvertFrom-Json
括在括号中时,powershell 将其解释为 单独的 管道,该管道在 之前执行并结束任何输出可以发送到 Where-Object
,因此 Where-Object
不知道 ConvertFrom-Json
希望它这样处理数组。
我们可以通过使用 -NoEnumerate
开关参数集显式调用 Write-Output
在 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)
,这反过来导致运行时 不 在针对下游的参数绑定期间枚举 arg
值cmdlet(在您的情况下 Where-Object
)
Why would this be desireable?
在解析 JSON 的特定上下文中,这种行为可能确实是可取的:
$data = '[]', '[]', '[]', '[]' |ConvertFrom-Json
既然我向它传递了 5 个有效的 JSON 文档,我难道不应该期望 ConvertFrom-Json
恰好有 5 个对象吗? :-)
使用空数组作为直接管道输入,没有通过管道发送,因为数组是enumerated,因为没有什么可枚举的 - 因为空数组没有元素 - 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" }
相比之下,在高达 v6.x"[]" | ConvertFrom-Json
的 PowerShell 版本中生成空数组作为单个输出对象 而不是枚举其(不存在的)元素,因为 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 通常 enumerate 多个输出 - 但 在 JSON 解析 的上下文中是可防御的;毕竟,如果 ConvertFrom-Json
枚举空数组,信息将 丢失 ,因为你无法将其与 empty [=162] 区分开来=] 输入 ("" | ConvertFrom-Json
).
共识是两个用例都是合法的,用户应该在两种行为之间选择——枚举与否——通过开关(有关相关讨论,请参阅this GitHub issue)。
因此,从 PowerShell [Core] 7.0 开始:
枚举是现在默认执行。
可以通过新的
-NoEnumerate
开关选择加入 旧 行为。
在 PowerShell 6.x- 中,如果需要枚举,模糊的解决方法是 强制枚举 通过简单地将 ConvertFrom-Json
调用包含在 (...)
中,grouping operator(将其转换为 表达式 ,以及表达式在管道中使用时总是枚举命令的输出):
# (...) 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
调用包含在 (...)
中,您的总体命令 returns “无”:松散地说,$null
,但更准确地说,一个“数组-valued 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.
错误。
通过将整个管道包装在 @(...)
和 array subexpression operator 中,您可以确保将输出处理为 数组 ,其中,数组值空输出,创建一个空数组 - 有一个 .Count
属性.
请注意,您应该能够在$null
和[System.Management.Automation.Internal.AutomationNull]::Value
上调用.Count
,考虑到 PowerShell 将 .Count
属性 添加到 每个 对象(如果尚未存在) - 包括标量,这是值得称赞的统一处理集合和标量。
也就是说,将 Set-StrictMode
设置为 -Off
(默认值)或 -Version 1
,以下 确实 有效并且 - 明智地 - returns 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开始),应该被认为是bug,如 this GitHub issue 所报告(Jeffrey Snover,不少)。