Глава 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()
}

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

Комментарии (20)


  1. shpaker
    06.03.2016 16:49
    +1

    Спасибо за информативную статью, было очень интересно, продолжайте ещё да побольше побольше...


    1. pak-nikolai
      06.03.2016 17:24

      прошу прощения, я оказывается нажал опубликовать в процессе написания.


  1. IamKarlson
    06.03.2016 17:22

    Вы cерьезно?


    1. pak-nikolai
      06.03.2016 17:24

      прошу прощения, я оказывается нажал опубликовать в процессе написания.


      1. IamKarlson
        06.03.2016 17:44

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


  1. ApeCoder
    06.03.2016 18:30
    +1

    foreach ($service in (Get-Service –name B*)) {
    $service.pause()
    }

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

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

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

    gsv b* | % pause


    1. pak-nikolai
      06.03.2016 19:03
      +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()


      1. ApeCoder
        06.03.2016 19:13

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

        Мой пример тоже использует конвейер.


        1. pak-nikolai
          06.03.2016 19:22

          попробуйте, выбрал сервисы побезопаснее на своем компе, на 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*
          ''


          1. ApeCoder
            06.03.2016 19:28

            Да, был неправ, стартуют все


  1. artyfarty
    06.03.2016 18:47
    -1

    Пожалуйста остановитесь, на моей главной уже три подряд топика про PS, при том что я на него не подписан…


  1. padla2k
    08.03.2016 10:13
    +1

    Мне кажется в главе нет еще одного различия командлета и оператора (ведь книга Posh in depth основана на версии 3.0) — поддержка параллельных операций. Командлет их неумеет, а оператор может выполнять действия внутри операторных скобок параллельно в конструкции

    workflow {
      foreach -parallel ($one in $more) {
    
      }
    }


    1. pak-nikolai
      08.03.2016 12:13

      ну это же перевод. Параллельный операции в главах wrokflow


  1. vba
    08.03.2016 22:01

    Добрый день,

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

     "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

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


    1. pak-nikolai
      08.03.2016 22:37

      знак конвеера случайно не забыли?

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

      я правильно вас понял что вам нужно разбить строку по делимитерам и подать на foreach?


    1. FilimoniC
      08.03.2016 23:01

      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 $_}


      1. ApeCoder
        09.03.2016 11:01

        echo как правило не нужно, так как результат автоматически показывается с переводами строк, так что:

        [char[]]"abs"


    1. ApeCoder
      09.03.2016 11:03

      Кстати, перевод строки правильно "`n"


  1. mtp
    09.03.2016 00:59

    Спасибо за перевод.

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

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


    1. pak-nikolai
      09.03.2016 01:02

      да все тоже самое. просто в воркфлоу появляется параметр -parallel, тупо перебирает то что в скобках и стартует паралельно