Эта конструкция имеет те же цели что и командлет 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)
IamKarlson
06.03.2016 17:22Вы cерьезно?
ApeCoder
06.03.2016 18:30+1foreach ($service in (Get-Service –name B*)) { $service.pause() }
Благодаря тому что, в новых версиях появился синтаксис
ForEach-Object [-MemberName] <String> [-ArgumentList <Object[]>]
Пример можно сократить до
gsv b* | % pause
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()
ApeCoder
06.03.2016 19:13его нельзя так использовать, вторая конструкция выберет только первый попавшийся сервис на b* а не все. Я не очень понял — как вы избежите перебора и почему использовать вычисления в скобках лучше.
Мой пример тоже использует конвейер.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* ''
artyfarty
06.03.2016 18:47-1Пожалуйста остановитесь, на моей главной уже три подряд топика про PS, при том что я на него не подписан…
padla2k
08.03.2016 10:13+1Мне кажется в главе нет еще одного различия командлета и оператора (ведь книга Posh in depth основана на версии 3.0) — поддержка параллельных операций. Командлет их неумеет, а оператор может выполнять действия внутри операторных скобок параллельно в конструкции
workflow { foreach -parallel ($one in $more) { } }
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
Как бы все понятно в чем дело, но почему все именно так сделанно?pak-nikolai
08.03.2016 22:37знак конвеера случайно не забыли?
"abc".split("") | % { echo "$($_)\n"}
я правильно вас понял что вам нужно разбить строку по делимитерам и подать на foreach?
FilimoniC
08.03.2016 23:01- У вас пропущен символ конвейера ( | )
"abc".split("") | % { echo "$($_)\n"}
- В PoSh символ новой строки —
`r
или`n , а не \r\n
"abc".split("") | % { echo "$($_)r
n"}
` - В случае echo (он же Write-Output) вообще не требуется символ новой строки — он подразумевается и не отключается (в отличие от Write-Host)
"abc".split("") | % { echo "$($_)"} - В соответствии с MSDN, String::Split вам вернет всего одну строку в этом случае. Для разбора по символьно нужно использовать ToCharArray
"abc".ToCharArray() | % { echo $_}
ApeCoder
09.03.2016 11:01echo как правило не нужно, так как результат автоматически показывается с переводами строк, так что:
[char[]]"abs"
- У вас пропущен символ конвейера ( | )
mtp
09.03.2016 00:59Спасибо за перевод.
Вот из-за этой путаницы с двумя foreach мне всегда нравились конвеерные конструкции вида… | % {… }. Как, собственно, и в комментариях выше, привет, коллеги! :-)
Согласен с тем, что у форича есть плюс в плане параллельных конструкций, но, я если честно, их как-то через workflow привык делать, несмотря на его ограничения. Там не одно и то же внутри, нет?pak-nikolai
09.03.2016 01:02да все тоже самое. просто в воркфлоу появляется параметр -parallel, тупо перебирает то что в скобках и стартует паралельно
shpaker
Спасибо за информативную статью, было очень интересно, продолжайте ещё да побольше побольше...
pak-nikolai
прошу прощения, я оказывается нажал опубликовать в процессе написания.