Foreach — из книги PowerShell in depth

https://www.manning.com/books/powershell-in-depth-second-edition
  • Перевод
Глава 19.3. В ней описывается конструкция Foreach, как ее применять.

Эта конструкция имеет те же цели что и командлет ForEach-Object. Командлет ForEach-Object имеет алиас ForEach, который легко спутать с оператором ForEach… потому что они имеют абсолютно одинаковые имена. PowerShell смотрит на контекст чтобы выяснить, какой же Foreach применяется сейчас. Вот пример того, как оператор, и командлет делают одно и тоже
Get-Service –name B* | ForEach { $_.Pause() }

$services = Get-Service –name B*
ForEach ($service in $services) 
{
    $service.Pause()
}


Разберем как работает эта оператор Foreach, в нем есть две переменные в скобках, разделенные ключевым словом in. Вторая переменная, как ожидается содержит один или несколько объектов с которыми мы хотим что то сделать. Первая переменная для внутреннего использования, она будет содержать по очереди в каждом проходе объект из второй переменной. Если вы писали на VBScript то такого рода вещи должны выглядеть для вас знакомыми.

Обычная практика именовать вторую переменную во множественном числе, а первую в единственном. Это не требуется соглашением или стандартом, хотя вы можете написать Foreach( $Fred in $Rocvill ) при условии что в $Rockvill содержатся объекты, PowerShell будет рад обработать это. Но придерживайтесь осознанного именования переменных, тогда вам придется гораздо меньше запоминать.

PowerShell автоматически принимает по одному объекту из второй переменной и помещает его в первый на каждом проходе цикла. Внутри конструкции можно использовать первую переменную для того чтобы делать что то с объектом, например вы можете вызвать метод Pause этого объекта. Не используйте $_ (или PSItem) в операторе, как вы это делаете в командлете.

Иногда, вы можете быть не уверены какую конструкцию использовать — оператор или командлет. Теоретически передача по конвееру на Foreach-object может использовать меньше памяти в некоторых ситуациях. По нашим наблюдениям командлет работает медленнее с большими наборами объектов. Если у вас есть сложная обработка в конвеере, особенно если ведется обработка оператором Foreach внутри командлета Foreach лучше использовать полное название чтобы однозначно определить что именно исполльзуется.

Вам также нужно быть осторожнее если вы хотите сделать передачу по конвееру на другой командлет или функцию. Пример
PS C:\> foreach ($service in $services) {
>>                $service | select Name,DisplayName,Status
>>  } | Sort Status
>>
An empty pipe element is not allowed.
At line:3 char:4
+ } | <<<< Sort Status
+ CategoryInfo : ParserError: (:) [],
ParentContainsErrorRecordException
+ FullyQualifiedErrorId : EmptyPipeElement

PowerShell выдал ошибку, потому что нечего передать дальше по конвееру на Sort, но этот пример будет работать:
PS C:\> $services | foreach {
>> $_ | select Name,DisplayName,Status
>> } | Sort Status
>>
Name DisplayName Status
---- ----------- ------
Browser Computer Browser Stopped
BDESVC BitLocker Drive Encrypt... Stopped
bthserv Bluetooth Support Service Running
BFE Base Filtering Engine Running
BITS Background Intelligent ... Running

Другим фактором является нужно ли вам будет использовать коллекцию объектов еще раз, если вам понадобится дальше коллекция то лучше использовать оператор, чтобы не получать данные снова. Последний момент состоит в том что многие скрипты которые вы найдете в интернете являются на самом деле конвертацией скриптов VBScript, в нем постоянно приходилось перебирать коллекции объектов. Поэтому всегда стоит остановиться и подумать секунду чтобы определить наилучший подход и надо ли перебирать коллекцию вообще.

Наш совет — не используйте перебор если есть возможность этого не делать. Для примера перепишем наш код приведенный ранее по другому:
Get-Service –name B* | Suspend-Service

и пример сортировки будет намного лучше выглядеть если написать его так:
Get-Service b* | Sort Status | select Name,DisplayName,Status

Если вам не нужно для чего то перебирать все объекты не делайте этого. Использование Foreach командлета либо оператора иногда является признаком того вы делаете то что не следует делать. Это конечно верно не во всех случаях, но задумайтесь может ли PowerShell сделать часть работы за вас. Не зацикливайтесь на этом вопросе, многие командлеты просто не принимают выходные данные из конвеера на параметры которые вам могут быть необходимы, в таком случае использование Foreach становится необходимостью. Важнее закончить работу чем отслеживать правильно или неправильно вы что то написали.
вставка переводчика
имеется в виду способность пошика связывать между собой параметры объектов на конвеере по именам параметров. Если есть вероятность что пош не сможет однозначно идентифицировать параметр объекта в процессе «parameter binding» то придется перебирать объекты по одному. Например WMI. Общая рекомендфция — используйте конвеерную обработку, цикл как правило, прерывает конвеер, а иногда приводит к ненужным итерациям. Например эффективнее сделать Select а затем обработку, чем if внутри Foreach и обработку

Хороший момент чтобы напомнить вам о скобках. Мы приврали когда сказали что оператору Foreach требуется две переменные. С технической точки зрения для этого нужна только одна переменная. Вторая должна содержать коллекцию объектов, которые могут находится либо в переменной как в наших примерах до этого, либо могут быть результатом выражения в скобках, например:
foreach ($service in (Get-Service –name B*)) {
$service.pause()
}

