ForEach-Object cmdlet不带-Parallel参数的情况下,它的实现是循环调用一个匿名函数,依次把管道传入的集合里的每个对象当作上下文来运行代码块。所以在代码块里使用return,退出的是当前调用匿名函数的作用域,外层循环继续对下个传入对象调用匿名函数。下面是实验:

"A", "B", "C", "D" | ForEach-Object {
    if ($_ -eq "C") {
        Write-Host "嘿嘿嘿,C被我跳过啦!"
        return
    }
    Write-Host $_
}

输出如下:

PS C:\Users\Curious\Desktop> .\test.ps1
A
B
嘿嘿嘿,C被我跳过啦!
D

综上,在ForEach-Object cmdlet的PROCESS代码块中使用return,脚本的行为类似于在for循环语句中的continue关键字。

PowerShell中不止有ForEach-Object这个cmdlet,还有foreach关键字,foreach作为关键字,行为和for是完全一样的,想达到提前进入下个循环项的目的,只能使用continue。代码如下:

"A", "B", "C", "D" | ForEach-Object {
    if ($_ -eq "C") {
        Write-Host "嘿嘿嘿,C被我跳过啦!"
        return
    }
    Write-Host $_
}

$collection = "E", "F", "G", "H"

foreach ($currentItemName in $collection) {
    if ($currentItemName -eq "G") {
        Write-Host "呵呵,我也会跳过G。"
        continue
    }
    Write-Host $currentItemName
}

输出:

PS C:\Users\Curious\Desktop> .\test.ps1
A
B
嘿嘿嘿,C被我跳过啦!
D
E
F
呵呵,我也会跳过G。
H

需要注意的一点是,foreach语句的形式跟ForEach-Object有很大的区别,写脚本的时候要分清写的到底是foreach还是ForEach-Object才能决定用return还是continue

这是一个很容易混淆的问题,因为实际上只有ForEach-Object可以接受管道输入,foreach语句是不行的,但假如我们写出以下形式的代码,却也可以正常运行。原因是写在管道中的foreach%都被当作ForEach-Object的Alias,实际上运行的还是ForEach-Object。这些易混淆的习惯写法在VSCode的PowerShell插件里会被提示。PowerShell是我最喜欢的脚本语言,但草率的内置alias破坏了它的完美,关于这一点,可以参考curl作为内置命令alias引起的争议。

"A", "B", "C", "D" | foreach {
    if ($_ -eq "C") {
        Write-Host "嘿嘿嘿,C被我跳过啦!"
        return
    }
    Write-Host $_
}

"A", "B", "C", "D" | % {
    if ($_ -eq "C") {
        Write-Host "嘿嘿嘿,C被我跳过啦!"
        return
    }
    Write-Host $_
}