Warum verhält sich "Weiter" wie "Brechen" in einem Foreach-Objekt?


123

Wenn ich in einem PowerShell-Skript Folgendes mache:

$range = 1..100
ForEach ($_ in $range) {
    if ($_ % 7 -ne 0 ) { continue; }
    Write-Host "$($_) is a multiple of 7"
}

Ich erhalte die erwartete Ausgabe von:

7 is a multiple of 7
14 is a multiple of 7
21 is a multiple of 7
28 is a multiple of 7
35 is a multiple of 7
42 is a multiple of 7
49 is a multiple of 7
56 is a multiple of 7
63 is a multiple of 7
70 is a multiple of 7
77 is a multiple of 7
84 is a multiple of 7
91 is a multiple of 7
98 is a multiple of 7

Wenn ich jedoch eine Pipeline verwenden ForEach-Object, continuescheint der Pipeline - Schleife auszubrechen.

1..100 | ForEach-Object {
    if ($_ % 7 -ne 0 ) { continue; }
    Write-Host "$($_) is a multiple of 7"
}

Kann ich ein continueähnliches Verhalten erzielen, während ich noch ForEach-Object ausführe, damit ich meine Pipeline nicht auflösen muss?


Hier ist eine Seite mit vielen Befehlen, mit denen Sie arbeiten können foreach: techotopia.com/index.php/…
bgmCoder

Hier finden Sie eine anständige Erklärung und ein Beispiel ... Powershell.com/cs/blogs/tips/archive/2015/04/27/…
Nathan Hartley

Antworten:


164

Verwenden Sie einfach die returnanstelle der continue. Dies returnkehrt von dem Skriptblock zurück, der ForEach-Objectbei einer bestimmten Iteration aufgerufen wird , und simuliert somit das continuein einer Schleife.

1..100 | ForEach-Object {
    if ($_ % 7 -ne 0 ) { return }
    Write-Host "$($_) is a multiple of 7"
}

Beim Refactoring ist ein Gotcha zu beachten. Manchmal möchte man einen foreachAnweisungsblock mit einem ForEach-ObjectCmdlet in eine Pipeline konvertieren (es hat sogar den Alias foreach, der diese Konvertierung vereinfacht und auch Fehler erleichtert). Alle continues sollten durch ersetzt werden return.

PS: Leider ist es nicht so einfach zu simulieren breakin ForEach-Object.


2
Nach dem, was OP sagt, continuekann anscheinend ein breakIn simuliert werden ForEach-Object:)
Richard Hauer

6
@ Richard Hauer Solch ein continueWille bricht das ganze Skript, nicht nur ForEach-Objectwo es verwendet wird.
Roman Kuzmin

22

Da For-EachObjekt ein Cmdlets und keine Schleife und continueund breakgelten nicht für sie.

Zum Beispiel, wenn Sie haben:

$b = 1,2,3

foreach($a in $b) {

    $a | foreach { if ($_ -eq 2) {continue;} else {Write-Host $_} }

    Write-Host  "after"
}

Sie erhalten folgende Ausgabe:

1
after
3
after

continueDies liegt daran, dass das auf die äußere foreach-Schleife und nicht auf das Cmdlet foreach-object angewendet wird. In Abwesenheit einer Schleife die äußerste Ebene, wodurch Sie einen Eindruck davon bekommen, wie sie sich verhält break.

Wie kommt man zu einem continueähnlichen Verhalten? Ein Weg ist natürlich Where-Object :

1..100 | ?{ $_ % 7  -eq 0} | %{Write-Host $_ is a multiple of 7}

Die Verwendung des Cmdlets Where-Object ist ein guter Vorschlag. In meinem tatsächlichen Fall halte ich es nicht für sinnvoll, die mehreren Codezeilen vor meiner if-Anweisung in eine einzige lange Zeile schwer lesbaren Codes zu verwandeln. In anderen Situationen würde das jedoch für mich funktionieren.
Justin Dearing

@ JustinDearing - In my actual case, I don't think it makes sense to make the multiple lines of code preceding my if statement into a single long line of hard to read code.Was meinst du?
Manojlds

3
@manojlds Vielleicht denkt er, dass Ihre einzeilige Lösung "schwer zu lesen" ist, zumindest für mich ist das Gegenteil der Fall. Die Pipeline-Methode ist sehr leistungsfähig und klar und der richtige Ansatz für solche einfachen Dinge. Das Schreiben von Code in die Shell, ohne dies auszunutzen, ist sinnlos.
mjsr

In meinem Fall war dies die richtige Antwort. Fügen Sie eine where-Bedingung hinzu, um die Objekte herauszufiltern, für die ich fortfahren oder zurückkehren möchte, damit ich sie nicht erst verarbeiten muss. +1
Chris Magnuson

3

Eine andere Alternative ist eine Art Hack, aber Sie können Ihren Block in eine Schleife einschließen, die einmal ausgeführt wird. Auf diese Weise continuewird der gewünschte Effekt erzielt:

1..100 | ForEach-Object {
    for ($cont=$true; $cont; $cont=$false) {
        if ($_ % 7 -ne 0 ) { continue; }
        Write-Host "$($_) is a multiple of 7"
    }
}

4
Ehrlich gesagt ist das hässlich :) Und nicht nur ein Hack, denn anstelle des foreach-Objekts hätten Sie auch eine foreach-Schleife verwenden können.
Manojlds

1
@manojlds: 1..100 dient nur zur Veranschaulichung. do {} while ($ False) funktioniert genauso gut wie für Loop und etwas intuitiver.
Harry Martyrossian

2

Eine einfache elseAussage macht es wie folgt:

1..100 | ForEach-Object {
    if ($_ % 7 -ne 0 ) {
        # Do nothing
    } else {
        Write-Host "$($_) is a multiple of 7"
    }
}

Oder in einer einzigen Pipeline:

1..100 | ForEach-Object { if ($_ % 7 -ne 0 ) {} else {Write-Host "$($_) is a multiple of 7"}}

Eine elegantere Lösung besteht jedoch darin, Ihren Test umzukehren und nur für Ihre Erfolge eine Ausgabe zu generieren

1..100 | ForEach-Object {if ($_ % 7 -eq 0 ) {Write-Host "$($_) is a multiple of 7"}}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.