如何在PowerShell中退出ForEach-Object

时间:2012-04-23 09:32:24

标签: powershell foreach

我有以下代码:

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

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

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

10 个答案:

答案 0 :(得分:85)

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

$todo=$project.PropertyGroup 
foreach ($thing in $todo){
    if ($thing -eq 'some_condition'){
        break
    }
}

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

$a=1,2,3
foreach ($value in $a){
  Write-Host $value
}
Write-Host $a

我不能评论为什么PowerShell的作者允许这样做,但大多数其他脚本语言(PerlPython和shell)允许类似的结构。

答案 1 :(得分:8)

要停止ForEach-Object所属的管道,只需使用continue下脚本块内的语句ForEach-Object。在continueforeach(...) {...}中使用ForEach-Object {...}时,Where-Object的行为会有所不同,这就是为什么这样做的原因。如果您想继续在管道中生成丢弃某些原始对象的对象,那么最好的方法是使用{{1}}过滤掉。

答案 2 :(得分:2)

foreachforeach-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"
# Cancels whole script at 15, "after2" not printed.
11..20 | ForEach-Object {
if ($_ -eq 15) {continue}
Write-Host $_
}
write-host "after2"
# Cancels whole script at 25, "after3" not printed.
21..30 | ForEach-Object {
if ($_ -eq 25) {break}
Write-Host $_
}
write-host "after3"

foreach

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

答案 3 :(得分:2)

下面是对问题#1的建议方法,如果希望使用ForEach-Object cmdlet,可以使用该方法。 它不会直接回答问题,因为它不会退出管道。 但是,它可以在Q#1中实现所需的效果。 像我这样的业余爱好者唯一可以看到的缺点就是在处理大型管道迭代时。

    $zStop = $false
    (97..122) | Where-Object {$zStop -eq $false} | ForEach-Object {
    $zNumeric = $_
    $zAlpha = [char]$zNumeric
    Write-Host -ForegroundColor Yellow ("{0,4} = {1}" -f ($zNumeric, $zAlpha))
    if ($zAlpha -eq "m") {$zStop = $true}
    }
    Write-Host -ForegroundColor Green "My PSVersion = 5.1.18362.145"

我希望这是有用的。 祝大家新年快乐。

答案 4 :(得分:2)

如果您坚持使用 ForEach-Object ,那么我建议添加这样的“中断条件”:

LIKE

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

$Break = $False;

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

    $Break = $_ -Eq 3;

    Write-Host "Current number is $_";
}

答案 5 :(得分:1)

由于ForEach-Objectcmdlet,因此breakcontinue在这里的行为不同于foreach keyword。两者都将停止循环,但也将终止整个脚本:

中断:

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

# OUTPUT:
# 0
# 1

继续:

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

# OUTPUT:
# 0
# 1

到目前为止,除了“滥用”异常外,我还没有找到一种不破坏脚本就可以破坏foreach脚本块的“好方法”:

投掷:

try {
    0..3 | foreach {
        if ($_ -eq 2) { throw }
        $_
    }
} catch { }
echo "End"

# OUTPUT:
# 0
# 1
# End

替代方法(并非总是可能的)是使用foreach 关键字

foreach:

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

# OUTPUT:
# 0
# 1
# End

答案 6 :(得分:1)

我在寻找一种方法来进行细粒度的流控制以摆脱特定代码块时发现了这个问题。我没有提到我确定的解决方案...

使用带有break关键字的标签

来自:about_break

Break语句可以包含一个标签,使您可以退出嵌入式 循环。标签可以指定任何循环关键字,例如Foreach,For或 而在脚本中。

这是一个简单的例子

:myLabel for($i = 1; $i -le 2; $i++) {
        Write-Host "Iteration: $i"
        break myLabel
}

Write-Host "After for loop"

# Results:
# Iteration: 1
# After for loop

然后是一个更复杂的示例,该示例显示带有嵌套标签并破坏每个标签的结果。

:outerLabel for($outer = 1; $outer -le 2; $outer++) {

    :innerLabel for($inner = 1; $inner -le 2; $inner++) {
        Write-Host "Outer: $outer / Inner: $inner"
        #break innerLabel
        #break outerLabel
    }

    Write-Host "After Inner Loop"
}

Write-Host "After Outer Loop"

# Both breaks commented out
# Outer: 1 / Inner: 1
# Outer: 1 / Inner: 2
# After Inner Loop
# Outer: 2 / Inner: 1
# Outer: 2 / Inner: 2
# After Inner Loop
# After Outer Loop

# break innerLabel Results
# Outer: 1 / Inner: 1
# After Inner Loop
# Outer: 2 / Inner: 1
# After Inner Loop
# After Outer Loop

# break outerLabel Results
# Outer: 1 / Inner: 1
# After Outer Loop

您还可以通过将代码块包装在仅执行一次的循环中来使其适应其他情况。

:myLabel do {
    1..2 | % {

        Write-Host "Iteration: $_"
        break myLabel

    }
} while ($false)

Write-Host "After do while loop"

# Results:
# Iteration: 1
# After do while loop

答案 7 :(得分:1)

有一种方法可以从 ForEach-Object 中断而不抛出异常。它使用了一个鲜为人知的 Select-Object 特性,使用 -First parameter,当指定数量的管道项目已被处理时,它实际上会中断管道。

简化示例:

$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

输出:

1
2

注意对 $null 的赋值是为了隐藏 $true 的输出,它是由中断条件产生的。值 $true 可以替换为 42"skip""foobar",您可以命名它。我们只需要将某些东西通过管道传送到 Select-Object,这样它就会中断管道。

答案 8 :(得分:0)

没有打破管道的直接方法,但是,您可以使用Where-Object应用过滤器。在可能的情况下,最佳解决方案是将Foreach-Object(使用管道传递对象的位置)转换为标准的Foreach循环结构,例如:

Foreach-Object(在管道中传递对象):

1..10 | Foreach-Object {
     if ($_ -eq 3) {return;}
     £_;
}
Write-Host "Only skips one iteration (iterates all 10 times)";

转换为标准的Foreach循环结构:

Foreach ($i in 1..10) { 
     if ($i -eq 3) {break;}
     $i;
}
Write-Host "Ends the loop after 2nd iteration (iterates only 2 times)";

Where-Object过滤器相同(无法转换为Foreach循环):

1..10 | Where-Object {$_ -le 2} | Foreach-Object {
     $_;
}
Write-Host "Ends the loop after 2nd iteration (iterates only 2 times)";

HTH

答案 9 :(得分:0)

问题1的答案- 您可以简单地让您的if语句停止为真

$project.PropertyGroup | Foreach {
    if(($_.GetAttribute('Condition').Trim() -eq $propertyGroupConditionName.Trim()) -and !$FinishLoop) {
        $a = $project.RemoveChild($_);
        Write-Host $_.GetAttribute('Condition')"has been removed.";
        $FinishLoop = $true
    }
};