Pourquoi 'continue' se comporte-t-il comme 'break' dans un objet Foreach?

123

Si je fais ce qui suit dans un script PowerShell:

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

J'obtiens la sortie attendue de:

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

Cependant, si j'utilise un pipeline et ForEach-Object, continuesemble sortir de la boucle du pipeline.

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

Puis-je avoir un continuecomportement de type-like tout en faisant ForEach-Object, pour ne pas avoir à interrompre mon pipeline?

Justin Dearing
la source
Voici une page avec de nombreuses commandes à utiliser avec foreach: techotopia.com/index.php/…
bgmCoder
J'ai trouvé une explication décente et un échantillon ici ... powershell.com/cs/blogs/tips/archive/2015/04/27
Nathan Hartley

Réponses:

164

Utilisez simplement le returnau lieu du continue. Cela returnrevient du bloc de script qui est appelé par ForEach-Objectsur une itération particulière, ainsi, il simule le continuedans une boucle.

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

Il y a un piège à garder à l'esprit lors de la refactorisation. Parfois, on veut convertir un foreachbloc d'instructions en pipeline avec une ForEach-Objectapplet de commande (il a même l'alias foreachqui aide à rendre cette conversion facile et à faire des erreurs aussi). Tous les continues doivent être remplacés par return.

PS: Malheureusement, il est facile de simuler breaken ForEach-Object.

Roman Kuzmin
la source
2
D'après ce que dit OP, il continuepeut apparemment être utilisé pour simuler un breakin ForEach-Object:)
Richard Hauer
6
@ Richard Hauer Un tel cas continuecassera tout le script, pas seulement ForEach-Objectlà où il est utilisé.
Roman Kuzmin
22

Parce que l' For-Eachobjet est une applet de commande et non une boucle et continueet breakne s'y applique pas.

Par exemple, si vous avez:

$b = 1,2,3

foreach($a in $b) {

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

    Write-Host  "after"
}

Vous obtiendrez la sortie comme:

1
after
3
after

C'est parce que le continueest appliqué à la boucle foreach externe et non à l'applet de commande foreach-object. En l'absence de boucle, le niveau le plus extérieur, vous donnant ainsi une impression d'agir comme break.

Alors, comment obtenez-vous un continuecomportement semblable? Un moyen est bien sûr Where-Object :

1..100 | ?{ $_ % 7  -eq 0} | %{Write-Host $_ is a multiple of 7}
manojlds
la source
L'utilisation de l'applet de commande Where-Object est une bonne suggestion. Dans mon cas réel, je ne pense pas qu'il soit logique de transformer les multiples lignes de code précédant mon instruction if en une seule longue ligne de code difficile à lire. Cependant, cela fonctionnerait pour moi dans d'autres situations.
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.Que voulez-vous dire?
manojlds
3
@manojlds pense peut-être que votre solution en une ligne est "difficile à lire", du moins pour moi, c'est complètement le contraire. La manière de faire les choses par pipeline est vraiment puissante et claire et constitue la bonne approche pour des choses simples comme celle-là. Ecrire du code dans le shell sans en profiter est inutile.
mjsr
Dans mon cas, c'était la bonne réponse, ajoutez une condition where pour filtrer les objets sur lesquels je ferais une continuation ou un retour afin de ne pas avoir besoin de les traiter en premier lieu. +1
Chris Magnuson
3

Une autre alternative est une sorte de hack, mais vous pouvez envelopper votre bloc dans une boucle qui s'exécutera une fois. De cette façon, continueaura l'effet souhaité:

1..100 | ForEach-Object {
    for ($cont=$true; $cont; $cont=$false) {
        if ($_ % 7 -ne 0 ) { continue; }
        Write-Host "$($_) is a multiple of 7"
    }
}
zdan
la source
4
Franchement, c'est moche :) Et pas seulement un hack car au lieu de l'objet foreach, vous auriez pu aussi bien utiliser une boucle foreach.
manojlds
1
@manojlds: 1..100 est uniquement à titre d'illustration. do {} while ($ False) fonctionne aussi bien que for loop et est un peu plus intuitif.
Harry Martyrossian
2

Une simple elsedéclaration le fait fonctionner comme dans:

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

Ou en un seul pipeline:

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

Mais une solution plus élégante consiste à inverser votre test et à générer une sortie uniquement pour vos succès

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