如何在 PowerShell 中退出 Foreach-Object

我有以下密码:

$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”和“ keep”,但是它不起作用。

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

308142 次浏览

首先,Foreach-Object没有一个实际的循环,在其中调用 break将取消整个脚本,而不是跳到后面的语句。

相反,breakcontinue在实际的 foreach循环中将如您所期望的那样工作。

第一项。将 break放入 foreach循环中确实会退出循环,但是它不会停止管道。听起来你想要这样的东西:

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

第二项。PowerShell 允许您在 foreach循环中通过该数组修改数组,但是这些修改在退出循环之前不会生效。尝试运行下面的代码作为示例。

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

我不能解释为什么 PowerShell 的作者允许这样做,但是大多数其他脚本语言(Perl巨蟒和 shell)允许类似的结构。

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

foreachforeach-object之间存在差异。

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

为了在 PS 中进行测试,这里有一些脚本来显示差异。

对象:

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

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

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

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

解决方案-1 : 在 Foreach-Object之前使用 Where-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-Object管道的 从身体内部注意: return不会退出,它只是跳过特定的迭代(类似于大多数编程语言中的 continue) ,下面是 return的一个例子:

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.
$_;
}

高温

由于 ForEach-ObjectCmdlet,因此 breakcontinue在这里的表现将不同于 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:

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


# OUTPUT:
# 0
# 1
# End

第一个问题的答案 - 您可以简单地让 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
}
};

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

希望这个有用。 祝大家新年快乐。

我发现这个问题时,正在寻找一种方法,有细粒度的流控制,以打破从一个特定的代码块。我决定的解决方案没有被提及。

使用带有 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

如果你在使用 每个对象时使用 坚持,那么我建议添加一个“中断条件”,如下所示:

$Break = $False;


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


$Break = $_ -Eq 3;


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

以上代码必须输出1、2、3,然后跳过(中断前)4。预期输出:

Current number is 1
Current number is 2
Current number is 3

有一种方法可以在不引发异常的情况下从 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

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