如何在管道中处理 $null

23

我在PowerShell代码中经常遇到以下情况:我有一个返回对象集合或$null的函数或属性。如果你将结果推送到管道中,当$null是唯一的元素时,你也会处理管道中的元素。

示例:

$Project.Features | Foreach-Object { Write-Host "Feature name: $($_.Name)" }

如果没有任何功能($Project.Features返回$null),你将看到一条仅包含“Feature name:”的单行。

我看到了三种解决方法:

if ($Project.Features -ne $null)
{
  $Project.Features | Foreach-Object { Write-Host "Feature name: $($_.Name)" }
}
或者
$Project.Features | Where-Object {$_ -ne $null) | Foreach-Object { 
  Write-Host "Feature name: $($_.Name)" 
}
或者
$Project.Features | Foreach-Object { 
  if ($_ -ne $null) {
    Write-Host "Feature name: $($_.Name)" }
  }
}

实际上,我不喜欢这些方法中的任何一个,但你认为哪种方法最好?

4个回答

30

我认为没有人喜欢 "foreach ($a in $null) {}" 和 "$null | foreach-object{}" 都只迭代一次的事实。不幸的是,除了你展示的那些方式之外,没有其他方法可以做到这一点。你可以更简洁一些:

$null | ?{$_} | % { ... }

?{$_}where-object {$_ -ne $null}的缩写,因为$null被视为布尔表达式时将被视为$false

我在我的配置文件中定义了一个过滤器,如下所示:

filter Skip-Null { $_|?{ $_ } }

使用方法:

$null | skip-null | foreach { ... }
一个过滤器和一个函数一样,不同之处在于默认块是process{}而非end{}。

更新:自PowerShell 3.0起,$null不再可迭代为集合。耶!

- Oisin


6
简洁的简写方式的问题在于它会拒绝任何强制转换为“false”的内容,包括像0""@()@(0)等。我希望Skip-Null只跳过$null,尽管在此问题的上下文中结果相同,但对于可能在其他地方使用的筛选器来说,这是我预期的行为... - Joey
遗憾的是,@() |?{$false} 仍然返回 $null 而不是返回一个空列表。 - ekkis
1
$Null 应该在等式比较的左侧 (Where-Object {$Null -ne $_}),因为 $_ 可能包含一个(子)数组,其中有多个项目为 $Null,这将错误地过滤掉对象。 - iRon

12

如果你可以修改你的函数,那么就让它返回一个空的集合/数组而不是 $null:

PS> function Empty { $null }
PS> Empty | %{'hi'}
hi

PS> function Empty { @() }
PS> Empty | %{'hi'}

否则,按照Oisin的建议执行,尽管我建议稍作修改:

filter Skip-Null { $_|?{ $_ -ne $null } } 
否则这也会过滤0$false更新于2012年4月30日:此问题已在PowerShell v3中得到解决。V3不会迭代标量$null值。

为什么我不能只接受一个答案!它们都很好,谢谢你们!我给了Oisin“答案”,Keith已经有最多的分数:-) - Serge van den Oever
当你在17个月后编辑了这篇文章时,我碰巧正在查看它,这种情况的概率有多大?是否有一个cmdlet可以计算这个? - Lance U. Matthews
@BACON 这绝对不是巧合。我通过我的SO收件箱注意到你在我的一个答案下发表了评论。 :-) 只是想指出,在V3中这不是一个问题。 - Keith Hill
很高兴这个问题已经解决了。叹气 - Dave Markle
5
在v3或v4版本中,这个问题并没有得到解决。他们只在foreach中修复了它,而没有针对管道操作进行修复。请参考http://blogs.msdn.com/b/powershell/archive/2012/06/14/new-v3-language-features.aspx。 - JJJ

2
另一种可能性:
$objects | Foreach-Object -Begin{If($_ -eq $null){continue}} -Process {do your stuff here}

More info in about_Continue


2

给Keith的回答补充一下:

个人认为,应该返回。这是有道理的:

PS> function Empty { if ('a' -eq 'b') { 'equal' } }
PS> Empty | % { write-host result is $_ }

但是,如果你将Empty的结果分配给变量,那么现在你就会遇到问题:

PS> $v = Empty
PS> $v | % { write-host result is $_ }

有一个小技巧可以让它工作。只需像这样将Empty的结果包装为数组:

PS> $v = @(Empty)
PS> $v | % { write-host result is $_ }
PS> $v.GetType()
IsPublic IsSerial Name      BaseType
-------- -------- ----      --------
True     True     Object[]  System.Array
PS> $v.Length
0

1
这是我今天使用的方法,如果我不控制被调用命令的定义。否则,如果函数通常返回多个项,以便我可以对其进行foreach循环,则返回一个空数组 - 太多次忘记在@()中包装了。 :-) - Keith Hill
@Keith,我觉得你写了一篇关于这个棘手行为的很棒的文章。你可以在这里链接它,其他人应该读一读,绝对值得!;) - stej

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接