Объяснение цикла For, ForEach и Do While/Until в PowerShell
Одной из самых основных функций в программировании, помимо «Если, иначе», являются циклы. Они позволяют обрабатывать данные или выполнять функцию до тех пор, пока не будет достигнуто определенное условие. В PowerShell мы можем использовать различные типы циклов: Цикл For, ForEach, While, Do While и Do Until.
Знание того, когда использовать что, важно для написания лучших сценариев PowerShell. Особенно разница между тремя циклами while иногда трудно воспринимается.
В этой статье мы рассмотрим различные функции циклов, которые мы можем использовать в PowerShell, как их использовать и в чем их отличие.
Цикл For в PowerShell
Цикл For в PowerShell — это, по сути, самый основной цикл, который вы можете использовать. Он выполняется заданное количество раз в части скрипта, пока не будет выполнено условие.
Чтобы использовать цикл For в PowerShell, вам нужно указать счетчик $i, условие $i -lt 5 (выполняйтесь, пока $i меньше 5) и увеличивать счетчик.
For ($i = 0; $i -lt 5; $i++) {
Write-host $i
}
Увеличение счётчика также может быть выполнено внутри скрипта. Это позволяет увеличивать счётчик только тогда, когда в вашем скрипте выполнено определённое условие.
For ($i = 0; $i -lt 5) {
Write-Host $i;
$i++
}Вы также можете перебирать массив с помощью простого цикла For в PowerShell. Мы можем использовать длину массива в качестве условия и перебирать массив до тех пор, пока не обработаем все элементы в массиве.
Использовать цикл foreach для обработки массива проще, чем цикл for, об этом позже.
$fruit = @('apple','pear','banana','lemon','lime','mango')
-le means less or equal to
For ($i = 0; $i -le $fruit.length; $i++) {
Write-Host $fruit[$i];
}PowerShell ДляКаждого 1..10
PowerShell также имеет сокращённый синтаксис для цикла. Вы можете задать диапазон, например, 1..10, а затем перебрать каждое число в этом диапазоне с помощью оператора ForEach. Это позволяет вам указать, сколько раз вы хотите запустить скрипт, и просто передать скрипт через конвейер.
$path = "C:\temp"
Create a new file ForEach 1..10 number
1..10 | % {
$newFile = "$path\test_file_" + $_ + ".txt";
New-Item $newFile
}Цикл Foreach и ForEach-Object в PowerShell
Чтобы обработать каждый элемент в массиве или коллекции, вы можете использовать функции ForEach в PowerShell.
В PowerShell есть два типа функций foreach: foreach и ForEach-Object. Разница между ними заключается в том, что foreach загружает всю коллекцию в память перед началом обработки данных, в то время как ForEach-Object обрабатывает данные по одному. Это делает ForEach-Object идеальным для использования в конвейере с другой функцией.
У ForEach-Object есть два сокращения, которые вы можете использовать: ForEach и %. Место, где вы используете ForEach, определяет, какая из двух версий используется.
$fruits = @('apple','pear','banana','lemon','lime','mango')
Foreach - Loads all the fruits in the memory and process it
Takes 21,4553 miliseconds
Foreach ($fruit in $fruits) {
Write-Host $fruit;
}
#Shorthand for foreach
Takes 14,3926 miliseconds
$fruits.foreach( {
Write-Host $_;
})
ForEach-Object
Takes 7,8812 miliseconds
$fruits | ForEach-Object {Write-Host $_}
Shorthand for ForEach-Object
Takes 7.3507 miliseconds
$fruits | ForEach {Write-Host $_}
Also a Shorthand for ForEach-Object
Takes 7,2982 miliseconds
$fruits | % {Write-Host $_}Как вы можете видеть, в этом примере foreach немного медленнее, но причина этого в том, что нужно немного времени, чтобы загрузить коллекцию в память.
Если мы возьмем большой набор данных, например, с 300000 записями, то мы можем увидеть, что foreach может быть гораздо быстрее, чем ForEach-Object. Единственное, потребление памяти будет немного выше у foreach.
$items = 0..300000
Takes 180 ms to complete
(Measure-Command { foreach ($i in $items) { $i } }).TotalMilliseconds
Takes 1300 ms to complete
(Measure-Command { $items | ForEach-Object { $_ } }).TotalMilliseconds
Takes 850 ms to complete
(Measure-Command { $items.ForEach({ $i })}).TotalMillisecondsКакую функцию использовать, действительно зависит от вашего набора данных. Если вам нужно обрабатывать большое количество данных, то ForEach-Object будет лучшим выбором, чтобы избежать проблем с памятью. Малые наборы данных можно отлично обрабатывать с помощью ForEach.
Еще одно преимущество ForEach-Object заключается в том, что вы можете передать за ним другой cmdlet, например, экспортировать в CSV.
Блоки сценариев ForEach-Object
ForEach-Object позволяет использовать блоки скриптов, что может быть действительно полезно, когда вам нужно зарегистрировать процесс или очистить после завершения скрипта.
$path = "C:\temp"
$totalSize = 0
Get-ChildItem $path | ForEach-Object -Begin {
Write-Host "Processing folder $path"
} -Process {
$totalSize += ($_.length / 1kb)
} -End {
Write-host $totalSize "Kb"
}Цикл Do While в PowerShell
Цикл Do While в PowerShell используется для выполнения скрипта пока условие истинно или выполнено. В PowerShell также есть While-цикл, без части Do. Они по сути одинаковы, оба выполняются до тех пор, пока условие не станет истинным.
Если вы запустите скрипты ниже, вы увидите, что они оба создают 10 тестовых файлов в временной папке. Оба работают до тех пор, пока $i меньше 10.
$i = 0;
$path = "C:\temp"
Do While loop
Do {
# Do Something
$newFile = "$path\dowhile_test_file_" + $i + ".txt";
New-Item $newFile
$i++;
}
While ($i -lt 10)
While loop
$i = 0;
While ($i -lt 10) {
# Do Something
$newFile = "$path\while_test_file_" + $i + ".txt";
New-Item $newFile
$i++;
}Но есть одна большая разница между двумя. Цикл Do While всегда выполнится хотя бы один раз, независимо от условия, которое вы используете. Цикл while будет выполняться только тогда, когда условие выполнено.
Рассмотрим следующий пример: мы хотим запустить скрипт, пока $i меньше 10. Но мы установили счетчик на 15 для начала. Как вы увидите при запуске скрипта, оператор Do While выполняется один раз, а оператор While вовсе не выполняется.
$i = 15;
Do {
Write-host "Do While $i is less then 15";
$i++;
}
While ($i -lt 10)
$i = 15;
while ($i -lt 10)
{
Write-host "While $i is less then 15";
$i++;
}Вы часто используете цикл do-while в PowerShell, когда ожидаете выполнения условия, например, пока процесс не завершится или, как в примере ниже, пока интернет-соединение не будет отключено.
PowerShell не перейдёт к строке Write-Host, пока условие в цикле не будет выполнено. В этом примере мы тестируем интернет-соединение с помощью Test-Connection. Он вернёт true, когда у нас есть соединение, и иначе вернёт false.
Do {
Write-Host "Online"
Start-Sleep 5
}
While (Test-Connection -ComputerName 8.8.8.8 -Quiet -Count 1)
Write-Host "Offline"Создание цикла сна в PowerShell
Если вы хотите создать цикл, который будет проверять, вернулась ли служба в онлайн, или для создания файла, то лучшим вариантом будет использование запрета внутри вашего цикла.
Без части со сном скрипт будет выполняться с максимальной скоростью, что может составлять несколько тысяч итераций в секунду или более.
С помощью команды Start-Sleep мы можем сделать небольшую паузу перед следующей итерацией:
Do {
Write-Host "Online"
Start-Sleep 5
}
While (Test-Connection -ComputerName 8.8.8.8 -Quiet -Count 1)
Write-Host "Offline"В приведенном примере мы ждем 5 секунд, прежде чем снова протестировать соединение с 8.8.8.8. Вы также можете использовать миллисекунды вместо секунд в команде start-sleep.
Start-Sleep -Miliseconds 50Цикл Do Until в PowerShell
Цикл Do Until по своей сути похож на цикл Do While в PowerShell. Единственное отличие заключается в условии, при котором они выполняются.
- Цикл Do While продолжает выполняться до тех пор, пока условие истинно. Когда условие становится ложным, он остановится.
- Цикл Do Until продолжает выполняться до тех пор, пока условие является ложным. Когда условие становится истинным, он останавливается.
Do {
Write-Host "Computer offline"
Start-Sleep 5
}
Until (Test-Connection -ComputerName 'lab01-srv' -Quiet -Count 1)
Write-Host "Computer online"Когда использовать 'While' или 'Until', действительно зависит от того, что вы хотите сделать. В принципе, можно сказать, что если условие выхода должно быть истинным, то используйте 'Until'. Если условие выхода отрицательное (ложное), то следует использовать 'While'.
Остановка цикла Do While/Until
В PowerShell мы можем использовать Break , чтобы остановить цикл преждевременно. Лучше ограничить использование break в ваших циклах, так как они делают ваш код более трудным для чтения. Если вы хорошо структурировали свой код, то вам это действительно не нужно.
$i=0;
Do {
Write-Host "Computer offline"
Start-Sleep 1
$i++;
if ($i -eq 2) {
Break
}
}
Until (Test-Connection -ComputerName 'test' -Quiet -Count 1)Использование Continue в цикле PowerShell
Еще один полезный метод, который вы можете использовать внутри цикла do-while/until в PowerShell, это Continue. С помощью Continue вы можете остановить выполнение скрипта внутри тела Do и перейти к следующей итерации цикла.
Это может быть полезно, если у вас есть большой скрипт внутри блока Do и вы хотите перейти к следующему элементу, когда выполнено определенное условие.
$servers = @('srv-lab01','srv-lab02','srv-lab03')
Foreach ($server in $servers) {
if ((Test-Connection -ComputerName $server -Quiet -Count 1) -eq $false) {
Write-warning "server $server offline"
Continue
}
# Do stuff when server is online
}Примеры циклов PowerShell
Ниже вы найдете несколько примеров циклов, которые могут помочь вам создать свои собственные циклы.
Бесконечный цикл PowerShell
В PowerShell вы можете создать бесконечный цикл с помощью функции While. While будет продолжать выполняться, пока условие истинно. Поэтому, чтобы создать бесконечный цикл, мы можем просто сделать следующее:
While ($true) {
# Do stuff forever
Write-Host 'looping'
# Use Break to exit the loop
If ($condition -eq $true) {
Break
}
}Чтобы выйти из бесконечного цикла, вы можете использовать либо Ctrl + C, либо на основе условия с помощью Break.
Цикл PowerShell по файлам
Чтобы пройтись по файлам, мы будем использовать функцию foreach-object. Файлы являются объектами, которые мы можем получить с помощью команды get-childitem, поэтому мы можем использовать конвейер для передачи результата в foreach-object. Еще одна причина использовать ForEach-Object вместо foreach заключается в том, что мы заранее не знаем, сколько файлов будем обрабатывать.
Цикл foreach загрузит все данные в память, чего мы не хотим с неизвестным набором данных.
$path = "C:\temp"
$csvItems = 0
Get-ChildItem $path | ForEach-Object {
$_.Name;
If ($_.Extension -eq '.csv') {
$csvItems++;
}
}
Write-host "Total CSV Files are $csvItems";Если вы хотите включить подпапку, вы можете использовать опцию -recurse. Используйте -filter, чтобы получить только файлы .csv, например.
Get-ChildItem $path -recurse -filter *.csvПеребирать текстовый файл
В PowerShell вы можете проходить по текстовому файлу построчно. Это может быть действительно полезно, если вы хотите автоматически считывать файлы журналов, например.
Пример ниже хорошо работает с небольшими файлами, но проблема в том, что Get-Content считывает весь файл в память.
$lineNr = 0
foreach($line in Get-Content c:\temp\import-log.txt) {
if($line -match 'warning'){
# Work here
Write-warning "Warning found on line $lineNr :"
Write-warning $line
}
$lineNr++;
}Для чтения и обработки больших файлов с помощью PowerShell вы также можете использовать .Net файл-ридер:
foreach($line in [System.IO.File]::ReadLines("c:\temp\import-log.txt"))
{
$line
}Завершение
Надеюсь, пример помог в создании ваших циклов PowerShell For, Foreach-Object, Do While и Until. Если у вас есть вопросы, просто оставьте комментарий ниже.