Логические операторы и управляющие инструкции в Windows PowerShell, часть 2. Цикл powershell


Логические операторы и управляющие инструкции в PowerShell, часть 1

Как и в любом языке программирования, в Windows PowerShell есть возможность проверки условий на истинность. Это позволяет выбирать между различными действиями, в зависимости от условий.

За проверку истинности/ложности в языке отвечают логические операторы, а за действия в том или ином случае управляющие инструкции.

Логических операторов в PowerShell, в общем-то, не так уж и много.

Оператор Значение Пример использования (возвращается значение $True)
-and И (1 -eq 1) -and (2 -eq 2)
-or ИЛИ (1 -ne 2) -or (1 -eq 2)
-not НЕ -not (1 -gt 2)
! НЕ !(1 -gt)

В примерах использованы операторы сравнения. Если Вы их подзабыли, можно подсмотреть их здесь.

Что касается управляющих инструкций, то это уже более широкая тема, на которой нужно остановиться подробнее.

Инструкция If … ElseIf … Else

If и Else знакомы всем, кто имеет хотя бы поверхностное понятие о языках программирования. В Windows PowerShell при помощи If можно выполнить код только в том случае, если условие возвращает значение $True. В противном случае будет выполняться код после Else. Можно поэтапно проверять несколько условий при помощи ElseIf.

В общем виде всё это выглядит так:

If (условие1)

   {блок_кода1}

[ElseIf (условие2)

   {блок_кода2}]

[Else

   {блок_кода3}]

Сначала проверяется условие1, если оно верно (значение $True), то выполняется блок_кода1. Если условие1 ложно (значение $False), то проверяется условие2. Его истинность приведет к исполнению блока_кода2, а ложность — блока_кода3.

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

Посмотрим, как это работает. Запишем код следующего содержания:

$a=3 If ($a -eq 5) { 'a равно 5' } Else {'a не равно 5'}

Как видим, PowerShell сразу же вывел необходимое сообщение. Кроме того, этот пример показывает, что Windows PowerShell способен выполнять инструкции, состоящие из нескольких строк.

Цикл While

Перейдем к циклам. Цикл While является самым простым из доступных в PowerShell. В нем цикл будет выполняться до тех пор, пока условие имеет значение $True. В общем виде While выглядит так:

While (условие) {блок_команд}

Приведем пример такого цикла.

While ($b -ne 3) { $b++ $b } Цикл While

Думаю, здесь всё интуитивно понятно. При каждом наше переменная $b увеличивается на 1 до тех пор, пока не станет равной 3.

Цикл Do … While

В целом, это почти то же самое, но с одним отличием — условие проверяется не до блока команд, а после.

Do {блок_команд} While (условие)

$c=5 Do {$c++; $c} While ($c -ne 10) Цикл Do … While

Цикл For

Данный цикл является циклом со счетчиком. Такой цикл обычно применяется для выполнения определенных действий с каждым из элементов какого-либо массива. В общем виде цикл For выглядит так:

For (инициация; условие; повторение) {блок_команд}

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

условие — часть инструкции, которая может принимать значение $True или $False. Проверка условия проводится при каждой итерации цикла. При значении $True выполняется блок_команд.

повторение — одна или несколько разделяемых запятыми команд, чаще всего используемых для изменения переменной, проверяемой внутри условия. Эти команды выполняются после блока_команд и перед новой проверкой условия.

блок_команд — одна или несколько команды, выполняемых, когда значение условия равно $True.

Собственно, вот простой пример.

For ($d=7; $d -lt 12; $d++) {$d} Цикл For

Пока значение переменной $d будет меньше 12, цикл For будет увеличивать её значение на 1 и выводить нам на экран.

В следующей части статьи продолжим разбирать управляющие инструкции в виде циклов.

Продолжение следует…

webistore.ru

Логические операторы и управляющие инструкции в PowerShell, часть 2

В прошлой части мы разобрались с логическими операторами и начали описывать управляющие инструкции в Windows PowerShell. Продолжим перечисление.

Цикл ForEach

ForEach позволяет последовательно перебирать элементы коллекции, роль которой обычно исполняет массив. Особенностью цикла ForEach является то, что его синтаксис зависит от того, где расположена инструкция — внутри конвейера команд или вне его.

ForEach вне конвейера команд

В этом случае цикл выглядит следующим образом:

ForEach ($элемент in $коллекция) {блок_команд}

В круглых скобках указывается коллекция. При выполнении цикла PowerShell автоматически создает переменную $элемент. Перед каждой итерацией в цикле этой переменной присваивается очередного элемента в коллекции. На каждом из элементов выполняется блок_команд.

Например, следующий код берет элементы из массива $primerArray и умножает их на 2:

$primerArray = 1,2,3,4 ForEach ($primer in $primerArray) {2*$primer} Цикл ForEach

Сначала мы создаём массив $primerArray и наполняем его содержимым. При первом выполнении цикла переменной $primer придаётся значение первого элемента массива. Далее с переменной совершаются действия, описанные в нашем блоке кода. Цикл повторяется до тех пор, пока не будут перебраны все элементы массива.

При использовании инструкции ForEach создавать коллекции элементов необязательно. Мы можем работать уже с существующими при помощи соответствующих командлетов.

Например, при помощи командлета dir мы можем сформировать коллекцию файлов с расширением txt в текущей папке. Код, приведенный ниже, считает общий размер данных файлов.

