如何在PowerShell中退出ForEach-Object

Mic*_*ync 52 powershell foreach

我有以下代码:

$project.PropertyGroup | Foreach {
    if($_.GetAttribute('Condition').Trim() -eq $propertyGroupConditionName.Trim()) {
        $a = $project.RemoveChild($_);
        Write-Host $_.GetAttribute('Condition')"has been removed.";
    }
};
Run Code Online (Sandbox Code Playgroud)

问题1:如何退出ForEach?我尝试使用"break"和"continue",但它不起作用.

问题2:我发现我可以在一个foreach循环中改变列表...我们不能像在C#中那样做...为什么PowerShell允许我们这样做?

小智 91

项目#1.breakforeach循环中放置确实退出循环,但它不会停止管道.听起来你想要这样的东西:

$todo=$project.PropertyGroup 
foreach ($thing in $todo){
    if ($thing -eq 'some_condition'){
        break
    }
}
Run Code Online (Sandbox Code Playgroud)

项目#2.PowerShell允许您foreach在该数组的循环内修改数组,但这些更改在您退出循环之后才会生效.尝试运行下面的代码作为示例.

$a=1,2,3
foreach ($value in $a){
  Write-Host $value
}
Write-Host $a
Run Code Online (Sandbox Code Playgroud)

