PowerShell:ForEach-Object与InputObject的作用是什么?

4
ForEach-object文档中提到:“当您使用InputObject 参数与ForEach-Object一起使用时,而不是将命令结果流式传输到ForEach-Object InputObject 的值将被视为单个对象。”这种行为可以直接观察到:
PS C:\WINDOWS\system32> ForEach-Object -InputObject @(1, 2, 3) {write-host $_}
1 2 3

这似乎很奇怪。如果没有要在上面进行“for”的“each”,那么“ForEach”的意义是什么?是否真的没有办法让ForEach-object直接作用于数组的各个元素而不需要管道呢?如果不行,那么使用带有InputObjectForEach-Object完全是无用的。我是否对此有所误解?


你还应该查看ForEach-Object文档的注释部分中建议的about_Foreach - notjustme
1
命令通常使用进程块来循环遍历所有的输入对象项,这需要使用管道。他们将不得不添加额外的foreach循环来循环遍历命令行上的所有输入对象项。我不知道这里的源代码以及为什么没有这样做。 - js2010
虽然我没有任何依据,但我认为这是生成 cmdlets 的公共脚手架的副作用。我认为这是唯一的原因。你是否在 PoSh GitHub 网站上开了一个问题? - Lee_Dailey
类似的问题已经被问过了,关于sort-object:https://dev59.com/questions/XpPfa4cB1Zd3GeqPJOSx - js2010
我不确定这是否应该被视为需要修复的问题,因为它是文档化行为,并且它的工作方式很有道理。 - codewario
2
@Lee_Dailey:这里有一个 GitHub 问题链接:https://github.com/PowerShell/PowerShell/issues/4242 - mklement0
2个回答

4
在处理集合的任何命令或类似于ForEach-Object的命令中,直接使用-InputObject作为参数是没有意义的,因为该命令被设计为处理集合,需要将其展开并逐个处理元素。但我也不会称这个参数为“无用”,因为它仍然需要被定义,以便可以设置允许通过管道输入。

为什么是这样呢?

-InputObject是约定俗成的一个通用参数名称,应该被视为管道输入。它是一个具有[Parameter(ValueFromPipeline = $true)]设置的参数,因此更适合从管道获取输入而不是作为直接参数传递。将其作为直接参数传递的主要缺点是,不能保证集合被展开,可能会出现一些未预期的行为。来自上面链接到的about_pipelines页面的说明:

当您将多个对象通过管道传递给命令时,PowerShell会逐个向命令发送对象。当您使用命令参数时,对象会被发送为单个数组对象。这个微小的差别具有重大的影响。

为了用不同的话解释上面的引用,通过管道传递集合(例如,一个数组或列表)将自动展开该集合并将其逐个传递到管道中的下一个命令中。该命令不会展开-InputObject本身,数据是逐个元素传递的。这就是为什么将集合直接传递给-InputObject参数可能会出现问题的原因 - 因为该命令可能没有设计来展开集合,它期望每个集合元素都以逐个分开的方式交给它。

请考虑以下示例:

# Array of hashes with a common key
$myHash = @{name = 'Alex'}, @{name='Bob'}, @{name = 'Sarah'}

# This works as intended
$myHash | Where-Object { $_.name -match 'alex' }

以上代码按预期输出以下内容:
Name                           Value
----                           -----
name                           Alex

但是如果你直接像这样将哈希作为InputArgument传递:
Where-Object -InputObject $myHash { $_.name -match 'alex' }

它返回整个集合,因为通过管道传递时-InputObject从未展开,但在此上下文中,$_.name -match 'alex'仍然返回true。换句话说,当将集合直接作为参数提供给-InputObject时,它被视为单个对象而不是针对集合中的每个元素执行每次操作。这也可以在检查该数据集的错误条件时呈现出预期工作的外观
Where-Object -InputObject $myHash { $_.name -match 'frodo' }

在这种情况下,返回的是空值,因为即使在这种情况下,frodo也不是哈希集合中任何一个name键的值。


简而言之,如果某个东西期望输入作为管道输入传递,通常情况下,尤其是传递集合时,以这种方式进行操作更加安全。然而,如果您正在使用非集合项目,则使用-InputObject参数直接传入也可能没有问题。


我明白你的意思,但请问一下:(a) 对于 ForEach-object 命令,InputObject 参数是否无用?还是 (b) 在某些情况下它有用途? - NewSites
如果您正在使用非集合对象,则使用“-InputObject”参数应该没有任何问题,如果您更喜欢这样做的话。 - codewario
我为什么要在非集合对象上使用 ForEach-Object - NewSites
我更新了答案,首先专门处理了“ForEach-Object”,然后继续回答其他问题。 - codewario
1
谢谢。我已经接受了答案,也感谢你提供了详细的解释。根据你更新的内容,我的理解是这个参数对于该命令的操作是必要的,但从程序员的角度来看,它没有任何作用,也没有理由使用它。 - NewSites
显示剩余3条评论

3

Bender the Greatest的有用答案很好地解释了当前的行为。

对于绝大多数cmdlet而言,直接使用-InputObject参数是无意义的,该参数应被视为一种实现细节,其唯一目的是方便管道输入。

然而,也有例外情况。例如Get-Member cmdlet中,直接使用-InputObject可以让您检查集合本身的类型,而通过管道提供该集合则会报告关于其元素的类型的信息。

考虑到当前的工作方式,非常不幸的是,在大多数cmdlet的帮助主题中,-InputObject特性与“真正的”参数并列,并且没有足够清晰的表述(截至本文撰写时):说明应明确传达“不要直接使用此参数,请改用管道”。

此GitHub问题提供了一个分类概述,介绍哪些cmdlet如何处理直接-InputObject参数的信息


退后一步:

虽然从技术上讲,这是一种破坏性变更,但默认情况下,-InputObject参数(或任何管道绑定参数)应该能够接受并枚举集合,即使它们是通过直接参数传递而不是通过管道传递,以一种对实现命令透明的方式。

这将使直接参数输入与管道输入平等,并具有前者在处理已在内存中的集合时更快的优点。


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