$a=0; ForEach ($b in dir *.txt) {$a += $b.Length}

В этом примере мы создаём переменную $a с нулевым значением. Затем при помощи командлета dir формируем коллекцию файлов с расширением txt в той директории, где мы сейчас находимся. Значение элементов этой коллекции придаётся переменной $b. После этого к текущему значению переменной $a добавляет значение поля Length (размер файла) переменной $b. Как итог, если мы обратимся к переменной $a, она покажет нам суммарный размер файлов с расширением txt.

ForEach внутри конвейера команд

Если инструкция ForEach находится внутри конвейера команд, то PowerShell используется псевдоним ForEach, соответствующий командлету ForEach-Object. Таким образом, на самом деле, в данном случае выполняется командлет ForEach-Object. В данном случае элементы коллекции предоставляются предыдущим командлетом в конвейере, поэтому часть ($элемент in $коллекция) не нужна.

В упрощенном виде ForEach внутри конвейера выглядит следующим образом:

команда | ForEach {блок_команд}

В таком случае пример с подсчетом размера txt-файлов будет выглядеть следующим образом.

$a=0; dir *.txt | ForEach {$a += $_.Length} ForEach в конвейере команд

На самом деле, в псевдониме ForEach может указываться не один блок команд, а три.

команда | ForEach {начальный_блок_команд} {средний_блок_команд} {конечный_блок_команд}

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

Вот как будет выглядеть наш пример в данном случае:

dir *.txt | ForEach {$a=0} {$a += $_.Length} {Write-Host $a}

Инструкция Break

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

$n=0; While ($True) {If ($n++ -ge 3) {Break} $n} Инструкция Break

В данном случае условием цикла While является $True, поэтому такой цикл никогда бы не завершился. Инструкция Break срабатывает при достижении переменной $n значения 3 и прекращает работу цикла.

Инструкция Continue

Инструкция Continue осуществляет переход к следующей итерации цикла любого типа.

For ($n=0; $n -le 7; $n++) {If ($n -ge 5) {Continue} $n} Инструкция Continue

Здесь на экран выводятся цифры от 0 до 4, так как для всех последующих срабатывает инструкция Continue.

В следующей части мы закончим разбирать управляющие инструкции в Windows PowerShell.

Продолжение следует…

webistore.ru

Foreach, Foreach-Object и оптимизация циклов в PowerShell

Примечание: данный пост перепечатан в связи с закрытием бложиков на spaces.live.com, как имеющий какую-то ценность для автора и/или читателей.

Коллега, Вася Гусев в посте Непонятные штуки - $_ и % рассказал интересную и полезную тему про циклы. Однако я хочу немного дополнить этот рассказ. Я хочу показать некоторые технические нюансы использования цикла вида Foreach-Object и Foreach на практических примерах.

1) задача: произвести простое последовательное переприсвоение каждого элемента большого массива в другую переменную. Для решения этой задачи мы сгенерируем массив случайных чисел:

[vPodans] $a = 1..50000 | %{get-random}

И каждый элемент массива $a переприсвоим в переменную $X. А так же измерим время выполнения цикла. Для измерения времени работы цикла я буду использовать команду Measure-Command, которая показывает время исполнения команды с достаточно высокой точностью. Сперва переменную $a перенаправим по конвейеру в цикл Foreach-Object.

[vPodans] (Measure-Command {$a | Foreach-Object {$x = $_}}).TotalSeconds 4,7633872 [vPodans] (Measure-Command {$a | Foreach-Object {$x = $_}}).TotalSeconds 4,7561438 [vPodans] (Measure-Command {$a | Foreach-Object {$x = $_}}).TotalSeconds 4,7858102 [vPodans]

Я повторил команду 3 раза и мы получили среднее время обработки цикла равным примерно 4,7 секунд. Теперь повторим ту же операцию, но с использованием Foreach.

[vPodans] (Measure-Command {Foreach ($b in $a) {$x = $b}}).TotalSeconds 0,0465927 [vPodans] (Measure-Command {Foreach ($b in $a) {$x = $b}}).TotalSeconds 0,04518 [vPodans] (Measure-Command {Foreach ($b in $a) {$x = $b}}).TotalSeconds 0,0465084 [vPodans]

Здесь же мы видим, что цикл в среднем выполняет ту же операцию за 0,046 секунды! Или иными словами Foreach почти в 100(!) раз быстрее, чем Foreach-Object. При проведении простой обработке большого массива мы видим заметное преимущество Foreach. Однако, от типа данных и типа их обработки это число (100 раз) может изменяться как в сторону увеличения, так и уменьшения. Для этого обратимся к примеру 3 и 4.

2) Задача: выбрать все записи в журнале System (в моём случае в журнале находится 42855 записей) журнала событий и записать сообщения в текстовый файл. Данную задачу можно решить при помощи цикла Foreach-Object и Foreach. В отличии от предыдущего примера мы сталкиваемся с новым фактором влияния - запись на диск. И снова сперва будем использовать Foreach-Object.

  • Пример 3. Чтение журнала System и выдача тела эвентов по конвейеру в цикл:
Get-Process -Id $PID (Measure-Command {Get-EventLog System | %{$_.message >> events.txt}}).TotalSeconds Get-Process -Id $PID

И вот результаты выполнения данного скрипта:

[vPodans] Get-Process -Id $PID Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 219 7 37424 35568 178 0,98 4356 powershell [vPodans] (Measure-Command {Get-EventLog System | %{$_.message >> events.txt}}).TotalSeconds 249,4354958 [vPodans] Get-Process -Id $PID Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 229 7 42648 42116 186 229,48 4356 powershell [vPodans]

