PowerShell中的ForEach和ForEach-Object之间的区别

36

ForEachForEach-Object 有什么区别吗?

我有一小段代码像这样,可以正常工作

$txt = Get-Content 'C:\temp\000.txt'
$result = foreach ($line in $txt) {$line.replace(".ini","")}
$result | out-file 'c:\temp\001.txt'

但如果我使用 'ForEach-Object',就会出现错误....

$txt = Get-Content 'C:\temp\000.txt'
$result = foreach-object ($line in $txt) {$line.replace(".ini","")}
$result | out-file 'c:\temp\001.txt'

为什么要使用 ForEach-Object 并如何输出循环结果?


1
https://social.technet.microsoft.com/Forums/en-US/e8da8249-ea91-4772-ae85-582a4b37425b/powershell-foreachobject-vs-foreach - dugas
4个回答

34

它们是用于不同目的的不同命令。ForEach-Object cmdlet 用于管道,您可以使用 $PSItem 或 $_ 引用当前对象,以运行 {scriptblock},如下所示:

1..5 | ForEach-Object {$_}

>1
>2
>3
>4
>5

现在,你也可以在一行的开头使用一个非常相似的关键字,即ForEach。在这种情况下,你可以运行一个{scriptblock},在其中定义变量名,例如:

ForEach ($number in 1..5){$number}
>1
>2
>3
>4
>5

核心区别在于命令的使用位置,其中一个用于管道中,而另一个则开始其自己的管道。在生产脚本中,我建议使用ForEach关键字而不是cmdlet。


关键词:ForEach-Object 在管道中使用。不错。 - Root Loop
4
ForEach也可以进行管道传输,因为它也是ForEach-Object的别名。(但反过来不成立 - ForEach-Object不是关键字,不能用于循环。) - mwfearnley

31

foreachForEach-Object的别名,但它似乎也是一个关键字(这很令人困惑)。

您正在使用的语法是foreach ($<item> in $<collection\>){<statement list>},具体信息请参见help about_foreach

作为ForEach-Object别名的foreach,请参见help ForEach-Object

关键字foreach在给定的()部分中迭代每个$<item>,属于$<collection>的一部分。

别名foreach / 函数ForEach-Object对其接收到的集合中的每个项进行迭代。


如果我将代码更改为 ForEach-Object,我会得到错误 表达式或语句中的意外标记'in'。 表达式中缺少闭合')'。 `表达式或语句中的意外标记')'。....... - Root Loop
1
没错。因为 ForEach-Object 不使用那种语法。看看我列出的帮助文档。 - Etan Reisner
对于一个初学者级别的问题,这是一个相当高级的答案。请参见@FoxDeploy下面的答案,以获得一个图示示例。 - matty

20
两个之前的回答都是正确的,但是https://blogs.technet.microsoft.com/heyscriptingguy/2014/07/08/getting-to-know-foreach-and-foreach-object/中有一个很好的总结:

当你将输入通过管道传递到ForEach时,它是ForEach-Object的别名。但是,当你在行首放置ForEach时,它是一个Windows PowerShell语句。

更多细节如下:

ForEach语句会在处理每个项目之前一次性将所有项加载到集合中。ForEach-Object希望通过管道流式传输项目,从而降低内存要求,但同时会造成性能损失。

他随后包括了一些性能测量结果,并得出结论:

那么你该使用哪个呢?答案是“取决于情况”。您可以使用ForEach语句或ForEach-Object cmdlet遍历项目集合。如果您有足够的内存,想要最佳性能,并且不关心通过管道将输出传递给另一个命令,则ForEach非常适合。 ForEach-Object(及其别名%和ForEach)从管道获取输入。虽然处理所有内容较慢,但它为您提供了优势开始、处理和结束块的定义,此外,它允许您通过管道将对象流式传输到另一个命令。最终,请根据您的要求和系统的能力选择最适合您的方法。


也许我漏掉了什么,但如果 ForEachForEach-Object 的别名,那么你怎么能在不调用 ForEach-Object 的情况下使用 ForEach 关键字呢? - Charlie Patton
2
我已经理解了,所以我想在这里发布一下,为那些感到困惑的人提供帮助。ForEach-Object 的语法是 ForEach-Object / ForEach / % { },而 foreach 关键字则通过语法 foreach ($item in $items) {} 调用。 - Charlie Patton

5
除了之前提到的技术差异,为了完整性,以下是一些实际差异(请参见):
1. 在管道中,您不知道已处理的项目的总数。在这种情况下,您会选择先获取完整列表,然后进行foreach循环。
示例:
$files = gci "c:\fakepath"
$i = 0
foreach ($file in $files) {
    $i++
    Write-Host "$i / $($files.Count) processed"
}

2.) 对于现有列表,foreach循环比管道版本更快,因为脚本块不必每次被调用。(但是根据您的工作和项目数量,差异可能可以忽略。)

示例:

$items = 0..100000
Measure-Command { $items | ForEach-Object { $_ } }
# ~500ms on my machine
Measure-Command { foreach ($i in $items) { $i } }
# ~70ms on my machine

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