Perché "continue" si comporta come "interruzione" in un oggetto Foreach?


123

Se eseguo le operazioni seguenti in uno script PowerShell:

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

Ottengo l'output previsto di:

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

Tuttavia, se uso una pipeline e ForEach-Object, continuesembra uscire dal ciclo della pipeline.

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

Posso ottenere un continuecomportamento simile mentre continuo a eseguire ForEach-Object, in modo da non dover interrompere la mia pipeline?


Ecco una pagina con molti comandi da usare con foreach: techotopia.com/index.php/…
bgmCoder

Ho trovato una spiegazione e un esempio decenti qui ... powershell.com/cs/blogs/tips/archive/2015/04/27/…
Nathan Hartley

Risposte:


164

Usa semplicemente il returninvece di continue. Questo returnritorna dal blocco di script che è invocato da ForEach-Objectsu una particolare iterazione, quindi, simula il continuein un ciclo.

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

C'è un trucco da tenere a mente durante il refactoring. A volte si desidera convertire un foreachblocco di istruzioni in una pipeline con un ForEach-Objectcmdlet (ha anche l'alias foreachche aiuta a rendere facile questa conversione e anche a rendere facili gli errori). Tutti continuei messaggi dovrebbero essere sostituiti con return.

PS: Purtroppo, non è così facile da simulare breakin ForEach-Object.


2
Da quello che sta dicendo OP apparentemente continuepuò essere usato per simulare un breakin ForEach-Object:)
Richard Hauer

6
@ Richard Hauer Tale continue interromperà l'intera sceneggiatura, non solo ForEach-Objectdove viene utilizzato.
Roman Kuzmin

22

Perché For-Eachobject è un cmdlet e non un ciclo continuee breaknon si applicano ad esso.

Ad esempio, se hai:

$b = 1,2,3

foreach($a in $b) {

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

    Write-Host  "after"
}

Otterrai l'output come:

1
after
3
after

È perché continueviene applicato al ciclo foreach esterno e non al cmdlet foreach-object. In assenza di un loop, il livello più esterno, quindi dandoti l'impressione che si comporti come break.

Allora come si ottiene un continuecomportamento simile? Un modo è Where-Object Ovviamente :

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

L'utilizzo del cmdlet Where-Object è un buon suggerimento. Nel mio caso reale, non penso abbia senso trasformare le più righe di codice che precedono la mia istruzione if in un'unica lunga riga di codice difficile da leggere. Tuttavia, questo funzionerebbe per me in altre situazioni.
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.Cosa intendi?
manojlds

3
@manojlds forse pensa che la tua soluzione di una riga sia "difficile da leggere", almeno per me è completamente il contrario. Il modo pipeline di fare le cose è davvero potente e chiaro ed è l'approccio corretto per cose semplici come questa. Scrivere codice nella shell senza trarne vantaggio è inutile.
mjsr

Nel mio caso questa era la risposta giusta, aggiungi una condizione where per filtrare gli oggetti su cui avrei continuato o ritornato in modo da non doverli elaborare in primo luogo. +1
Chris Magnuson

3

Un'altra alternativa è una specie di hack, ma puoi avvolgere il tuo blocco in un ciclo che verrà eseguito una volta. In questo modo, continueavrà l'effetto desiderato:

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

4
Francamente, questo è brutto :) E non solo un trucco perché invece dell'oggetto foreach, potresti anche usare un ciclo foreach.
manojlds

1
@manojlds: 1..100 è solo a scopo illustrativo. do {} while ($ False) funziona altrettanto bene come per ciclo e un po 'più intuitivo.
Harry Martyrossian

2

Una semplice elsedichiarazione lo fa funzionare come in:

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

O in una singola pipeline:

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

Ma una soluzione più elegante è invertire il test e generare output solo per i tuoi successi

1..100 | ForEach-Object {if ($_ % 7 -eq 0 ) {Write-Host "$($_) is a multiple of 7"}}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.