Здесь я добавил команду Get-Process, которая покажет нам некоторые моменты, которые в первых двух примерах были неактуальны. Итак, выгрузку тела сообщений журнала System в файл цикл Foreach-Object выполнил за 249 секунд, при этом показав незначительное увеличение потребляемой памяти. К слову говоря, размер файла составил 21,5МБ.  Хочу отметить, что цифры потребления памяти очень сильно расходятся с показаниями Task Manager, который во время инициализации консоли показывает 17,5МБ потребления памяти и 22МБ после завершения работы.

 

  • Пример 4. Чтение журнала System в перменную и обработка тела эвентов циклом Foreach:
Get-Process -Id $PID (Measure-Command {$events = Get-EventLog System}).TotalSeconds (Measure-Command {Foreach ($event in $events) {$event.message >>events.txt}}).TotalSeconds Get-Process -Id $PID

Скрипт выполняет точно такую же операцию, только я добавил ещё один временной штамп, когда переменная $events будет заполнена и будет подана в foreach на разбор. И вот, что я получил:

[vPodans] Get-Process -Id $PID Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 218 7 37400 35560 178 0,98 512 powershell [vPodans] (Measure-Command {$events = Get-EventLog System}).TotalSeconds 10,8001047 [vPodans] (Measure-Command {Foreach ($event in $events) {$event.message >>events.txt}}).TotalSeconds 340,2231286 [vPodans] Get-Process -Id $PID Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 237 7 116652 114432 270 331,39 512 powershell [vPodans]

Итак, что мы здесь видим? Мы видим, что время считывания журнала System в переменную $Events составило чуть больше 10 секунд. А вот уже обработка тела эвентов (запись каждого тела в файл) заняла 340 секунд! Если сравнивать с примером использования Foreach-Object мы не то, чтобы получили уменьшение времени выполнения, но даже увеличение на 91 секунду или 1,5 минуты и это без учёта 10 секунд чтения журнала в переменную. При этом мы так же наблюдаем внушительный прирост потребления памяти почти в 4 раза! По сведениям Task Manager потребление памяти увеличилось с 17,5МБ до 95МБ (даже в 5 раз).

На показательных примерах я продемонстрировал особенности работы цикла Foreach-Object и Foreach при обработке различных типов данных и различных операциях с ними - только простое последовательное переприсвоение и запись достаточно большого объёма данных в файл.

Выводы:

  • Foreach-Object выгоден для сложной обработки большого объёма массивных данных и перенаправления их в небыстрые хранилища (например, в файл, где очень важным фактором становится невысокая скорость работы диска). Данный цикл работает достаточно быстро и характеризуется скромным потреблением памяти. Однако, когда мы обрабатываем данные несложными операциями (т.е. по сути важным фактором становится скорость работы центрального процессора и памяти), то Foreach-Object значительно уступает в скорости циклу Foreach и уступание может достигать колоссальных размеров (в примерах 1 и 2 он уступил в скорости в 100 раз).
  • Foreach выгоден при несложной обработке данных на лету без использования медленных посредников (как запись в файл), когда в основном используются только ресурсы процессора. За счёт увеличения скорости обработки данных в оперативной памяти увеличивается и потребление памяти. Но с увеличением сложности обработки (если взять пример 2 и сделать не переприсвоение, а простое математическое деление каждого элемента массива на число 3 и присвоение результата в переменную, то разница в скорости работы уже составит не 100 раз, а всего лишь 3 раза) скорость выполнения цикла заметно падает. Этот момент очень ярко выражается при использовании медленных посредников (жёсткий диск, сеть) может не только не давать прирост в скорости, но и даже снижать производительность по сравнению с Foreach-Object в схожих операциях. И в целом, Foreach характеризуется значительным увеличением потребления памяти. И чем сложнее обработка, тем более ощутимо увеличивается потребление памяти и снижается скорость работы, как это показано на примере 4.

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

Удачи! © One

www.sysadmins.lv

Обработка коллекций с помощью циклов foreach оболочки PowerShell | Windows IT Pro/RE

Чтобы максимально эффективно использовать мощный инструментарий Windows PowerShell, необходимо знать, как с помощью цикла foreach поочередно обращаться ко всем элементам коллекции, например строкового массива или списка служб Windows. В среде PowerShell реализовано два типа циклов foreach — инструкция foreach и команда ForEach-Object. Они позволяют добиваться аналогичных результатов, но отличаются друг от друга в некотором отношении. В предлагаемой статье я разъясню эти различия, а также покажу, как следует использовать инструкцию foreach и команду ForEach-Object.

Инструкция foreach

В ходе выполнения инструкции foreach оболочка PowerShell «перебирает» элементы коллекции. Для каждого элемента цикл проводится один раз; при этом выполняется блок инструкций, именуемый блоком сценария. Чтобы создать цикл foreach, нужно определить коллекцию, к элементам которой он будет обращаться, задать переменную, которая будет содержать все элементы данной коллекции, и составить блок, который будет выполняться при каждом шаге по элементам коллекции.

Поясним сказанное на примере. В следующей команде объявляется переменная $birds, затем она инициализируется со строковым массивом, после чего эта переменная используется в инструкции foreach:.

$birds = "owl", "crow", "robin", "wren", "jay" oreach ($bird in $birds) {    "$bird = " + $bird.length }

