Как использовать циклы While и Foreach в Powershell Foreach на примерах


22 октября 2019


Как работать с циклами в Powershell ForEach-Object и While на примерах

Основное предназначение циклов в Powershell, так же как и в других языках, выполнение итераций. Проще говоря они помогают выполнять одну и ту же инструкцию для разных значений. Отличительной чертой циклов в Powershell является их количество, всего их 7:

  1. Foreach-Object - команда;
  2. Foreach - выражение;
  3. Foreach() - метод;
  4. For - цикл;
  5. While - цикл;
  6. Do-While - цикл;
  7. Do-Until - цикл.

Каждый из них по своему выполняет итерации. Само понятие итераций обозначает повторное выполнение действий, например команд. Иногда можно услышать слова "итерируемый объект" или "итератор", которые обозначают объект проходящий через цикл.

Foreach-Object

Foreach-Object относится к командам, а не циклам. Чаще всего мы с ним работаем через конвейер.

Итерации

Так же, как и во многих других командах Powershell у Foreach-Object есть параметр InputObject, через который помещается объект для перебора. На примере ниже этот объект в виде массива с числами:

ForEach-Object -InputObject @(1,2,3,4) -Process {$PSItem}

Цикл ForEach-Object Powershell

$PSitem это переменная, которая хранит текущее значение массива. Мы ее выводим через параметр Process, в котором можно дополнить логику командами или условиями. Переменная $PSitem аналогична такому написанию $_ .

Если вы имеете опыт работы с Powershell, то чаще использовали с конвейером:

Get-Service | ForEach-Object {$PSitem.Name}

пример цикла ForEach-Object Powershell

По сути в команде выше происходит то же самое, просто в функциях и командах есть возможность установить параметр, который принимает значения по умолчанию из конвейера. Я так же добавил вывод только имени, так как у объекта из Get-Service есть такое свойство. Более подробно это описывалось статьей по созданию команд и функций в Powershell. Параметры, которые принимают значения из конвейера можно увидеть так:

Get-Help ForEach-Object -Parameter *

Цикл ForEach-Object Powershell конвейер

Когда вам нужно использовать больше логики зажимайте shift + enter для перехода на новую строчку, если работаете из консоли. Скрипт ниже получает список процессов, передает их через конвейер, где через наш командлет происходит анализ время отклика CPU с выводом результата:

Get-Process | ForEach-Object -Process {
    if ($PSItem.CPU -ge 100){Write-Host 'Процесс плохо работает' $PSItem.Name}
    else {Write-Host 'Нормально работает' $PSItem.Name}
}

команда с конвейером и циклом ForEach-Object Powershell

Можно использовать и другой синтаксис. Так я получу имена сервисов:

Get-Service | ForEach-Object -MemberName Name

Цикл ForEach-Object Powershell

Я не видел, что бы кто-то использовал такой синтаксис, но он возможен. На примере ниже я преобразую строки в массив двумя способами:

"Первый,второй,третий" | ForEach-Object -MemberName Split -ArgumentList ","

"Первый,второй,третий" | ForEach-Object {$PSItem.split(",")}

Цикл ForEach-Object Powershell преобразование в массив

Split - это метод, который преобразует строку в массив используя указанный разделитель (в нашем случае запятая). Методы и свойства, доступные у объекта, можно увидеть через Get-Member:

"первый" | Get-Member

Методы Powershell

Сохранение через переменные

Результат этого командлета, так же как и любого другого можно сохранить в переменную и использовать дальше:

$result = ForEach-Object -InputObject @('localhost','127.0.0.1') -Process {Get-Service -ComputerName $PSItem}
$result

Цикл ForEach-Object Powershell сохранение в переменную

Алиасы

У этой команды есть алиасы foreach и знак %:

% -InputObject @(1,2,3) -Process {$PSItem}
@(1,2,3) | % {$PSItem}
@(1,2,3) | foreach {$PSItem}

Цикл ForEach-Object Powershell алиасы

Обратите внимание, что использование следующего синтаксиса приведет к ошибке:

foreach -InputObject @(1,2,3) -Process ($PSItem)

Связано это с тем, что в Powershell работает командлет и метод с одним именем. Каждый из них имеет свой синтаксис и вызывая foreach без конвейера мы используем метод. Он будет разобран следующим.

 

ForEach

Этот цикл, в отличие от предыдущего командлета, не будет работать через конвейер. Если вы попытаетесь использовать этот метод через конвейер, то будет использован командлет.

