我有以下代码:
$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允许我们这样做?
答案 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的作者允许这样做,但大多数其他脚本语言(Perl,Python和shell)允许类似的结构。
答案 1 :(得分:8)
要停止ForEach-Object
所属的管道,只需使用continue
下脚本块内的语句ForEach-Object
。在continue
和foreach(...) {...}
中使用ForEach-Object {...}
时,Where-Object
的行为会有所不同,这就是为什么这样做的原因。如果您想继续在管道中生成丢弃某些原始对象的对象,那么最好的方法是使用{{1}}过滤掉。
答案 2 :(得分:2)
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"
# 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-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脚本块的“好方法”:
投掷:
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)
我在寻找一种方法来进行细粒度的流控制以摆脱特定代码块时发现了这个问题。我没有提到我确定的解决方案...
来自: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
}
};