Инструкция foreach начинается с ключевого слова foreach, за которым следует пара скобок, заключающих три компонента ($bird in $birds). Первый компонент — это переменная цикла, которую мы определяем для использования в инструкции foreach. В данном случае переменная цикла имеет имя $bird, но пользователь может присвоить ей любое имя по своему усмотрению — лишь бы оно соответствовало соглашениям об именовании PowerShell. По мере того как цикл переходит от одного элемента коллекции к другому, переменная цикла всегда содержит текущую величину коллекции. Так, при первой итерации переменная $bird имеет значение owl, при второй — crow и т. д.

Второй компонент в скобках — ключевое слово in. Используйте его «как есть». Третий элемент — собственно коллекция, доступ к которой в данном случае осуществляется с помощью переменной $birds. Далее следует пара фигурных скобок. В них заключен блок сценария, выполняемый при каждом шаге цикла. В нашем примере этот блок содержит только одну инструкцию ("$bird = " + $bird.length), создающую простую строку, которая выводится на консоль. В данном случае переменная $bird получает величину из коллекции, а свойство Length считывает число символов этой величины.

Команда возвращает следующие результаты

owl = 3 crow = 4 robin = 5 wren = 4 jay = 3

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

$count = 0 $birds = "owl", "crow", "robin", `    "wren", "jay" foreach ($bird in $birds) {    $count += 1    "$bird = " + $bird.length    Write-Host } "Total number of birds is $count."

Первая инструкция в этом блоке сценария увеличивает значение переменной $count на единицу. Переменная $count определяется в первой строке и используется для подсчета промежуточной суммы элементов коллекции. Вторая инструкция формирует строку и передает ее на консоль, как в предыдущем примере. А третья инструкция — это команда Write-Host, которая попросту добавляет к выходным данным пустую строку.

При выполнении каждой итерации цикла выполняются все три инструкции блока сценария. Но код, следующий за блоком сценария, выполняется только раз — по завершении последней итерации цикла. Этот код использует переменную $count в выходных результатах. В данном случае значение этой переменной составляет 5. Данное значение присвоено переменной в ходе последней итерации, как показано в следующих результатах

owl = 3 crow = 4 robin = 5 wren = 4 jay = 3 Total number of birds is 5.

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

foreach ($svc in Get-Service) {   $svc.name + ": " +   $svc.canstop.tostring ().toupper ()}

В данном фрагменте кода третий компонент в скобках — команда Get-Service. Эта команда возвращает коллекцию объектов, где один объект представляет отдельную службу на локальной системе. Объект service назначается переменной $svc при каждой итерации цикла. В цикле инструкция foreach использует переменную $svc для получения имени службы (с помощью свойства Name объекта service) и добавляет к ней двоеточие. Далее инструкция foreach использует переменную $svc для обращения к свойству CanStop объекта service, которое возвращает логическое значение, указывающее, можно ли остановить данную службу после ее запуска. Наконец, инструкция foreach вызывает методы ToString и ToUpper для форматирования полученного значения. Но перед тем как переводить строку в верхний регистр с помощью метода ToUpper, необходимо перевести значение свойства CanStop в строковый формат с помощью метода ToString, поскольку метод ToUpper применяется только к строковым значениям. Если вы не хотите переводить строку результата в верхний регистр, можете обойтись без вызова метода ToString, как и метода ToUpper. Результаты представлены на экране 1.

Отметим, что при ссылках на методы и свойства объектов их имена нечувствительны к регистру. К примеру, при вызове метода ToString можно использовать только строчные буквы (как в приведенных примерах), только прописные или как те, так и другие.

Всякий раз при определении коллекции в инструкции foreach мы, в сущности, реализуем конвейер. В приведенном выше примере конвейер формируется из выходных данных команды Get-Service. Но можно создавать и более сложные конвейеры, например такие:

foreach ($svc in Get-Service | where {$_.status -eq 'running' }) {    $svc.name + ": " +    $svc.canstop.tostring (). toupper () }

В этой команде выходные данные Get-Service передаются по конвейеру команде Where-Object (вызываемой с помощью псевдонима where), которая выбирает из значений, возвращаемых командой Get-Service, только те объекты service, свойство Status которых определяется как running. Для обращения к текущему значению в конвейере команда Where-Object использует встроенную переменную $_. На экране 2 представлены примерные результаты, возвращаемые с помощью данной команды.

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

$svcs = Get-Service | where {$_.status -eq 'running'} foreach ($svc in $svcs) {    $svc.name + ": " +    $svc.canstop.tostring ().   toupper () }

Нетрудно убедиться, что для вызова коллекции инструкция foreach использует переменные $svc. Эта команда возвращает те же результаты, что и предыдущая.

Команда ForEach-Object

Мы рассмотрели, как используется инструкция foreach для перебора элементов коллекции, но это еще не все. Наряду с данной инструкцией в оболочке PowerShell реализована команда ForEach-Object. И чтобы жизнь не казалась пользователям совсем уж безмятежной, слово foreach служит псевдонимом для обращения к упомянутой команде.

ForEach-Object получает коллекцию из конвейера и, подобно инструкции foreach, поочередно обращается ко всем ее элементам. Поясним сказанное на примере. Следующая команда возвращает те же результаты (показанные на экране 2), что были получены в результате выполнения двух предыдущих команд:

Get-Service | where {$_.status -eq 'running'} | foreach {    $_.name + ": " +    $_.canstop.tostring ().toupper () }

Эта инструкция начинается с передачи по конвейеру выходных данных Get-Service команде Where-Object. Далее коллекция, возвращенная командой Where-Object, передается по конвейеру команде ForEach-Object (для обращения к последней используется псевдоним foreach). Обратите внимание, что за псевдонимом foreach следует только блок сценария — никакого кода в скобках нет. Следствие этого различия между инструкцией foreach и командой ForEach-Object состоит в том, что необходимость определения переменной элементов отпадает. Вместо нее используется встроенная переменная $_. Во всех прочих отношениях оставшаяся часть блока сценария ничем не отличается от кода, использовавшегося в двух предыдущих примерах (отметим, что открывающую фигурную скобку следует размещать на той же строке, где указывается псевдоним foreach, иначе PowerShell будет рассматривать первую строку как завершенную инструкцию).

Но как же тогда оболочка PowerShell отличает ключевое слово foreach от псевдонима foreach? Если foreach стоит в начале инструкции, PowerShell рассматривает ее как ключевое слово и обрабатывает следующий за ним код как инструкцию foreach. Если же это слово указывается в другом месте, PowerShell рассматривает его как псевдоним команды ForEach-Object.

В среде PowerShell предусмотрен еще один псевдоним для ссылки на команду ForEach-Object; это знак процента (%). Так, инструкция

Get-Service | where {$_.status -eq 'running'} | % {    $_.name + ": "+    $_.canstop.tostring ().toupper () }

возвращает те же результаты, что и в предыдущем примере, хотя в ней вместо foreach используется символ %.

Различия

Итак, для получения одних и тех же результатов можно использовать как инструкцию foreach, так и команду ForEach-Object, но надо иметь в виду, что между ними существует несколько различий. Во-первых, как мы уже убедились, команда несколько проще, поскольку в случае ее применения не нужно создавать специальную переменную цикла — вместо нее используется встроенная переменная $_.

Второе отличие сводится к тому, каким образом среда PowerShell обрабатывает эти две инструкции. Когда PowerShell обрабатывает инструкцию PowerShell, она, перед тем как приступить к работе с отдельными величинами, генерирует всю коллекцию. Когда же оболочка обрабатывает команду ForEach-Object, она работает с каждым значением в момент соответствующего шага по конвейеру; таким образом, в каждый момент времени используются меньшие объемы памяти. В случаях, когда важно экономно расходовать ресурсы памяти, более целесообразно использовать команду.

Третье различие состоит в следующем. Если выходные данные команды ForEach-Object можно передать далее по конвейеру, то при использовании инструкции foreach такой возможности нет. В следующем фрагменте кода выходные данные команды ForEach-Object передаются команде Sort-Object:

Get-Service | where {$_.status -eq 'running'} | foreach {    $_.name + ": " +    $_.canstop.tostring ().toupper () } | sort -descending

Команда Sort-Object (вызываемая с использованием псевдонима sort) сортирует выходные данные конвейера в порядке убывания, как показано на экране 3.

Еще одно преимущество команды ForEach-Object по сравнению с инструкцией foreach состоит в том, что команда поддерживает три типа блоков сценария, как видно из следующего фрагмента кода:

Get-Service | where {$_.status -eq 'running'} | foreach {    $count = 0} {    $_.name + ": " +       $_.canstop.tostring ().toupper ()    $count ++} {Write-Host    "$count services are running."    Write-Host }

Первый блок сценария присваивает переменной $count значение 0. Эта переменная отслеживает число элементов в коллекции. Второй блок сценария получает значения свойств Name и CanStop для каждой службы и увеличивает значение переменной $count на 1. Третий блок сценария содержит указание распечатать сообщение, содержащее общее число служб, которое получено на основе последнего значения переменной $count.

При включении трех блоков сценария указанным выше способом PowerShell выполняет первый блок до первого выполнения цикла, второй блок выполняется по разу при каждом проходе, а третий блок — после выполнения последнего цикла. На экране 4 видно, как отображается общее число служб в соответствии с инструкциями последнего блока сценария.

На следующих уроках

Инструкция foreach и команда ForEach-Object — мощные инструменты для работы с коллекциями. Любой из них можно использовать для создания циклов, в ходе которых выполняется набор инструкций для каждого элемента коллекции. При составлении сценариев PowerShell придется часто пользоваться инструкциями foreach. И, как будет видно из следующих статей, вы сможете создавать гораздо более сложные команды, нежели те, что были показаны выше.

Роберт Шелдон ([email protected]) — технический консультант и автор книг по технологиям Microsoft Windows и базам данных

www.osp.ru

PowerShell: прощай, Goto | Windows IT Pro/RE

Когда более 30 лет назад в MS-DOS появились пакетные файлы, язык программирования BASIC был основным языком программирования для микрокомпьютеров. Оператор Goto используют в языке BASIC, чтобы упростить повторение строк кода, поэтому разработчики Microsoft добавили команду Goto к «языку» пакетных файлов с целью обеспечить функцию того же типа. В листинге 1 показан простой пакетный файл, в котором команда Goto используется для перехода к метке пакетного файла, чтобы повторить команду Echo пять раз.

Когда «язык» пакетных файлов был обновлен в Cmd.exe на платформе Windows NT, Microsoft усовершенствовала команду Call, чтобы метка в пакетном файле могла действовать как простая подпрограмма, аналогично старому оператору GOSUB в языке BASIC. В листинге 2 показан пример такого использования команды Call.

При выполнении пакетного файла в листинге 2 выводится фраза Hello, Ken, а затем End of batch file. Команда Call позволяет перейти к команде с меткой, а затем выполнение возвращается строке, следующей после команды Call. Кроме того, в подпрограммах пакетного файла нет встроенного способа передачи значений без переменных среды и неинтуитивных синтаксических приемов Cmd.exe.

Дело в том, что команды Goto и Call пакетного файла позволяют повторять строки кода, но это делается неструктурированным способом. То есть команда Goto или Call обеспечивает произвольный переход к любой метке в пакетном файле. Пакетный файл становится более сложным, использование Goto или Call в конечном итоге ведет к превращению кода в «спагетти», так как отслеживать логику пакетного файла становится все труднее.

Авторы PowerShell полностью обошли проблему «спагетти-кода». В PowerShell нет ни команды Goto, ни команды Call, потому что они не нужны. Вместо них в PowerShell появились структурированные программные инструкции, заменившие как Goto (для итерации), так и Call (для подпрограмм).

Итерации PowerShell

Итерация (или цикл) — это повторение кода. PowerShell располагает четырьмя базовыми операторами, которые повторяют выполнение строк кода в зависимости от условия: while, do while, do until и for. Четыре оператора, или конструкции цикла, фактически устраняют необходимость в команде Goto.

Когда в MS-DOS появились пакетные файлы, BASIC был основным языком программирования для микрокомпьютеров, и GOTO была незаменимой командой. К счастью, положение изменилось. Ниже показано, как while, do while, do until и for заменили традиционные команды в различных ситуациях.

Оператор While

Оператор while, или цикл while, повторяет строки кода, пока сохраняется истинность определенного условия. В листинге 3 показан пример сценария, в котором устанавливается переменная и выдается значение этой переменной при условии, что оно больше 0. Выходные данные сценария — числа 5, 4, 3, 2 и 1.

Код в скобках после ключевого слова while — условный оператор, который определяет, сколько раз выполняется код в фигурных скобках (известный как блок сценария, scriptblock). Когда вы используете цикл while, PowerShell проверяет условие в начале цикла. Выполните команду help about_While в PowerShell, чтобы получить больше информации и примеры цикла while.

Оператор Do

Оператор do, или цикл do, похож на оператор while; разница в том, что PowerShell проверяет условный оператор в конце цикла, а не в начале. Для цикла do требуется, чтобы в начале блока присутствовало ключевое слово while или until, поэтому мы обычно называем его циклом do while или do until. В листинге 4 показан тот же цикл, что и в листинге 3, реализованный в виде цикла do while.

В листинге 5 показан тот же цикл, что и в листингах 3 и 4, но реализованный в виде цикла do until. Ключевое слово until меняет логику цикла «повторять до тех пор, пока условие не станет истинным» вместо «повторять, пока условие истинно».

Единственная разница между циклами while, do while и do until — место проверки оператора условия (начало или конец цикла) и логика повторения (пока условие не нарушится или до его выполнения). Выполните команду help about_Do в PowerShell, чтобы получить больше информации и примеры циклов do while и do until.

Оператор For

Оператор for, или цикл for, повторяет код в блоке в зависимости от набора условий. Код в цикле for имеет инструкцию инициализации, инструкцию условия и инструкцию повторения. Инструкция инициализации выполняется в начале цикла и задает начальные условия, инструкция условия проверяет, должен ли продолжаться цикл, а инструкция повторения выполняется в каждом цикле. В листинге 6 показан простой пример цикла for.

В листинге 6 инструкция инициализации — $n = 5, инструкция условия — $n -ge 1, а инструкция повторения — $n-. Вывод листинга 6 точно такой же, как в листингах 3, 4 и 5. Выполните команду help about_For в PowerShell, чтобы получить больше информации и примеры цикла for.

Какой оператор использовать

Все четыре основных оператора итерации PowerShell (while, do while, do until и for) позволяют повторно выполнять код в зависимости от условия. Используйте тот оператор повторения, который позволяет получить более ясный программный код.

Оператор Foreach

Пользователям, более глубоко знающим PowerShell, известно об операторе foreach, или цикле foreach, который позволяет повторять команды для каждого элемента массива или коллекции объектов. Цикл foreach больше похож на команду For пакетного файла. В следующей статье я покажу, как можно заменить команду For пакетного файла в PowerShell.

Подпрограммы PowerShell

PowerShell не нуждается в команде Call или метках подпрограмм. Вместо этого в PowerShell применяются функции, как в других структурированных языках программирования. Функция — просто имя блока программного кода. Для запуска кода в функции просто вставьте имя функции в программу. В листинге 7 содержится простой пример как определения, так и выполнения функции PowerShell.

Ключевое слово function указывает PowerShell, что мы определяем функцию. Вставляя имя функции в программный код (как в последней строке листинга 7), PowerShell выполняет код внутри фигурных скобок.

Подпрограммы пакетного файла позволяют передавать параметры в подпрограмму с командой Call, и вы можете использовать подставляемые параметры в подпрограмме (%1 и т. д., как показано в листинге 2). Оператор param в верхней части функции позволяет сделать то же в PowerShell: он определяет параметры функции. Листинг 8 представляет собой расширенный вариант листинга 7 с параметром.

При выполнении программного кода в листинге 8 с параметром переменная $name в функции заменяется параметром, переданным из последней строки (строка «Ken»).

Напомню, что у подпрограмм пакетного файла нет собственного способа для возврата значений. Вывод функции в листинге 8 — собственно выходная строка. Кроме того, PowerShell без проблем возвращает несколько значений из функции; значения автоматически возвращаются списком (также именуемым массивом или коллекцией). В листинге 9 показана функция, которая возвращает список из трех чисел.

Таким образом, в PowerShell нет необходимости в командах Goto и Call. Циклические конструкции PowerShell (while, do while, do until и for) заменяют Goto, а функции заменяют команду Call. Если вы все еще создаете пакетные файлы, то эта статья поможет вам заменить команды Goto и Call в ваших будущих сценариях PowerShell.

Листинг 1. Пример пакетного файла Goto @Echo Off Set NUM=5 :REPEAT Echo %NUM% Set /A NUM-=1 If %NUM% GTR 0 Goto :REPEAT Set NUM= Листинг 2. Пакетный файл с подпрограммой @Echo Off Call :SAYHELLO Ken Echo End of batch file Goto :END :SAYHELLO Echo Hello, %1 :END Листинг 3. Цикл while в PowerShell $n = 5 while ( $n -gt 0 ) {   $n   $n-- } Листинг 4. Цикл do while в PowerShell $n = 5 do {   $n   $n-- } while ( $n -gt 0 ) Листинг 5. Цикл do until в PowerShell $n = 5 do {   $n   $n-- } until ( $n -eq 0 ) Листинг 6. Цикл for в PowerShell for ( $n = 5; $n -ge 1; $n-- ) {   $n } Листинг 7. Простая функция PowerShell function SayHello {   "Hello, world" } # Run the function SayHello Листинг 8. Функция PowerShell с параметром function SayHello {   param($name)   "Hello, $name" } # Run the function SayHello Ken Листинг 9. Функция PowerShell возвращает список function MyFunction {   $list = @(1,2,3)   $list } $result = MyFunction # Output the list $result

www.osp.ru

Windows PowerShell: Жизненный цикл расширенной функции

Расширенные функции в Windows PowerShell — их еще называют «сценарными командлетами» — могут казаться сложными, но есть способ управления их функциями подготовки и очистки.

Дон Джонс

Ряд слушателей моих курсов по Windows PowerShell на моем сайте испытывали трудности. Я надеюсь, что более подробный анализ позволит и вам избавиться от непонимания. Мы поговорим о расширенных функциях Windows PowerShell, которые неформально называют «сценарными командлетами». Шаблон функции этого типа выглядит так:

Function Do-Something { [CmdletBinding()] param( [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [string[]]$computername ) BEGIN {} PROCESS {} END {} }

У этих командлетов есть ряд вводящих в заблуждение особенностей. В частности, в этом комадлете есть входной параметр по имени -computername. Он может принимать входные данные по конвейеру. Это означает, что функцию можно вызывать двумя разными способами. Во-первых, ей можно передавать строки по конвейеру, например из текстового файла, содержащего по одному имени компьютера в строке:

Get-Content names.txt | Do-Something

Также можно просто передать одно или несколько имен компьютеров непосредственно в параметр, не используя конвейер:

Do-Something –computername SERVER1,SERVER2

В первом примере сначала выполняется блок BEGIN функции. Далее выполняется блок PROCESS — по одному разу для каждого поступившего на вход имени компьютера. Переменная $computername в любой момент времени может содержать только одно имя компьютера. Наконец по завершении выполнения указанных блоков выполнятся блок END.

Во втором примере блоки BEGIN и END вообще не выполняются. Блок PROCESS выполняется только раз, а через переменную $computername проходят все имена, переданные во входной параметр.

Такая большая разница в поведении может затруднять выполнения задач подготовки и очистки, которые выполняются в обоих ситуациях. Также могут возникать затруднения с использованием параметров $computername. В первом примере в каждый момент времени он содержит только одно значение, а во втором может быть несколько значений, которые заданы в параметре –computername. Разрешить проблему параметра $computername можно, просто разместив цикл ForEach внутри блока PROCESS:

Function Do-Something { [CmdletBinding()] param( [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [string[]]$computername ) BEGIN {} PROCESS { Foreach ($computer in $computername) { # use $computer here } } END {} }

При таком подходе в каждый момент времени переменная $computer будет содержать лишь одно имя компьютера, поэтому лучше работать именно с ней, а не $computername.

С подготовкой и очисткой дела обстоят сложнее. Нельзя выполнять подготовку в блоке PROCESS. Он выполняется несколько раз при передаче объектов по конвейеру. С другой стороны, подготовку нельзя разместить прямо в блоке BEGIN. Она просто не будет выполнена, если по конвейеру ничего не будет передано. Конечно, существует много способов решения этой проблемы, но такой кажется наилучшим:

Function Do-Something { [CmdletBinding()] param( [Parameter(Mandatory=$True, ValueFromPipeline=$True)] [string[]]$computername ) BEGIN { $setup_done = $false function DoSomethingSetup { set-variable -name setup_done -value $true -scope 1 } DoSomethingSetup } PROCESS { if (-not $setup_done) { DoSomethingSetup } Foreach ($computer in $computername) { # use $computer here } } }

Здесь используется тот факт, что у BEGIN, PROCESS и END общая область действия, а точнее они используют одинаковые переменные. Определив переменную как флаг, можно обеспечить, что ключевой элемент блока BEGIN — моя функция DoSomethingSetup — будет вызвана лишь раз.

Заметьте, что в функции DoSomethingSetup нужно прибегать к особому ухищрению, чтобы присвоить флагу значение $True по завершении подготовки. Так как DoSomethingSetup является функцией, у нее собственная область видимости, и обычно она не может менять значения переменных за пределами этой области. Можно явно изменить нужную переменную с помощью командлета Set-Variable.

Сложнее применять этот прием для задач очистки. В первом примере, где объекты передаются в Do-Something по конвейеру, блок END выполняется. Задачи очистки можно разместить в нем. Однако во втором примере по конвейеру ничего не поступает, и END не выполняется. Блок PROCESS выполняется лишь раз, поэтому он вряд ли «узнает», что END выполняться не будет.

Блок PROCESS не может просто вызвать функцию из блока END. Когда объекты передаются по конвейеру, блок PROCESS выполняется несколько раз и совершенно нежелательно, чтобы столько же раз выполнялся блок END.

Есть несколько хитрых способов решения проблемы, но простой подход всегда оказывается наилучшим: Если нужно выполнить определенную очистку (например, закрыть подключение к базе данных), забудьте о блоках BEGIN и END. Считайте, что блок PROCESS должен выполняться только раз, и вставьте все задания подготовки и очистки в нем.

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

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

Дон Джонс (Don Jones) носит звание MVP и является автором «Learn Windows PowerShell in a Month of Lunches» (Manning Publications Co., 2010), книги, призванной помочь каждому администратору освоить Windows PowerShell. Дон читает общедоступные и интерактивные курсы по Windows PowerShell.Связаться с ним можно через его веб-сайт ConcentratedTech.com.

technet.microsoft.com

about_For

Оператор For (цикл For) — конструкция языка для создания цикла, в котором команды в блоке команд выполняются, пока указанное условие принимает значение true.

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

Синтаксис

Ниже показан синтаксис оператора For.

for (<init>; <condition>; <repeat>) {<statement list>}

Заполнитель <init> представляет одну или несколько разделяемых запятыми команд, выполняемых перед началом цикла. Обычно элемент оператора <init> используется для создания переменной и присвоения ей начального значения. Обратите внимание на то, что синтаксис с запятой не работает с операторами присваивания, как в следующем примере:

$ofs=",";$rs = "rs"; $cs = "cs"; for ($r = $rs, $c = $cs; $true;) { "r is '$r' and c is '$c'"; break }

Эта переменная затем будет использоваться для проверки условия в следующей части оператора For.

Заполнитель <condition> представляет часть оператора For, которая принимает логическое значение true или false. Windows PowerShell® вычисляет условие при каждом выполнении цикла For. Если результатом вычисления выражения является значение true, выполняется блок команд, а выражение вычисляется снова. Если условие по-прежнему имеет значение true, команды в списке операторов выполняются снова. Цикл повторяется, пока условие не примет значение false.

Заполнитель <repeat> представляет одну или несколько команд, разделенных запятыми, которые выполняются при каждом повторении цикла. Обычно это используется для изменения переменной, проверяемой внутри части <condition> оператора.

Заполнитель <statement list> представляет одну или несколько команд, выполняющихся при каждом входе в цикл или его повторении. Содержимое списка операторов заключается в фигурные скобки.

Примеры

Как минимум для оператора For необходимы круглые скобки, заключающие блок <init>, <condition> и <repeat>, а также заключенная в фигурные скобки команда в блоке <statement list>.

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

Например, приведенный ниже оператор For будет постоянно отображать значение переменной $i до тех пор, пока команда не будет вручную прервана нажатием клавиш CTRL+C.

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

В список операторов можно добавить дополнительные команды, чтобы значение переменной $i увеличивалось на 1 при каждом выполнении цикла, как показано в примере ниже.

for (;;){$i++; Write-Host $i}

До тех пор пока пользователь не прервет команду нажатием клавиш CTRL+C, этот оператор будет постоянно отображать значение переменной $i, которое будет увеличиваться на 1 при каждом выполнении цикла.

Вместо изменения значения переменной в списке операторов цикла For можно воспользоваться блоком <repeat>, как показано ниже.

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

Этот оператор по-прежнему будет повторяться бесконечно долго или до тех пор, пока пользователь не прервет команду нажатием клавиш CTRL+C.

Задав соответствующее условие (с помощью блока <condition> оператора For), можно завершить цикл For, когда условие примет значение false. В примере ниже цикл For выполняется, пока значение переменной $i меньше или равно 10.

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

Вместо создания и инициализации переменной за пределами оператора For то же самое можно сделать внутри цикла For с помощью блока <init>.

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

В качестве разделителя блоков <init>, <condition> и <repeat> оператора For вместо точки с запятой можно использовать знаки возврата каретки. В примере ниже показана эта альтернативная форма оператора For.

for (<init> <condition> <repeat>){ <statement list> }

Такая альтернативная форма оператора For используется в файлах сценариев Windows PowerShell и в командной строке Windows PowerShell. Но при вводе интерактивных команд в командной строке удобнее использовать синтаксис оператора For с точкой с запятой.

Цикл For более гибок, чем цикл Foreach, так как он позволяет увеличивать значения в массиве или коллекции с помощью шаблонов. В примере ниже переменная $i увеличивается на 2 в блоке <repeat> оператора For.

for ($i = 0; $i -ile 20; $i += 2) {Write-Host $i}

technet.microsoft.com