Эта версия труднее читается, но является абсолютно законной, устраняя необходимость хранить промежуточные результаты в переменной. Внутренние скобки вычисляются в первую очередь и производят коллекцию объектов которая передается дальше.
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 20
  • +1
    Спасибо за информативную статью, было очень интересно, продолжайте ещё да побольше побольше...
    • 0
      прошу прощения, я оказывается нажал опубликовать в процессе написания.
    • 0
      Вы cерьезно?
      • 0
        прошу прощения, я оказывается нажал опубликовать в процессе написания.
        • 0
          Вот вы переопубликовали топик, а мой коментарий останется здесь навсегда
      • +1
        foreach ($service in (Get-Service –name B*)) {
        $service.pause()
        }

        Благодаря тому что, в новых версиях появился синтаксис

        ForEach-Object [-MemberName] <String> [-ArgumentList <Object[]>]  

        Пример можно сократить до

        gsv b* | % pause
        • +1
          ставлю плюс за знание сокращений. Но всетаки я переводил статью чтобы показать что можно избежать перебора совсем, идея не перебирать объекты, а использовать конвеер и вычисления в скобках. Вообще идея фильтровать раньше, форматировать позже. Избегать перебора форичом вместо этого использовать конвеер. Тоесть вместо конструкций типа

          foreach ($service in $services) { $service | select Name,DisplayName,Status } | Sort Status

          нужно привыкать использовать конвеер так

          Get-Service b* | Sort Status | select Name,DisplayName,Status

          вместо

          Get-Service –name B* | ForEach { $_.Pause() }
          или
          gsv b* | % pause

          использовать

          (Get-Service b*).Start()
          • 0
            его нельзя так использовать, вторая конструкция выберет только первый попавшийся сервис на b* а не все. Я не очень понял — как вы избежите перебора и почему использовать вычисления в скобках лучше.

            Мой пример тоже использует конвейер.
            • 0
              попробуйте, выбрал сервисы побезопаснее на своем компе, на fd* у меня два сервиса

              cls
              Get-Service fd*
              (Get-Service fd*).Start()
              Start-Sleep -s 1
              Get-Service fd*
              (Get-Service fd*).Stop()
              Start-Sleep -s 1
              Get-Service fd*
              ''
              • 0
                Да, был неправ, стартуют все
        • –1
          Пожалуйста остановитесь, на моей главной уже три подряд топика про PS, при том что я на него не подписан…
          • +1
            Мне кажется в главе нет еще одного различия командлета и оператора (ведь книга Posh in depth основана на версии 3.0) — поддержка параллельных операций. Командлет их неумеет, а оператор может выполнять действия внутри операторных скобок параллельно в конструкции

            workflow {
              foreach -parallel ($one in $more) {
            
              }
            }
            • 0
              ну это же перевод. Параллельный операции в главах wrokflow
            • 0
              Добрый день,

              Подскажите пожалуйста почему для сокращений не прокатывает вот такой вариант:

               "abc".split("") % { echo "$($_)\n"}

              Запуск сообщит об ошибке

              Method invocation failed because [System.String[]] does not contain a method named 'op_Modulus'.
              At line:1 char:1
              + "abc".split("") % { echo "$($_)\n"}
              + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                  + CategoryInfo          : InvalidOperation: (op_Modulus:String) [], RuntimeException
                  + FullyQualifiedErrorId : MethodNotFound

              Как бы все понятно в чем дело, но почему все именно так сделанно?
              • 0
                знак конвеера случайно не забыли?

                "abc".split("") | % { echo "$($_)\n"}

                я правильно вас понял что вам нужно разбить строку по делимитерам и подать на foreach?
                • 0
                  1. У вас пропущен символ конвейера ( | )
                    "abc".split("") | % { echo "$($_)\n"}
                  2. В PoSh символ новой строки — `r или `n , а не \r\n
                    "abc".split("") | % { echo "$($_)rn"}
                    `
                  3. В случае echo (он же Write-Output) вообще не требуется символ новой строки — он подразумевается и не отключается (в отличие от Write-Host)
                    "abc".split("") | % { echo "$($_)"}
                  4. В соответствии с MSDN, String::Split вам вернет всего одну строку в этом случае. Для разбора по символьно нужно использовать ToCharArray
                    "abc".ToCharArray() | % { echo $_}
                  • 0
                    echo как правило не нужно, так как результат автоматически показывается с переводами строк, так что:

                    [char[]]"abs"
                  • 0
                    Кстати, перевод строки правильно "`n"
                  • 0
                    Спасибо за перевод.

                    Вот из-за этой путаницы с двумя foreach мне всегда нравились конвеерные конструкции вида… | % {… }. Как, собственно, и в комментариях выше, привет, коллеги! :-)

                    Согласен с тем, что у форича есть плюс в плане параллельных конструкций, но, я если честно, их как-то через workflow привык делать, несмотря на его ограничения. Там не одно и то же внутри, нет?
                    • 0
                      да все тоже самое. просто в воркфлоу появляется параметр -parallel, тупо перебирает то что в скобках и стартует паралельно

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.