77

我有以下代码:

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

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

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

4

10 回答 10

125

首先,Foreach-Object不是一个实际的循环,调用break它会取消整个脚本,而不是跳到它后面的语句。

相反,break并且continue将在实际foreach循环中按预期工作。

项目#1。将 a 放入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)都允许类似的构造。

于 2012-05-02T19:40:42.680 回答
30

foreach和之间有区别foreach-object

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

为了在 PS 中进行测试,您可以在此处使用脚本来显示差异。

ForEach-对象:

# 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"

前锋

# 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"
于 2019-01-18T01:11:01.707 回答
11

要停止其中一部分的管道,只需使用脚本块ForEach-Object中的语句。当您在 in和 in 中使用它时表现不同,这就是它可能的原因。如果您想在管道中继续生成对象,丢弃一些原始对象,那么最好的方法是使用.continueForEach-Objectcontinueforeach(...) {...}ForEach-Object {...}Where-Object

于 2014-12-24T17:15:01.973 回答
8

由于ForEach-Object是一个cmdletbreak并且continue在此处的行为与使用foreach 关键字不同。两者都会停止循环,但也会终止整个脚本:

休息:

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 脚本块而不破坏脚本,除了“滥用”异常,尽管powershell 核心使用这种方法

扔:

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

# OUTPUT:
# 0
# 1
# End

另一种选择(并非总是可能)是使用foreach 关键字

前锋:

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

# OUTPUT:
# 0
# 1
# End
于 2019-10-18T09:31:53.990 回答
4

如果我希望使用 ForEach-Object cmdlet,下面是问题 #1 的建议方法。它没有直接回答这个问题,因为它没有退出管道。但是,它可能会在 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"

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

于 2020-01-01T15:25:59.990 回答
4

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

$Break = $False;

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

    $Break = $_ -Eq 3;

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

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

Current number is 1
Current number is 2
Current number is 3
于 2020-05-25T02:55:30.020 回答
2

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

使用带有 break 关键字的标签

来自:about_break

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

这是一个简单的例子

: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
于 2020-04-05T00:50:03.323 回答
2

有一种方法可以在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

输出:

1
2

请注意,分配 to$null是为了隐藏$true由 break 条件产生的 的输出。该值$true可以替换为42, "skip", "foobar",您可以命名它。我们只需要通过管道传递一些东西,Select-Object这样它就会破坏管道。

于 2021-02-28T21:36:18.197 回答
0

您有两个选项可以在 PowerShell 中突然退出ForEach-Object管道:

  1. 首先应用退出逻辑Where-Object,然后将对象传递给Foreach-Object, 或
  2. (在可能的情况下)转换Foreach-Object为标准Foreach循环结构。

让我们看看示例:以下脚本在第二次迭代后退出 Foreach-Object 循环(即管道仅迭代 2 次)“:

解决方案1:使用Where-Object过滤器之前Foreach-Object

[boolean]$exit = $false;
1..10 | Where-Object {$exit -eq $false} | Foreach-Object {
     if($_ -eq 2) {$exit = $true}    #OR $exit = ($_ -eq 2);
     $_;
}

或者

1..10 | Where-Object {$_ -le 2} | Foreach-Object {
     $_;
}

解决方案2:转换Foreach-Object为标准Foreach循环结构:

Foreach ($i in 1..10) { 
     if ($i -eq 3) {break;}
     $i;
}

PowerShell 确实应该提供一种更直接的方式来从管道主体中退出或突围。注意:不会退出,它只会跳过特定的迭代(类似于大多数编程语言),这里是一个例子:Foreach-Objectreturncontinuereturn

Write-Host "Following will only skip one iteration (actually iterates all 10 times)";
1..10 | Foreach-Object {
     if ($_ -eq 3) {return;}  #skips only 3rd iteration.
     $_;
}

高温高压

于 2019-08-31T21:08:49.143 回答
-1

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

$project.PropertyGroup | Foreach {
    if(($_.GetAttribute('Condition').Trim() -eq $propertyGroupConditionName.Trim()) -and !$FinishLoop) {
        $a = $project.RemoveChild($_);
        Write-Host $_.GetAttribute('Condition')"has been removed.";
        $FinishLoop = $true
    }
};
于 2019-11-06T21:00:54.250 回答