Почему «continue» ведет себя как «break» в Foreach-Object?

123

Если я сделаю следующее в сценарии PowerShell:

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

Я получаю ожидаемый результат:

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

Однако, если я использую конвейер и ForEach-Object, continueкажется, вырвется из цикла конвейера.

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

Могу ли я добиться continueподобного поведения при выполнении ForEach-Object, чтобы мне не пришлось разбивать конвейер?

Джастин Диринг
источник
Вот страница с множеством команд для использования foreach: techotopia.com/index.php/…
bgmCoder
Нашел достойное объяснение и образец здесь ... powershell.com/cs/blogs/tips/archive/2015/04/27/…
Натан Хартли,

Ответы:

164

Просто используйте returnвместо continue. Это returnвозвращается из блока сценария, который вызывается ForEach-Objectна определенной итерации, таким образом, он имитирует continueцикл.

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

При рефакторинге следует иметь в виду одну хитрость. Иногда кто-то хочет преобразовать foreachблок операторов в конвейер с помощью ForEach-Objectкомандлета (у него даже есть псевдоним, foreachкоторый помогает упростить это преобразование и также легко исправить ошибки). Все continues следует заменить на return.

PS: К сожалению, это не так легко имитировать breakв ForEach-Object.

Роман Кузьмин
источник
2
Из того, что говорит OP, по-видимому, continueможно использовать для имитации breakin ForEach-Object:)
Ричард Хауэр,
6
@ Richard Hauer: Это continueсломает весь сценарий, а не только то, ForEach-Objectгде он используется.
Роман Кузьмин
22

Поскольку For-Eachобъект является командлет , а не цикл , а continueи breakне применять к нему.

Например, если у вас есть:

$b = 1,2,3

foreach($a in $b) {

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

    Write-Host  "after"
}

Вы получите следующий результат:

1
after
3
after

Это связано с тем, что continueприменяется к внешнему циклу foreach, а не к командлету foreach-object. В отсутствие петли это самый внешний уровень, поэтому создается впечатление, что он действует как break.

Так как же добиться continueподобного поведения? Один из способов, конечно, это Where-Object :

1..100 | ?{ $_ % 7  -eq 0} | %{Write-Host $_ is a multiple of 7}
manojlds
источник
Рекомендуется использовать командлет Where-Object. В моем фактическом случае я не думаю, что имеет смысл превращать несколько строк кода, предшествующих моему оператору if, в одну длинную строку трудночитаемого кода. Однако это сработало бы для меня в других ситуациях.
Джастин Диринг,
@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.Что ты имеешь в виду?
Manojlds
3
@manojlds, возможно, он думает, что ваше однострочное решение "трудно читать", по крайней мере, для меня это совершенно противоположное. Конвейерный способ решения задач действительно мощный и понятный, и это правильный подход для таких простых вещей. Писать код в оболочке, не пользуясь этим, бессмысленно.
mjsr
В моем случае это был правильный ответ: добавьте условие where для фильтрации объектов, которые я буду выполнять, продолжая или возвращаясь, чтобы мне не нужно было обрабатывать их в первую очередь. +1
Крис Магнусон
3

Другой вариант - это своего рода взлом, но вы можете заключить свой блок в цикл, который будет выполняться один раз. Таким образом, continueбудет желаемый эффект:

1..100 | ForEach-Object {
    for ($cont=$true; $cont; $cont=$false) {
        if ($_ % 7 -ne 0 ) { continue; }
        Write-Host "$($_) is a multiple of 7"
    }
}
Zdan
источник
4
Честно говоря, это некрасиво :) И не просто хакерство, потому что вместо объекта foreach можно было бы использовать цикл foreach.
manojlds
1
@manojlds: 1..100 используется только для иллюстрации. do {} while ($ False) работает так же хорошо, как и цикл, и немного более интуитивно понятен.
Гарри Мартиросян
2

Простое elseутверждение заставляет его работать так:

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

Или в одном конвейере:

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

Но более элегантное решение - перевернуть тест и генерировать результат только для ваших успехов.

1..100 | ForEach-Object {if ($_ % 7 -eq 0 ) {Write-Host "$($_) is a multiple of 7"}}
Alvin
источник