Итерации

Это самый простой цикл Powershell, который переходит от одного значения объекта к другому. Синтаксис следующий:

ForEach ($item in $array){
    Scriptblock
}
  • $item - хранит текущий элемент из $array. Эту переменную можно вызвать в ScriptBlock;
  • $array - это любая коллекция, из которой нужно получить значения;
  • Scriptblock - это область для написания сценария и остальной логики.

Для примера с foreach в Powershell выполним перебор значений массива:

$array = @(1,2,3,4,5) 
foreach ($item in $array){
    $item
}

Цикл ForEach Powershell с массивом

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

$array = @(1,2,3,4,5) 
foreach ($item in $array){
    $sum = $item + 10
    Write-Host "Это сумма двух чисел: " $sum
}

Цикл ForEach Powershell с массивом

Выше было написано, что мы не можем использовать конвейер, но исключение такое написание:

$(ForEach ($number in 4,5,6 ) { $number * 13}) | Write-Warning

Цикл ForEach Powershell сохранение в переменную

Как вы можете догадаться - это не работа цикла с конвейером, а просто передача массива.

Использование с командами

Как уже писалось выше не стоит использовать метод Powershell foreach через конвейер. Например так мы можем получить список PSProvider:

$psproviders = Get-PSProvider
foreach ($psprovider in $psproviders){$psprovider.Name}

Цикл ForEach Powershell с командами

Можно добавлять выражение в сам цикл, но такой подход как минимум понизит читаемость кода. Самое главное экранировать выражение круглыми скобками:

foreach ($psprovider in (Get-PSProvider | where Name -Like *Reg*)){
    $psprovider.Name
}

ForEach Цикл Powershell с командлетами

На примере ниже пример использования цикла и командлета с одни и тем же результатом:

$services = Get-Service

# командлет
$services | ForEach-Object -Process {
    if ($PSItem.Name -eq 'WinRM'){
        Write-Host "Статус сервиса WinRM" $PSItem.Status
        }
}

# цикл
foreach ($service in $services){
    if ($service.Name -eq 'WinRM'){
        Write-Host "Статус сервиса WinRM" $service.Status
        }
}

ForEach Цикл Powershell сравнение с командой

Как видно в случае с командой мы оперируем переменной $PSItem, а с циклом $service, так как мы ее определили еще в начале.

Работа с диапазоном или range

В Powershell можно легко указать диапазон численных значений. Я могу создать массив из чисел с 1 до 10 так:

1..10

ForEach Цикл Powershell с диапазоном

Так же можно использовать и в итерациях:

foreach ($i in 1..10){$i}

Continue и break

Одним из отличий работы с foreach от аналогичной команды является возможность использования contnue и break. 

Оператор continue минует выполнение оставшейся части скрипта после своего выполнения. На примере ниже есть два массива и если в одном из них будут встречаться одинаковые числа, то они не будут выведены:

$Numbers = 4..7
foreach ($Num in 1..10) { 
    if ($Numbers -Contains $Num) { 
        continue 
    } 
    $Num
}

 ForEach Цикл Powershell continue и break

В отличие от continue break останавливает итерации полностью. Его удобно использовать, когда мы ищем нужное значение и нам нужно остановить итерации после его нахождения:

$words = 'Pass1','Pass2','Stop','Pass3'
foreach ($word in $words) { 
    if ($word -eq 'Stop') { 
        break
    } 
    $word
}

ForEach Цикл Powershell с массивом

Вложенные

Когда у нас есть массив массивов может потребоваться использовать вложенные циклы Powershell. Само их использование не должно вызывать труда, но если вы попробуете использовать операторы break и continue это сработает иначе:

$array_list = @(
            @('ok1','Stop'),
            @('ok2', 'ok3')
            )

foreach ($array in $array_list){
    foreach ($word in $array){
        if ($word -eq 'Stop'){Break}
        $word
    }
}

ForEach вложенный цикл Powershell

Как вы видите на примере выше у нас остановился только внутренний цикл. Если нужно избежать таких ситуаций используйте OUTER:

$array_list = @(
            @('ok1','Stop'),
            @('ok2', 'ok3')
            )

:outer foreach ($array in $array_list){
    foreach ($word in $array){
        if ($word -eq 'Stop'){Break outer}
        $word
    }
}

ForEach вложенный цикл Powershel

Переменные

