如何在 PowerShell 中退出 ForEach-Object
Posted
技术标签:
【中文标题】如何在 PowerShell 中退出 ForEach-Object【英文标题】:How to exit from ForEach-Object in PowerShell 【发布时间】:2012-05-03 21:56:17 【问题描述】:我有以下代码:
$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 允许我们这样做?
【问题讨论】:
【参考方案1】:首先,Foreach-Object
不是真正的循环,在其中调用 break
将取消整个脚本,而不是跳到后面的语句。
相反,break
和 continue
将在实际的 foreach
循环中正常工作。
项目#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 的作者允许这样做,但大多数其他脚本语言(Perl、Python 和 shell)都允许类似的构造。
【讨论】:
我认为您的意思是在循环中添加一行$a = 1..23;
以表明更改不会反映在循环执行中。
第 2 项不正确(至少对于具有固定大小数组的 v5)。您可以修改数组并使其在 ForEach
构造中可见。如果您要在 ForEach
构造中添加 $a[1] = 9
,它将显示 9 作为第二个元素,但您不能从数组中添加/删除元素,并且您使用 +=
运算符添加的任何内容都不会显示直到最后。如果不是固定大小的数组(即$a=[System.Collections.ArrayList]@(1,2,3)
),那么任何修改内容的尝试都会导致Collection was modified; enumeration operation may not execute.
错误,终止循环。
这是一个完全错误的答案。问题是关于Foreach-Object
。在 powershell 中(顺便说一句,这很烦人)foreach
是一个“操作员”和一个“别名”。 smeltplate 在此答案中提供的foreach
是“操作员”而不是“别名”。请参阅here. 注意:此答案是对代码的完整重构,可能不适用于许多情况。
@JamieMarshall 是的,你是绝对正确的。中断 ForEach-Object 循环会在我的测试用例中结束整个脚本
这对我不起作用 - 我在 Get-ADGroupMember
上遇到了中断异常【参考方案2】:
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"
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"
【讨论】:
这是一个正确的答案。它解释了 commandlet ForEach-Object,就像有问题的一样。示例使用它的别名 foreach,但它仍然是命令行开关。接受的答案有效,但它解释了关键字 foreach。我知道 PowerShell 在这件事上令人困惑,但这仍然是两件不同的事情。 我花了这么多时间调试代码,想知道为什么break
打破了我的整个脚本,而不仅仅是ForEach-Object
循环,直到我终于找到了这个答案。谢谢!【参考方案3】:
要停止ForEach-Object
所属的管道,只需在ForEach-Object
下的脚本块内使用语句continue
。当您在foreach(...) ...
和ForEach-Object ...
中使用continue
时,它的行为会有所不同,这就是它可能的原因。如果您想在管道中继续生成对象,丢弃一些原始对象,那么最好的方法是使用Where-Object
进行过滤。
【讨论】:
+1。有趣的。我从来不知道这一点。无论您使用break
还是continue
,它似乎都一样。我想知道为什么这对问题的作者不起作用,因为他们说他们都试过了。
知道记录在哪里吗? about_break
和 about_continue
只讨论 for
、foreach
、while
和 switch
语句。 ForEach-Object
没有提到 break
或 continue
。
这对我不起作用 - 我在 Get-ADGroupMember
上得到一个 Continue Exception
Continue 在这种情况下会中断父循环的执行。在此处查看说明:powershell.org/forums/topic/…
感谢您的回答!【参考方案4】:
由于ForEach-Object
是cmdlet,break
和continue
在此处的行为与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 脚本块而不破坏脚本,尽管powershell core uses this approach:
投掷:
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
【讨论】:
事实证明,您的“投掷”示例就是powershell core 的做法。所以我认为这是唯一的解决方案。我试图弄清楚如何使用System.Management.Automation.StopUpstreamCommandsException
,但不太清楚如何使用。但我想抛出一个特定的异常并且只捕获该异常可以使它不会忽略其他异常【参考方案5】:
如果我希望使用 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"
我希望这是有用的。 祝大家新年快乐。
【讨论】:
哇。摆脱不可阻挡的管道真的很有帮助。谢谢 感谢“Where-Object”正是我所需要的。 X【参考方案6】:如果您坚持使用 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
【讨论】:
【参考方案7】:我在寻找一种方法来进行细粒度流控制以打破特定代码块时发现了这个问题。没有提到我确定的解决方案...
使用带有 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
【讨论】:
【参考方案8】:有一种方法可以在不引发异常的情况下从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
,这样它就会中断管道。
【讨论】:
【参考方案9】:问题 #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
;
【讨论】:
问题是这并没有停止循环;它只会一遍又一遍地评估为$false
(这对于非常大和/或昂贵的集合来说会很慢)。【参考方案10】:
您有两个选项可以在 PowerShell 中突然退出 ForEach-Object
管道:
-
首先在
Where-Object
中应用退出逻辑,然后将对象传递给Foreach-Object
,或者
(在可能的情况下)将Foreach-Object
转换为标准的Foreach
循环结构。
让我们看示例:以下脚本在第二次迭代后退出 Foreach-Object 循环(即管道仅迭代 2 次)“:
Solution-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
$_;
Solution-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.
$_;
HTH
【讨论】:
以上是关于如何在 PowerShell 中退出 ForEach-Object的主要内容,如果未能解决你的问题,请参考以下文章
仅在非交互式运行时如何从 Powershell 脚本返回退出代码
powershell 远程执行bat脚本,去启动一个应用,如何让应用一直运行,powershell能正常退出?