Командлет ForEach-Object предназначен для выполнения указанных нами действий с каждым из элементов массива.
Этот массив может быть указан в качестве значения параметра -InputObject, однако в подавляющем большинстве случаев элементы передаются командлету ему по конвейеру.
Нужно сказать, что при помощи командлета ForEach-Object мы можем обрабатывать несколько элементов параллельно, а также обращаться к какому-либо свойству или методу поступающих по конвейеру объектов.
Однако же в данной статье мы остановимся на его классическом варианте использования — выполнение нескольких скриптблоков для каждого поступающего элемента.
Для обращения к обрабатываемому в данный момент объекту мы используем автоматические переменные $_ или $PSItem, являющиеся полностью равнозначными.
Process
В самом простом случае мы можем использовать командлет ForEach-Object следующим образом.
Get-Service | ForEach-Object -Process { if ($_.StartType -eq 'Automatic' -and $_.Status -eq 'Stopped') { Start-Service $_ } }
Или же мы можем воспользоваться переменной.
$ScriptBlock = { if ($_.StartType -eq 'Automatic' -and $_.Status -eq 'Stopped') { Start-Service $_ } } Get-Service | ForEach-Object -Process $ScriptBlock
Здесь в качестве значения параметра -Process мы указали скриптблок, который будет выполняться для каждого поступающего по конвейеру объекта службы.
Alias
Командлет ForEach-Object обладает двумя алиасами — foreach и %. То есть следующие команды будут полностью равнозначными.
Get-Service | ForEach-Object Name Get-Service | foreach Name Get-Service | % Name
Однако же вернемся к скриптблокам.
Begin и End
Кроме параметра -Process, мы можем задать параметры -Begin и -End. Каждый из них выполняется один раз, вне зависимости от количества поступающих элементов: -Begin — перед началом их обработки, а -End — после того, как все элементы уже были обработаны.
Оба эти параметра в качестве значения принимает скриптблок, однако их отличие от параметра -Process в том, что, во-первых, они принимают единственный скриптблок, когда как -Process может принимать массив скриптблоков, а во-вторых, скриптблоки, указанные в этих параметрах не могут обращаться к поступающим объектам при помощи автоматических переменных $_ или $PSItem.
В целом, параметр -Begin используется для подготовки среды к обработке элементов, а -End — для неких завершающих действий.
Get-Process | ForEach-Object -Begin { [long]$WorkingSet = 0 } -Process { $WorkingSet += $_.WS } -End { $WorkingSet }
Кроме параметров -Begin, -Process и -End, существует еще один параметр: -RemainingScripts. Он, как и -Process, принимает массив скриптблоков и, в сущности, при выполнении командлета ForEach-Object они добавляются к массиву скриптбоков параметра -Process. Для чего он нужен мы поговорим ниже.
Positional parameters
Благодаря тому, что PowerShell поддерживает использование позиционных параметров, во многих командлетах мы можем пропустить имена некоторых параметров и указать только их значения.
То есть, мы можем указать командлет ForEach-Object следующим образом.
"Value" | ForEach-Object {"1: $_"} {"2: $_"} {"3: $_"}
Если мы запустим командлет Trace-Command для компонента ParameterBinding
Trace-Command -Name ParameterBinding -Expression { "Value" | ForEach-Object {"1: $_"} {"2: $_"} {"3: $_"} } -PSHost
то увидим, что первый аргумент — {«1: $_»} — был назначен параметру -Process, а все остальные — уже упомянутому нами параметру -RemainingScripts.
Происходит так потому, что в коде в определении параметра -RemainingScripts указан атрибут ValueFromRemainingArguments со значением true, и это указывает, что все не назначенные другим параметрам аргументы должны быть сопоставлены ему.
Если же мы запустим изначальную команду на выполнение то получим следующий результат.
1: 2: Value 3:
Почему так получилось?
Как мы уже говорили выше, в коде командлета ForEach-Object значения параметра -RemainingScripts добавляются в общий массив скриптблоков, после тех, что были указаны в параметре -Process. Далее PowerShell смотрит, указаны ли явным образом параметры -Begin и -End. Если нет, то первый скриптблок из массива становится начальным скриптблоком, как если бы он был указан в параметре -Begin, а последний скриптблок становится завершающим, таким, как можно было бы указать в параметре -End.
И если мы передадим командлету массив из двух элементов, мы увидим это более явно.
"Value1", "Value2" | ForEach-Object {"1: $_"} {"2: $_"} {"3: $_"}
1: 2: Value1 2: Value2 3:
Интересно, что если скриптблоков указано два, то первый из них будет начальным, а второй будет выполняться для каждого элемента.
"Value1", "Value2" | ForEach-Object {"1: $_"} {"2: $_"}
1: 2: Value1 2: Value2
Если же скриптблоков больше двух, то, как мы видели, первый и последний из них становятся начальным и завершающим, соответственно.
"Value1", "Value2" | ForEach-Object {"1: $_"} {"2: $_"} {"3: $_"} {"4: $_"}
1: 2: Value1 3: Value1 2: Value2 3: Value2 4:
И снова Begin и End
Для того, чтобы все указанные нами скриптблоки выполнялись для поступающих элементов, мы можем явным образом указать значения параметров -Begin и -End. Например, так.
"Value" | ForEach-Object {"1: $_"} {"2: $_"} {"3: $_"} -Begin {} -End {}
Или же так.
"Value" | ForEach-Object {"1: $_"} {"2: $_"} {"3: $_"} -Begin $null -End $null
В обоих случаях результат будет следующим.
1: Value 2: Value 3: Value
Кроме того, мы можем добавить пустые скриптблоки в качестве первого и последнего аргумента.
"Value" | ForEach-Object {} {"1: $_"} {"2: $_"} {"3: $_"} {}
Принимая во внимание все вышесказанное, мы можем представить использованную нами в начале статьи команду следующим образом.
Get-Process | ForEach-Object { [long]$WorkingSet = 0 } { $WorkingSet += $_.WS } { $WorkingSet }
Return
Командлет ForEach-Object — весьма важный элемент PowerShell. Он поддерживает несколько вариантов использования (как уже говорилось, мы рассмотрели не все из них), а также два вида синтаксиса. Однако его изящность и способность скрывать свою внутреннюю сложность, предоставляя в большинстве случаев достаточно простые решения различных задач, делает его невероятно удобным в использовании.