В этом типе цикла доступны три специальных переменных:

  • $foreach.MoveNext() - переход к следующему элементу;
  • $foreach.current - текущий элемент;
  • $foreach.reset() - обнуляет итерацию. Перебор начнется заново, что приведет к бесконечному циклу.

Такие возможности есть во многих языках программирования, но мне тяжело вспомнить ситуации что бы я их использовал. Для примера ниже мы используем массив чисел с 1 по 10. Самое первое число 1 будет выведено, после чего будет пропущено следующее:

foreach ($Int in 1..10) {
    $Int
    $foreach.MoveNext() | Out-Null
}

ForEach цикл Powershell movenext

$current просто выведет ту же переменную, объявленную в цикле:

foreach ($services in (Get-Service -Name *WinR*)) {
    $foreach.current
    $foreach.MoveNext() | Out-Null
}

ForEach цикл Powershell current

 

Сравнение ForEach и команды ForEach-Object

Foreach ForEach-Object
Загружает все элементы коллекции Загружает только один элемент в память через конвейер
Использует больше памяти из-за полной загрузки массивов Меньшее использование памяти из-за одного элемента
С небольшим объемом массивов работает быстрее Работает медленнее
Нельзя использовать через конвейер. Это не приведет к ошибке, но будет использован алиас командлета Можно использовать конвейер или параметр InputObject
Поддерживаются методы break и continue Нельзя прервать используя методы contiinue и break

Скорость работы обоих этих методов можно увидеть через следующие скрипты:

1..10 | Measure-Command -Expression {
        Get-WMIObject Win32_LogicalDisk | ForEach-Object {
                        [math]::Round($_.FreeSpace/1GB,2)
                        }
        }

1..10 | Measure-command -Expression {
        $disks = Get-WMIObject Win32_LogicalDisk 
        foreach ($disk in $disks ){
                 [math]::Round($disk.FreeSpace/1GB,2)
                 }
}

сравнение Foreach-Object и foreach в Powershell

 

Метод foreach()

В версии Powershell 4.0 появился метод foreach() для поддержки DSC.  Он немного отличается синтаксисом и подходом от описаны выше. Более простой способ понять, как он работает это посмотреть на его синтаксис:

$collection = @(1,2,3,4,5)
$collection.Foreach(
    {$PSItem}
)

метод Powershell Foreach

Как я прочитал этот метод предназначен для работы только с коллекциями и по идеи такой способ должен привести к ошибке:

$chislo = 1
$chislo.Foreach(
    {$PSItem + 1}
)

метод Powershell Foreach

На всякий случай я бы советовал преобразовывать такие данные в массив:

@($chislo).Foreach(
    {$PSItem + 1}
)

Работа с командами

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

(Get-Service).Foreach(
    {if ($PSItem.Status -eq 'Stopped'){
                Write-Host 'Этот сервис остановлен' $PSItem.Name
                }
    }
)

метод Powershell Foreach пример с командой

Самое главное экранировать результат команды в скобки или выполнять метод для переменной, которая уже хранит значения.

Еще один пример с сервисами:

$services = @('WinR*')
$services.Foreach({
    Get-Service $PSItem
    }
)

метод Powershell Foreach массив

 

for

В Powershell есть еще один способ итераций через for. Его отличие в том, что мы можем изменять основной объект до выполнения ScriptBlock. Синтаксис следующий:

for (объект; условие; действие){
    ScriptBlock
}
    

Для примера получим числа с 1 по 10:

for ($i=1; $i -le 10; $i++){
    Write-Host $i
}

цикл Powershell For

Поясню момент, который мог быть вызван написанием $i++, все следующие действия одинаковы, но не все сработают в этом цикле:

$i = 1
$i++
$i
$b = 1
$b += $b
$b
$c = 1
$c = $c + $c
$c

Powershell виды вычислений

Вы можете изменять несколько объектов:

$j = 10
for ($i=1; $i -le 10; $i++,$j--){
    Write-Host $i $j
}

цикл Powershell For изменение

В итерациях вы можете использовать любой тип данных, не только цифры:

for($s='' ;$s.length -le 10;$s=$s+'a'){
    $s
}

цикл Powershell For пример

Вы можете пропустить любую часть этого цикла, но если ничего не будет указано получится бесконечный цикл:

For(  ;  ;  ) { 
    "Вечный цикл, нажмите ctrl+c для отмены"
}

бесконечный цикл Powershell For

Возможно использовать этот цикл с командлетами, но пример высосан из пальца:

for (
    $check_site = Invoke-WebRequest -Uri "https://fixmypc.ru";
    $check_site.StatusCode -eq 200;
    ){
    Write-Warning 'Коричневый код. Предпринимаем действия'
    #Тут какие-то команды
    break
 }

цикл Powershell For с командой

Операторы break и continue работают так же.

 

While

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

$a = 0
While ($a -le 10){
    $a
    $a += 1
}

цикл While в Powershell

Пример выше работает до тех пор, пока переменная $a меньше или равна 10 или, другими словами, пока значение в скобках не станет True.

Более понятный пример это с утилитой ping. Когда мы потеряли доступ к интернету или упал сервер, то мы запускаем команду с ключом -t и она работает до тех пор пока мы не остановим этот процесс руками нажав Ctrl+c. Такая работа в Powershell относится к бесконечным циклам и на примере выглядит так:

While ($True){
    ping fixmypc.ru
    # sleep это задержка между запросами
    sleep 3
    # Для остановки нажмите ctrl+c
}

While в Powershell бесконечный цикл

Все эти операции можно выполнить и с foreach, но это плохая практика. Например так можно реализовать аналогичный вечный цикл с for:

foreach ($i in @(1,2)){
    ping fixmypc.ru
    sleep 3
    $foreach.Reset()
}

for в Powershell бесконечный цикл

Break и continue

Ключевой момент работы с while это использование break, который остановит итерации. Мы пинговали сайт, но останавливали итерации руками и что бы этого не делать в дальнейшем нужно добавить условие при котором будет выполнен break:

while ($True){
    $result = ping fixmypc.ru
    $result
    if ($result -like '*TTL*'){
            Write-Host "Сайт заработал"
            break
            }
}

break и continue в Powershell

Очень важно хорошо продумывать логику остановки скрипта. Вечные циклы, которые могут работать в полноценных сриптах, программах и планировщиках будет очень тяжело отлавливать.

Такие итерации останавливают либо методом break, либо счетчиком, который был показан в первом примере и ниже. Если мы будем использовать счетчик и continue, который пропускает выполнения условия, то тоже можем получить вечный цикл:

$i = 0
while ($i -le 5){
    if ($i -in @(2,3)){
        Continue}
    $i
    $i+=1
}

Я не помню, что бы я хоть раз использовал continue в реальных задачах, но стоит помнить о существовании такой возможности.

Командлеты

Мы можем выполнять команду в условиях и объявлять в ней переменную для дальнейшего использования:

while ($service = Get-Service -Name *WinRm*){
    if ($service.Status -eq 'Stopped'){
            Write-Host "Сервис остановлен"
            Write-Host "Приостановление работы"
            break
            }
    else {
            Write-Host "Сервис запущен"
            Write-Host "Приостановление работы"
            break
        }
}

While в Powershell на примере с командами

 

Do и While

Отличие работы этого цикла от предыдущего в том, что он проверяет условия после выполнения операций, а не до. Из-за этого, если условие изначально равно $False, цикл будет выполнен один раз:

$i = 1 
Do { 
    "Итерация i=$i" 
    $i++
} 
While($i -le 10)

Do и While в Powershell

Или поставим заведомо ложные условия при котором предыдущий цикл не стал бы работать:

Do { 
    "Итерация i=$i" 
    $i++
} 
While($false)

Do и While в Powershell пример

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

$process = $False
Do {
    $process = Read-Host "1. Включить сервер`n2. Выключить сервер`n3. Проверить логи`n4. Сделать что-то еще`nВыберете номер [1-4]"
    switch($process){
        1 {'Сервер включен'}
        2 {'Сервер выключен'}
        3 {'Проверено'}
        4 {'Сделано вчера'}
        Default {
            Write-Host "Неверный выбор"
            $process = $False
            }
    }
}
While ($process -eq $false)

пример Do и While в Powershell

Если вам интересны или вы не понимаете switch, то я бы советовал почитать "Как работать с Powershell Switch на примерах".

 

Do Until

Предыдущий цикл выполняет итерации до тех пор, пока главное условие равняется $True. Цикл Do Until имеет такой же синтаксис, но работает до тех пор пока условие не станет $True, то есть пока оно $False:

$process = $False
$i = 0
Do {
    Write-Host "Итерация номер: " $i
    $i++
    if ($i -eq 10){$process = $True}
}
Until($process)

Do и Until в Powershell пример

...

Теги: #powershell


Популярные тэги
О блоге
Этот блог представляет собой конспекты выученного материала, преобретенного опыта и лучшие практики в системном администрировании и программировании.