我无法评论为什么PowerShell的作者允许这样做,但大多数其他脚本语言(Perl,Python和shell)允许类似的结构.

  • @JamieMarshall 是的,你是绝对正确的。在 ForEach-Object 循环中中断会在我的测试用例中结束整个脚本 (5认同)
  • 这是完全错误的答案。问题是关于“ Foreach-Object”。在powershell中(主要是烦人的问题),foreach是一个“操作符”和一个“别名”。smeltplate在此答案中提供的“ foreach”是“运算符”,而不是“别名”。请参阅[此处。](https://blogs.technet.microsoft.com/heyscriptingguy/2014/07/08/getting-to-know-foreach-and-foreach-object/)注意:此答案是对...的完整重构代码,并且在许多情况下可能无法正常工作。 (3认同)
  • 第 2 项不正确(至少对于具有固定大小数组的 v5)。您可以修改数组并使其在“ForEach”构造中可见。如果你在 `ForEach` 结构中添加 `$a[1] = 9`,它将显示 9 作为第二个元素,但你不能从数组中添加/删除元素,以及你使用 `+ =` 运算符直到最后才会显示。如果不是固定大小的数组(即`$a=[System.Collections.ArrayList]@(1,2,3)`)那么任何修改内容的尝试都会导致`Collection was modified; 枚举操作可能无法执行。` 错误,终止循环。 (2认同)

zet*_*t42 10

有一种方法可以ForEach-Object在不引发异常的情况下中断。它采用了 的一个鲜为人知的功能Select-Object,即使用-First 参数,该功能实际上在处理了指定数量的管道项后会中断管道。

简化示例:

$null = 1..5 | ForEach-Object {

    # Do something...
    Write-Host $_
 
    # Evaluate "break" condition -> output $true
    if( $_ -eq 2 ) { $true }  
 
} | Select-Object -First 1     # Actually breaks the pipeline
Run Code Online (Sandbox Code Playgroud)

输出:

$null = 1..5 | ForEach-Object {

    # Do something...
    Write-Host $_
 
    # Evaluate "break" condition -> output $true
    if( $_ -eq 2 ) { $true }  
 
} | Select-Object -First 1     # Actually breaks the pipeline
Run Code Online (Sandbox Code Playgroud)

请注意,对 的赋值是为了隐藏由中断条件产生的$null输出。$true该值$true可以替换为42, "skip", "foobar",只要你能想到的。我们只需要通过管道传输一些东西,Select-Object这样它就会破坏管道。


dar*_*ove 9

要停止其中的管道,ForEach-Object只需使用continue脚本块下的语句ForEach-Object.continue当你在里面foreach(...) {...}和里面使用它时表现不同ForEach-Object {...},这就是为什么它是可能的.如果你想继续在管道中生成丢弃一些原始对象的对象,那么最好的方法是过滤掉使用Where-Object.


mar*_*sze 7

由于ForEach-Object是一个小命令breakcontinue会表现不同,这里比与foreach 关键字。两者都将停止循环,但也会终止整个脚本:

休息:

0..3 | foreach {
    if ($_ -eq 2) { break }
    $_
}
echo "Never printed"

# OUTPUT:
# 0
# 1
Run Code Online (Sandbox Code Playgroud)

继续:

0..3 | foreach {
    if ($_ -eq 2) { continue }
    $_
}
echo "Never printed"

# OUTPUT:
# 0
# 1
Run Code Online (Sandbox Code Playgroud)

到目前为止,我还没有找到一种在不破坏脚本的情况下破坏 foreach 脚本块的“好”方法,除了“滥用”异常,尽管powershell 核心使用这种方法

扔:

class CustomStopUpstreamException : Exception {}
try {
    0..3 | foreach {
        if ($_ -eq 2) { throw [CustomStopUpstreamException]::new() }
        $_
    }
} catch [CustomStopUpstreamException] { }
echo "End"

# OUTPUT:
# 0
# 1
# End
Run Code Online (Sandbox Code Playgroud)

另一种方法(并非总是可行)是使用foreach 关键字

foreach:

foreach ($_ in (0..3)) {
    if ($_ -eq 2) { break }
    $_
}
echo "End"

# OUTPUT:
# 0
# 1
# End
Run Code Online (Sandbox Code Playgroud)

  • 事实证明,您的“抛出”示例是如何[powershell core](https://github.com/PowerShell/PowerShell/blob/883ca98dd74ea13b3d8c0dd62d301963a40483d6/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object. cs#L725) 做到了。所以我认为这确实是唯一的解决方案。我试图弄清楚如何使用“System.Management.Automation.StopUpstreamCommandsException”,但不太清楚如何使用。但我想抛出一个特定的异常并且只捕获该异常可以做到这一点,这样你就不会忽略其他异常 (2认同)

Sto*_*ffi 6

foreach和之间有差异foreach-object

您可以在这里找到一个很好的描述:MS-ScriptingGuy

对于在PS中进行测试,这里有脚本来显示差异。

ForEach-Object:

# Omit 5.
1..10 | ForEach-Object {
if ($_ -eq 5) {return}
# if ($_ -ge 5) {return} # Omit from 5.
Write-Host $_
}
write-host "after1"
Run Code Online (Sandbox Code Playgroud)
# Cancels whole script at 15, "after2" not printed.
11..20 | ForEach-Object {
if ($_ -eq 15) {continue}
Write-Host $_
}
write-host "after2"
Run Code Online (Sandbox Code Playgroud)
# Cancels whole script at 25, "after3" not printed.
21..30 | ForEach-Object {
if ($_ -eq 25) {break}
Write-Host $_
}
write-host "after3"
Run Code Online (Sandbox Code Playgroud)

前言

# Ends foreach at 5.
foreach ($number1 in (1..10)) {
if ($number1 -eq 5) {break}
Write-Host "$number1"
}
write-host "after1"
Run Code Online (Sandbox Code Playgroud)
# Omit 15. 
foreach ($number2 in (11..20)) {
if ($number2 -eq 15) {continue}
Write-Host "$number2"
}
write-host "after2"
Run Code Online (Sandbox Code Playgroud)
# Cancels whole script at 25, "after3" not printed.
foreach ($number3 in (21..30)) {
if ($number3 -eq 25) {return}
Write-Host "$number3"
}
write-host "after3"
Run Code Online (Sandbox Code Playgroud)

  • 这是一个正确的答案。它解释了所讨论的命令行开关 ForEach-Object。示例使用其别名 foreach,但它仍然是命令行开关。接受的答案有效,但它解释了关键字 foreach。我知道 PowerShell 在这件事上令人困惑,但它们仍然是两个不同的东西。 (3认同)

Rik*_*kki 6

如果您坚持使用ForEach-Object,那么我建议添加一个“中断条件”,如下所示:

$Break = $False;

1,2,3,4 | Where-Object { $Break -Eq $False } | ForEach-Object {

    $Break = $_ -Eq 3;

    Write-Host "Current number is $_";
}
Run Code Online (Sandbox Code Playgroud)

上面的代码必须输出 1,2,3 然后跳过(break before) 4. 预期输出:

Current number is 1
Current number is 2
Current number is 3
Run Code Online (Sandbox Code Playgroud)