为什么此过滤器无法接受管道中的某些$ null值?

时间:2017-01-10 09:33:40

标签: powershell null pipeline piping

上下文

考虑以下辅助函数:

Filter If-Null(
   [Parameter(ValueFromPipeline=$true)]$value,
   [Parameter(Position=0)]$default
) {
   Write-Verbose "If ($value) {$value} Else {$default}"
   if ($value) {$value} else {$default}
}

它基本上是一个null-coalescing operator实现为管道功能。它应该像这样工作:

PS> $myVar = $null
PS> $myVar | If-Null "myDefault" -Verbose
VERBOSE: If () {} Else {myDefault}
myDefault

然而,当我将$myVar设置为空数组中的第一个元素时......

PS> $myVar = @() | Select-Object -First 1

......这应该与$null ...

有效相同
PS> $myVar -eq $null
True
PS> -not $myVar
True

...然后管道不再起作用了:

PS> $myVar | If-Null "myDefault" -Verbose

根本没有输出。甚至不是冗长的印刷品。这意味着If-Null甚至没有被执行。

问题

所以好像@() | select -f 1-eq$null,是有点不同 $null,不知何故会破坏管道?

任何人都可以解释这种行为吗?我错过了什么?

其他信息

PS> (@() | select -f 1).GetType()
You cannot call a method on a null-valued expression.
At line:1 char:1
+ (@() | select -f 1).GetType()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

PS> (@() | select -f 1) | Get-Member
Get-Member : You must specify an object for the Get-Member cmdlet.
At line:1 char:23
+ (@() | select -f 1) | Get-Member
+                       ~~~~~~~~~~
    + CategoryInfo          : CloseError: (:) [Get-Member], InvalidOperationException
    + FullyQualifiedErrorId : NoObjectInGetMember,Microsoft.PowerShell.Commands.GetMemberCommand
PS> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.0.10586.117
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.10586.117
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

解决方案

Ansgar's explanation是正确的(可以在重复问题的mklement0's answer中找到更好的解释)。我只是想分享我对这个问题的解决方案。

我已修复If-Null,即使没有处理任何内容,也会返回$default

Function If-Null(
    [Parameter(ValueFromPipeline = $true)]$value, 
    [Parameter(Position = 0)]$default
) {
    Process { 
        $processedSomething = $true
        If ($value) { $value } Else { $default } 
    }

    # This makes sure the $default is returned even when the input was an empty array or of
    # type [System.Management.Automation.Internal.AutomationNull]::Value (which prevents
    # execution of the Process block).
    End { If (-not $processedSomething) { $default }}
}

此版本现在可以正确处理空管道结果:

PS> @() | select -f 1 | If-Null myDefault
myDefault

1 个答案:

答案 0 :(得分:3)

通过管道展开数组,以便分别传递每个数组元素。如果将空数组传递给管道,它实际上将展开为空,这意味着永远不会调用下游cmdlet,从而为您留下一个空变量。

您可以通过将$null@()传递到一个循环来观察此行为,该循环只回显每个输入项的字符串:

PS C:\> @() | % { 'foo' }    # no output here!
PS C:\> $null | % { 'foo' }  # output: foo
foo

根据上下文,这与带有"值"的变量不同。 $null。即使在大多数情况下,PowerShell也会自动转换"空"变量为$null的值(如检查中所示),在将变量传递到管道中时,它不会这样做。在这种情况下,您仍然无法将任何内容传递到管道中,因此永远不会调用您的过滤器。