Все, что вы хотели знать об операторе if
Как и многие другие языки, PowerShell содержит операторы для условного исполнения кода в сценариях. Одним из таких операторов является оператор if. Сегодня мы более подробно поговорим об одной из важнейших команд в PowerShell.
Примечание.
Оригинал этой статьи впервые был опубликован в блоге автора @KevinMarquette. Группа разработчиков PowerShell благодарит Кевина за то, что он поделился с нами этими материалами. Читайте его блог — PowerShellExplained.com.
Условное выполнение
В сценариях часто требуется принимать решения и применять различные логики в зависимости от этих решений.
Именно это имеется в виду под условным выполнением. У вас есть один оператор или значение для вычисления, а затем вы выполняете другой раздел кода на основе этого вычисления. Именно это и делает оператор if
.
Инструкция if
Ниже приведен простой пример оператора if
.
$condition = $true
if ( $condition )
{
Write-Output "The condition was true"
}
Первое, что делает оператор if
, — вычисляет выражение в круглых скобках. Если он имеет значение $true
, то он выполняет scriptblock
в фигурных скобках. Если значение равнялось $false
, то этот блок сценария был бы пропущен.
В предыдущем примере оператор if
только что вычислил переменную $condition
. Он был равен $true
и выполнил бы команду Write-Output
в блоке сценария.
В некоторых языках можно поместить одну строку кода после оператора if
, чтобы выполнить ее. В PowerShell все не так. Чтобы все работало правильно, необходимо указать полный scriptblock
с фигурными скобками.
Операторы сравнения
Оператор if
чаще всего используется для сравнения двух элементов. PowerShell предлагает специальные операторы для различных сценариев сравнения. При использовании оператора сравнения значение в левой части сравнивается со значением в правой части.
-eq для "равно"
-eq
проверяет равенство двух значений, чтобы убедиться в том, что они равны друг другу.
$value = Get-MysteryValue
if ( 5 -eq $value )
{
# do something
}
В этом примере я беру известное значение 5
и сравниваю его с моим $value
, чтобы понять, совпадают ли они.
Один из возможных вариантов использования — проверка состояния значения до того, как выполнено действие с ним. Можно получить службу и убедиться в том, что до вызова Restart-Service
она имела состояние running.
В других языках, таких как C#, обычно используется ==
для проверки на равенство (например, 5 == $value
), но в PowerShell это невозможно. Еще одна распространенная ошибка, которую делают пользователи, — использование знака равенства (например, 5 = $value
), который зарезервирован для присвоения значений переменным. Если поместить известное значение слева, вероятность ошибки повышается.
Для этого оператора (как и для других) доступно несколько вариантов.
-eq
без учета регистра, равенство-ieq
без учета регистра, равенство-ceq
с учетом регистра, равенство
-ne "не равно"
Для многих операторов предусмотрен связанный оператор, который выполняет проверку на противоположный результат. -ne
проверяет, чтобы значения не были равны друг другу.
if ( 5 -ne $value )
{
# do something
}
Этот параметр позволяет убедиться в том, что действие выполняется только в том случае, если значение не равно 5
. В качестве одного из полезных примеров его использования можно проверить, имеет ли служба состояние running до того, как пытаться запустить ее.
Варианты:
-ne
без учета регистра, не равно-ine
без учета регистра, не равно-cne
с учетом регистра, не равно
Это инвертированные варианты -eq
. Эти типы будут сгруппированы при перечислении вариантов для других операторов.
-gt -ge -lt -le для "больше, чем" или "меньше, чем"
Эти операторы позволяют убедиться в том, что значение больше или меньше другого значения.
-gt -ge -lt -le
означают GreaterThan, GreaterThanOrEqual, LessThan и LessThanOrEqual.
if ( $value -gt 5 )
{
# do something
}
Варианты:
-gt
"больше, чем"-igt
"больше, чем", без учета регистра-cgt
"больше, чем", с учетом регистра-ge
"больше, чем" или "равно"-ige
"больше, чем" или "равно", без учета регистра-cge
"больше, чем" или "равно", с учетом регистра-lt
"меньше, чем"-ilt
"меньше, чем", без учета регистра-clt
"меньше, чем", с учетом регистра-le
"меньше, чем" или "равно"-ile
"меньше, чем" или "равно", без учета регистра-cle
"меньше, чем" или "равно", с учетом регистра
Я не знаю, зачем для этих операторов использовать параметры с учетом регистра или без учета регистра.
Сопоставление с использованием подстановочного знака -like
PowerShell использует собственный синтаксис сопоставления шаблонов на основе подстановочных знаков, и его можно сочетать с оператором -like
. Шаблоны с подстановочными знаками довольно просты.
?
соответствует любому отдельному символу.*
сопоставляет любое число символов
$value = 'S-ATX-SQL01'
if ( $value -like 'S-*-SQL??')
{
# do something
}
Важно отметить, что шаблон сопоставляет всю строку. Если необходимо сопоставить данные в середине строки, поместите *
на обоих концах строки.
$value = 'S-ATX-SQL02'
if ( $value -like '*SQL*')
{
# do something
}
Варианты:
-like
без учета регистра, подстановочный знак-ilike
без учета регистра, подстановочный знак-clike
с учетом регистра, подстановочный знак-notlike
без учета регистра, подстановочный знак, не сопоставлен-inotlike
без учета регистра, подстановочный знак, не сопоставлен-cnotlike
с учетом регистра, подстановочный знак, не сопоставлен
Регулярное выражение -match
Оператор -match
позволяет проверить строку на соответствие регулярному выражению. Используйте этот параметр, если шаблоны с подстановочными знаками не обеспечивают достаточной гибкости.
$value = 'S-ATX-SQL01'
if ( $value -match 'S-\w\w\w-SQL\d\d')
{
# do something
}
Шаблон регулярного выражения по умолчанию соответствует данным в любом месте строки. Поэтому можно указать подстроку, которую необходимо сопоставить следующим образом:
$value = 'S-ATX-SQL01'
if ( $value -match 'SQL')
{
# do something
}
Регулярные выражения представляют собой сложный язык сами по себе и достойны изучения. Я подробнее расскажу о -match
и множестве способов использования регулярных выражений в другой статье.
Варианты:
-match
без учета регистра, регулярное выражение-imatch
без учета регистра, регулярное выражение-cmatch
с учетом регистра, регулярное выражение-notmatch
без учета регистра, регулярное выражение, не сопоставлено-inotmatch
без учета регистра, регулярное выражение, не сопоставлено-cnotmatch
с учетом регистра, регулярное выражение, не сопоставлено
-is относится к типу
Тип значения можно проверить с помощью оператора -is
.
if ( $value -is [string] )
{
# do something
}
Эту возможность можно использовать, если вы работаете с классами или принимаете различные объекты по конвейеру. В качестве входных данных может быть задана служба или имя службы. Затем проверьте, есть ли у вас служба, и получите службу, используя только ее имя.
if ( $Service -isnot [System.ServiceProcess.ServiceController] )
{
$Service = Get-Service -Name $Service
}
Варианты:
-is
относится к типу-isnot
не относится к типу
Операторы коллекции
При использовании предыдущих операторов с одним значением результатом является $true
или $false
. При работе с коллекцией обработка выполняется несколько другим способом. Каждый элемент в коллекции вычисляется, и оператор возвращает каждое значение, результатом которого является $true
.
PS> 1,2,3,4 -eq 3
3
Это по-прежнему работает правильно в инструкции if
. Поэтому оператор возвращает значение, и тогда вся инструкция равна $true
.
$array = 1..6
if ( $array -gt 3 )
{
# do something
}
Есть одна небольшая ловушка, скрывающаяся в деталях здесь, что мне нужно указать. При использовании -ne
оператора таким образом легко ошибочно взглянуть на логику назад. Использование -ne
с коллекцией возвращает $true
, если какой-либо элемент в коллекции не совпадает с имеющимся значением.
PS> 1,2,3 -ne 4
1
2
3
Это может показаться разумным, но у нас есть операторы -contains
и -in
, которые более эффективно справятся с этой задачей. При этом -notcontains
выполняет то, что от него ожидается.
содержит-
Оператор -contains
проверяет коллекцию на наличие вашего значения. После обнаружения совпадения возвращается значение $true
.
$array = 1..6
if ( $array -contains 3 )
{
# do something
}
Это самый удобный способ узнать, содержит ли коллекция ваше значение. При использовании Where-Object
(или -eq
) каждый раз выполняется обход всего списка, и работа значительно замедляется.
Варианты:
-contains
без учета регистра, совпадение-icontains
без учета регистра, совпадение-ccontains
с учетом регистра, совпадение-notcontains
без учета регистра, не сопоставлено-inotcontains
без учета регистра, не сопоставлено-cnotcontains
с учетом регистра, не сопоставлено
-in
Оператор -in
похож на оператор -contains
за исключением того, что коллекция находится в правой части.
$array = 1..6
if ( 3 -in $array )
{
# do something
}
Варианты:
-in
без учета регистра, совпадение-iin
без учета регистра, совпадение-cin
с учетом регистра, совпадение-notin
без учета регистра, не сопоставлено-inotin
без учета регистра, не сопоставлено-cnotin
с учетом регистра, не сопоставлено
Логические операторы
Логические операторы используются для инверсии или объединения других выражений.
-not
Оператор -not
инвертирует выражение из $false
в $true
или из $true
в $false
. Ниже приведен пример, в котором требуется выполнить действие, когда Test-Path
равно $false
.
if ( -not ( Test-Path -Path $path ) )
Большинство операторов, о которых мы говорили, используют вариант, в котором использование оператора -not
не требуется. Однако в отдельных случаях это все-таки полезно.
! оператор
Можно использовать !
как псевдоним для -not
.
if ( -not $value ){}
if ( !$value ){}
Как можно заметить, !
чаще используется теми пользователями, которые переходят на PowerShell с других языков, таких как C#. Я предпочитаю печатать этот оператор, потому что мне его трудно увидеть при быстром просмотре сценариев.
-and
Выражения можно объединять с помощью оператора -and
. В этом случае обе стороны должны быть равны $true
, чтобы все выражение было равно $true
.
if ( ($age -gt 13) -and ($age -lt 55) )
В этом примере $age
должно быть равно 13 или более для левой части и менее 55 для правой части. Я добавил дополнительные круглые скобки, чтобы выделить эти данные в примере, но это необязательно, если выражение простое. Вот пример без дополнительных скобок.
if ( $age -gt 13 -and $age -lt 55 )
Вычисления выполняются слева направо. Если первый элемент принимает значение $false
, он преждевременно завершает работу и правильное сравнение не выполняется. Это удобно, если перед использованием значения необходимо убедиться в том, что оно существует. Например, Test-Path
выдает ошибку, если присвоить ему путь $null
.
if ( $null -ne $path -and (Test-Path -Path $path) )
-or
-or
позволяет указать два выражения и возвращает значение $true
, если одно из них равно $true
.
if ( $age -le 13 -or $age -ge 55 )
Как и в случае с оператором -and
, вычисление выполняется слева направо. Однако если первая часть равна $true
, то весь оператор равен $true
и не обрабатывает оставшуюся часть выражения.
Обратите также внимание на то, как работает синтаксис для этих операторов. Потребуется два отдельных выражения. Я видел, что пользователи пытаются сделать что-то похожее на $value -eq 5 -or 6
, не понимая, в чем их ошибка.
-xor (исключающее ИЛИ)
Это немного необычный оператор. -xor
позволяет принимать равным $true
только одно выражение. Таким образом, если оба элемента равны $false
или $true
, все выражение равно $false
. Еще один вариант — выражение равно $true
только в том случае, если результаты выражения отличаются.
Вряд ли кто-то станет вообще использовать этот логический оператор, и я даже не могу придумать хороший пример, зачем я сам стал бы это делать.
битовые операторы;
Побитовые операторы выполняют вычисления с битами в значениях и создают новое результирующее значение. Побитовые операторы обучения выходят за область этой статьи, но вот список из них.
-band
двоичное И-bor
двоичное ИЛИ-bxor
двоичное исключающее ИЛИ-bnot
двоичное НЕ-shl
сдвиг влево-shr
сдвиг вправо
Выражения PowerShell
В операторе условия можно использовать обычные функции PowerShell.
if ( Test-Path -Path $Path )
При выполнении Test-Path
возвращает $true
или $false
. Это также относится к командам, которые возвращают другие значения.
if ( Get-Process Notepad* )
Он оценивается, $true
если есть возвращенный процесс и $false
если нет. Вполне допустимо использовать выражения конвейера или другие операторы PowerShell:
if ( Get-Process | Where Name -eq Notepad )
Эти выражения можно объединять друг с другом с помощью операторов -and
и -or
, однако, возможно, потребуется использование круглых скобок, чтобы разбить выражения на части.
if ( (Get-Process) -and (Get-Service) )
Проверка на наличие значения $null
Отсутствие результата или значение $null
вычисляется как $false
в операторе if
. Если выполняется специальная проверка на наличие $null
, рекомендуется размещать $null
в левой части.
if ( $null -eq $value )
При работе со значениями $null
в PowerShell существует много нюансов. Если вы хотите узнать об этом больше, ознакомьтесь с моей статьей обо всем, что необходимо знать о $null.
Назначение переменной в условии
Я чуть не забыл добавить этот раздел, хорошо, что Прасун Карунан В. мне об этом напомнил.
if ($process=Get-Process notepad -ErrorAction ignore) {$process} else {$false}
Обычно присвоенное переменной значение не передается в конвейер или в консоль. При присвоении переменной в части выражения оно передается в конвейер.
PS> $first = 1
PS> ($second = 2)
2
Заметили, что присвоение $first
не создает выходные данные, а присвоение $second
— создает? Когда присвоение происходит в операторе if
, оно выполняется точно так же, как присвоение $second
выше. Далее приведен понятный пример возможного использования.
if ( $process = Get-Process Notepad* )
{
$process | Stop-Process
}
Если для $process
присваивается значение, то работа оператора $true
и $process
прекращается.
Убедитесь в том, что вы не путаете его с -eq
, так как это не проверка на равенство. Это малоизвестная функция, и большинство пользователей даже не представляют, что она так работает.
Назначение переменной из блока скриптов
Можно также использовать блок скрипта оператора if
для присвоения значения переменной.
$discount = if ( $age -ge 55 )
{
Get-SeniorDiscount
}
elseif ( $age -le 13 )
{
Get-ChildDiscount
}
else
{
0.00
}
Каждый блок скрипта записывает результаты выполнения команд или значение в качестве выходных данных. Затем можно присвоить результат выполнения оператора if
переменной $discount
. В этом примере можно так же легко присвоить эти значения переменной $discount
непосредственно в каждом блоке ScriptBlock. Не сказал бы, что я часто использую эту возможность для оператора if
, однако у меня есть пример недавнего использования.
Альтернативный путь выполнения
Оператор if
позволяет указать действие не только, когда оператор равен $true
, но и когда он равен $false
. Именно здесь используется оператор else
.
else
При использовании оператора else
он всегда является последней частью оператора if
.
if ( Test-Path -Path $Path -PathType Leaf )
{
Move-Item -Path $Path -Destination $archivePath
}
else
{
Write-Warning "$path doesn't exist or isn't a file."
}
В этом примере мы проверяем $path
, чтобы убедиться в том, что это файл. Если мы найдем файл, мы его переместим. В противном случае мы создаем предупреждение. Этот тип логики ветвления весьма распространен.
Вложенное IF
Операторы if
и else
принимают блок сценария, поэтому мы можем поместить в них любую команду PowerShell, в том числе еще один оператор if
. Это позволяет использовать гораздо более сложную логику.
if ( Test-Path -Path $Path -PathType Leaf )
{
Move-Item -Path $Path -Destination $archivePath
}
else
{
if ( Test-Path -Path $Path )
{
Write-Warning "A file was required but a directory was found instead."
}
else
{
Write-Warning "$path could not be found."
}
}
В этом примере мы сначала тестируем удачный путь, а затем используем его на практике. Если это не удалось сделать, выполняется еще одна проверка, чтобы указать более подробные сведения для пользователя.
elseif
Мы не ограничиваемся одной условной проверкой. Можно объединять операторы if
и else
в цепочку, а не вкладывать их с помощью оператора elseif
.
if ( Test-Path -Path $Path -PathType Leaf )
{
Move-Item -Path $Path -Destination $archivePath
}
elseif ( Test-Path -Path $Path )
{
Write-Warning "A file was required but a directory was found instead."
}
else
{
Write-Warning "$path could not be found."
}
Они выполняются в направлении сверху вниз. Верхний оператор if
вычисляется в первую очередь. Если это $false
, вычисление выполняется для следующего оператора elseif
или else
в списке. Последний оператор else
является действием по умолчанию, которое выполняется, если ни одно другое действие не возвращает $true
.
switch
На этом этапе я должен рассказать об операторе switch
. Он предоставляет альтернативный синтаксис для выполнения нескольких сравнений со значением. С помощью switch
указывается выражение, а полученный результат сравнивается с несколькими различными значениями. Если одно из этих значений совпадает, выполняется соответствующий блок кода. Взгляните на этот пример:
$itemType = 'Role'
switch ( $itemType )
{
'Component'
{
'is a component'
}
'Role'
{
'is a role'
}
'Location'
{
'is a location'
}
}
Существует три возможных значения, которые могут совпадать с $itemType
. В этом случае оно совпадает с Role
. Я использовал этот простой пример, просто чтобы продемонстрировать работу оператора switch
. В другой статье я подробно описываю все, что вы хотите знать об операторе switch.
Массив в строке
У меня есть функция с именем Invoke-SnowSql, которая запускает исполняемый файл с несколькими аргументами командной строки. Вот фрагмент этой функции, где я создаю массив аргументов.
$snowSqlParam = @(
'--accountname', $Endpoint
'--username', $Credential.UserName
'--option', 'exit_on_error=true'
'--option', 'output_format=csv'
'--option', 'friendly=false'
'--option', 'timing=false'
if ($Debug)
{
'--option', 'log_level=DEBUG'
}
if ($Path)
{
'--filename', $Path
}
else
{
'--query', $singleLineQuery
}
)
Переменные $Debug
и $Path
являются параметрами функции, которые указываются конечным пользователем.
Я вычисляю их в строке при инициализации массива. Если $Debug
имеет значение true, эти значения помещаются в нужной точке в $snowSqlParam
. То же самое верно для переменной $Path
.
Упрощение сложных операций
Вы неизбежно столкнетесь с такой ситуацией, когда необходимо проверить множество сравнений и для просмотра оператора If
придется долго прокручивать экран вправо.
$user = Get-ADUser -Identity $UserName
if ( $null -ne $user -and $user.Department -eq 'Finance' -and $user.Title -match 'Senior' -and $user.HomeDrive -notlike '\\server\*' )
{
# Do Something
}
Они могут быть трудночитаемыми, и из-за этого возможны ошибки. Эту проблему можно решить несколькими способами.
Продолжение строки
В PowerShell есть ряд операторов, которые позволяют переносить команды на следующую строку. Логические операторы -and
и -or
полезны, если нужно разбить выражение на несколько строк.
if ($null -ne $user -and
$user.Department -eq 'Finance' -and
$user.Title -match 'Senior' -and
$user.HomeDrive -notlike '\\server\*'
)
{
# Do Something
}
Там выполняется еще много действий, но размещение каждого фрагмента в отдельной строке в корне меняет дело. Я обычно использую эту функцию, если у меня более двух сравнений или мне приходится прокручивать экран вправо, чтобы прочитать любую часть логики.
Предварительное вычисление результатов
Мы можем взять эту инструкцию из оператора if
и просто проверить результат.
$needsSecureHomeDrive = $null -ne $user -and
$user.Department -eq 'Finance' -and
$user.Title -match 'Senior' -and
$user.HomeDrive -notlike '\\server\*'
if ( $needsSecureHomeDrive )
{
# Do Something
}
Это гораздо более понятно, чем в предыдущем примере. У вас также есть возможность использовать имя переменной, которое разъясняет, что именно вы проверяете. Это также пример самодокументируемого кода, в котором сохраняются несущественные комментарии.
Несколько операторов if
Можно разбить этот фрагмент на несколько операторов и проверить их по одному. В этом случае для объединения результатов используется флаг или переменная отслеживания.
$skipUser = $false
if( $null -eq $user )
{
$skipUser = $true
}
if( $user.Department -ne 'Finance' )
{
Write-Verbose "isn't in Finance department"
$skipUser = $true
}
if( $user.Title -match 'Senior' )
{
Write-Verbose "Doesn't have Senior title"
$skipUser = $true
}
if( $user.HomeDrive -like '\\server\*' )
{
Write-Verbose "Home drive already configured"
$skipUser = $true
}
if ( -not $skipUser )
{
# do something
}
Мне нужно было инвертировать логику, чтобы логика флагов работала корректно. Каждое вычисление представляет собой отдельный оператор if
. Преимущество такого подхода заключается в том, что при отладке вы сможете точно определить, что делает эта логика. В то же время мне удалось обеспечить гораздо более высокий уровень детализации.
Очевидным недостатком является то, что приходится писать гораздо больше кода. Код более сложен для просмотра, так как он занимает одну строку логики и разворачивает ее в 25 или более строк.
Использование функций
Кроме того, можно переместить всю логику проверки в функцию. Посмотрите, насколько простым и понятным это выглядит.
if ( Test-SecureDriveConfiguration -ADUser $user )
{
# do something
}
Вам по-прежнему необходимо создать функцию для выполнения проверки, однако благодаря этому с кодом будет гораздо проще работать. Это упрощает тестирование кода. В тестах можно имитировать вызов Test-ADDriveConfiguration
, и для этой функции потребуется всего два теста. В одном из них она возвращает $true
, а во втором — $false
. Тестировать другую функцию проще, потому что она совсем небольшая.
Текстовая область этой функции по-прежнему может состоять всего из одной строки, как это было в самом начале, или может быть расчлененной логикой, которую мы использовали в последнем разделе. Это эффективно в рамках обоих сценариев и позволяет легко изменить данную реализацию позднее.
Обработка ошибок
Одной из основных целей использования оператора if
является проверка наличия условий возникновения ошибок, прежде чем они появятся. Например, вы можете проверить, существует ли папка, до того, как попытаетесь ее создать.
if ( -not (Test-Path -Path $folder) )
{
New-Item -Type Directory -Path $folder
}
Я люблю говорить: если вы ожидаете, что возникнет исключение, значит, на самом деле это не исключение. Таким образом, лучше при любом удобном случае проверять свои значения и условия.
Более подробно о фактической обработке исключений см. в моей статье, где рассматривается все, что вы хотели знать об исключениях.
Заключение
Оператор if
очень простой, но именно он является основополагающим компонентом PowerShell. Вы будете очень часто его использовать практически в каждом созданном вами сценарии. Надеюсь, теперь вы лучше понимаете его работу, чем раньше.
PowerShell
Обратная связь
https://aka.ms/ContentUserFeedback.
Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделеОтправить и просмотреть отзыв по