Чому "продовження" поводиться як "прорив" в об'єкті Foreach?


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
З того, що говорить ОП, очевидно, continueможна використовувати для імітації breakв ForEach-Object:)
Річард Хауер

6
@ Річард Хауер Таке 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. За відсутності циклу, самого зовнішнього рівня, отже, ви створюєте враження, як він діє break.

То як же ви отримуєте continueподібну поведінку? Один із способів - де-об'єкт курсу:

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

Використання командлету Where-Object є гарною пропозицією. У моєму фактичному випадку я не думаю, що має сенс перетворювати кілька рядків коду, що передує моєму оператору, якщо заява - в один довгий рядок з важким для читання кодом. Однак це могло б працювати для мене в інших ситуаціях.
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.Що ти маєш на увазі?
manojlds

3
@manojlds, можливо, він вважає, що ваше одне рядкове рішення "важко читати", принаймні для мене це абсолютно навпаки. Трубопровідний спосіб дій дійсно потужний і зрозумілий, і це правильний підхід до таких простих речей. Писати код в оболонці, не користуючись цим, безглуздо.
mjsr

У моєму випадку це була правильна відповідь, додайте умову "де", щоб відфільтрувати об'єкти, які я б продовжував або повертав, щоб мені не потрібно було обробляти їх в першу чергу. +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"
    }
}

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"}}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.