簡體   English   中英

如何在 PowerShell 中退出 ForEach-Object

[英]How to exit from ForEach-Object in PowerShell

我有以下代碼:

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

問題 1:如何退出 ForEach-Object? 我嘗試使用“中斷”和“繼續”,但它不起作用。

問題#2:我發現我可以在foreach循環中更改列表...我們不能像在 C# 中那樣做... 為什么 PowerShell 允許我們這樣做?

首先, Foreach-Object不是一個實際的循環,在其中調用break將取消整個腳本而不是跳到它后面的語句。

相反,在實際的foreach循環中, breakcontinue將按照您的預期工作。

項目#1。 foreach循環中放置break確實會退出循環,但不會停止管道。 聽起來你想要這樣的東西:

$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)允許類似的構造。

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

要停止ForEach-Object所屬的管道,只需在ForEach-Object下的腳本塊內使用continue語句。 當您在foreach(...) {...}ForEach-Object {...}使用它時, continue行為會有所不同,這就是它可能的原因。 如果您想在管道中繼續生成對象並丟棄一些原始對象,那么最好的方法是使用Where-Object過濾掉。

因為ForEach-Object是一個cmdlet ,所以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

如果我希望使用 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"

我希望這是有用的。 祝大家新年快樂。

如果您堅持使用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

有一種方法可以在不拋出異常的情況下從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這樣它就會中斷管道。

我在尋找一種方法來進行細粒度流控制以打破特定代碼塊時發現了這個問題。 沒有提到我確定的解決方案......

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

您有兩個選項可以在 PowerShell 中突然退出ForEach-Object管道:

  1. 首先在Where-Object應用退出邏輯,然后將對象傳遞給Foreach-Object ,或者
  2. (在可能的情況下)將Foreach-Object轉換為標准的Foreach循環結構。

讓我們看一下示例:以下腳本在第二次迭代后退出 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.
     $_;
}

HTH

問題 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
    }
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM