Dlaczego „continue” zachowuje się jak „przerwa” w Foreach-Object?

123

Jeśli wykonam następujące czynności w skrypcie PowerShell:

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

Otrzymuję oczekiwany wynik:

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

Jeśli jednak używam rurociągu i ForEach-Object, continuewydaje się , że wyłamuje się z pętli rurociągu.

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

Czy mogę uzyskać continuepodobne zachowanie, nadal wykonując ForEach-Object, więc nie muszę przerywać mojego potoku?

Justin Dearing
źródło
Oto strona z wieloma komendami do użycia z foreach: techotopia.com/index.php/ ...
bgmCoder
Znalazłem przyzwoite wyjaśnienie i próbkę tutaj ... powershell.com/cs/blogs/tips/archive/2015/04/27/…
Nathan Hartley

Odpowiedzi:

164

Po prostu użyj returnzamiast continue. Te returnpowroty z bloku skryptu który powoływała ForEach-Objectsię na danej iteracji, tym samym, że symuluje continuew pętli.

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

Podczas refaktoryzacji należy mieć na uwadze pewien problem. Czasami ktoś chce przekonwertować foreachblok instrukcji na potok za pomocą polecenia ForEach-Objectcmdlet (ma nawet alias, foreachktóry ułatwia tę konwersję i ułatwia popełnienie błędów). Wszystkie continueznaki należy zastąpić return.

PS: Niestety, nie jest to łatwe do symulacji breakw ForEach-Object.

Roman Kuzmin
źródło
2
Z tego, co mówi OP, najwyraźniej continuemożna go użyć do symulacji breakw ForEach-Object:)
Richard Hauer
6
@ Richard Hauer Taki zepsuje continuecały skrypt, nie tylko ForEach-Objecttam, gdzie jest używany.
Roman Kuzmin
22

Ponieważ For-Eachobiekt jest cmdletem, a nie pętlą continuei breaknie ma do niego zastosowania.

Na przykład, jeśli masz:

$b = 1,2,3

foreach($a in $b) {

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

    Write-Host  "after"
}

Otrzymasz dane wyjściowe jako:

1
after
3
after

Dzieje się tak, ponieważ continuezostaje zastosowany do zewnętrznej pętli foreach, a nie do polecenia cmdlet foreach-object. W przypadku braku pętli, najbardziej zewnętrznego poziomu, co daje wrażenie, że zachowuje się jak break.

Jak więc uzyskać continuepodobne zachowanie? Jednym ze sposobów jest oczywiście Where-Object :

1..100 | ?{ $_ % 7  -eq 0} | %{Write-Host $_ is a multiple of 7}
manojlds
źródło
Użycie polecenia cmdlet Where-Object jest dobrą sugestią. W moim przypadku nie wydaje mi się sensowne tworzenie wielu wierszy kodu poprzedzających instrukcję if w jedną długą linię trudnego do odczytania kodu. Jednak to by zadziałało w innych sytuacjach.
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.Co masz na myśli?
manojlds
3
@manojlds może myśli, że twoje jedno wierszowe rozwiązanie jest „trudne do odczytania”, przynajmniej dla mnie jest zupełnie odwrotnie. Potokowy sposób robienia rzeczy jest naprawdę potężny i jasny i jest właściwym podejściem do takich prostych rzeczy. Pisanie kodu w powłoce bez korzystania z tego jest bezcelowe.
mjsr
W moim przypadku była to właściwa odpowiedź, dodaj warunek where, aby odfiltrować obiekty, które będę kontynuował lub wracał, aby nie musieć ich przetwarzać w pierwszej kolejności. +1
Chris Magnuson
3

Inną alternatywą jest rodzaj hackowania, ale możesz zawinąć swój blok w pętlę, która zostanie wykonana raz. W ten sposób continueprzyniesie pożądany efekt:

1..100 | ForEach-Object {
    for ($cont=$true; $cont; $cont=$false) {
        if ($_ % 7 -ne 0 ) { continue; }
        Write-Host "$($_) is a multiple of 7"
    }
}
zdan
źródło
4
Szczerze mówiąc, to brzydkie :) I to nie tylko hack, ponieważ zamiast foreach-object równie dobrze można było użyć pętli foreach.
manojlds
1
@manojlds: 1..100 służy wyłącznie do celów ilustracyjnych. do {} while ($ False) działa tak samo dobrze jak pętla for i jest nieco bardziej intuicyjny.
Harry Martyrossian
2

Proste elsestwierdzenie sprawia, że ​​działa jak w:

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

Lub w pojedynczym rurociągu:

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

Ale bardziej eleganckim rozwiązaniem jest odwrócenie testu i wygenerowanie wyniku tylko dla twoich sukcesów

1..100 | ForEach-Object {if ($_ % 7 -eq 0 ) {Write-Host "$($_) is a multiple of 7"}}
Alvin
źródło