Все, что вы когда-либо хотели знать об инструкции switch
Как и многие другие языки, PowerShell содержит команды для управления потоком выполнения в скриптах. Одна из них — инструкция switch, которая обеспечивает в PowerShell возможности, отсутствующие в других языках. Сегодня мы подробно рассмотрим работу с switch
в PowerShell.
Примечание.
Оригинал этой статьи впервые был опубликован в блоге автора @KevinMarquette. Группа разработчиков PowerShell благодарит Кевина за то, что он поделился с нами этими материалами. Читайте его блог — PowerShellExplained.com.
Инструкция if
Одна из первых инструкций, которую вы изучите, — if
. Она позволяет выполнять блок скрипта, если значение инструкции $true
.
if ( Test-Path $Path )
{
Remove-Item $Path
}
Вы можете использовать гораздо более сложную логику elseif
и else
операторы. Ниже приведен пример, в котором у меня есть числовое значение, обозначающее день недели, а я хочу получить имя в виде строки.
$day = 3
if ( $day -eq 0 ) { $result = 'Sunday' }
elseif ( $day -eq 1 ) { $result = 'Monday' }
elseif ( $day -eq 2 ) { $result = 'Tuesday' }
elseif ( $day -eq 3 ) { $result = 'Wednesday' }
elseif ( $day -eq 4 ) { $result = 'Thursday' }
elseif ( $day -eq 5 ) { $result = 'Friday' }
elseif ( $day -eq 6 ) { $result = 'Saturday' }
$result
Wednesday
На самом деле это довольно типичная ситуация, и решить эту задачу можно множеством способов. В частности, с помощью инструкции switch
.
Оператор switch
Инструкция switch
позволяет указать переменную и список возможных значений. Если значение соответствует переменной, выполняется ее блок ScriptBlock.
$day = 3
switch ( $day )
{
0 { $result = 'Sunday' }
1 { $result = 'Monday' }
2 { $result = 'Tuesday' }
3 { $result = 'Wednesday' }
4 { $result = 'Thursday' }
5 { $result = 'Friday' }
6 { $result = 'Saturday' }
}
$result
'Wednesday'
В этом примере значение $day
соответствует одному из числовых значений, поэтому $result
присваивается правильное имя. Мы делаем только назначение переменной в этом примере, но в этих блоках скриптов можно выполнить любую powerShell.
Присвоение значения переменной
Этот последний пример можно написать иначе.
$result = switch ( $day )
{
0 { 'Sunday' }
1 { 'Monday' }
2 { 'Tuesday' }
3 { 'Wednesday' }
4 { 'Thursday' }
5 { 'Friday' }
6 { 'Saturday' }
}
Мы помещаем значение в конвейер PowerShell и присваиваем ему значение $result
. Это же действие можно выполнить с помощью инструкций if
и foreach
.
По умолчанию.
Воспользовавшись ключевым словом default
, можно определить, что должно произойти при отсутствии совпадения.
$result = switch ( $day )
{
0 { 'Sunday' }
# ...
6 { 'Saturday' }
default { 'Unknown' }
}
Здесь по умолчанию возвращается значение Unknown
.
Строки
Я в этих последних примерах сопоставлял числа, но вы также можете сопоставлять строки.
$item = 'Role'
switch ( $item )
{
Component
{
'is a component'
}
Role
{
'is a role'
}
Location
{
'is a location'
}
}
is a role
Я решил не заключать здесь совпадения с Component
, Role
и Location
в кавычки, подчеркнув тем самым тот факт, что они необязательны. В большинстве случаев switch
обрабатывает их как строку.
Массивы
Одна из замечательных особенностей PowerShell switch
— особый способ обработки массивов. Если передать инструкции switch
массив, она обработает каждый элемент в этой коллекции.
$roles = @('WEB','Database')
switch ( $roles ) {
'Database' { 'Configure SQL' }
'WEB' { 'Configure IIS' }
'FileServer' { 'Configure Share' }
}
Configure IIS
Configure SQL
Если в массиве есть повторяющиеся элементы, они будут сопоставляться в соответствующем разделе несколько раз.
PSItem
С помощью $PSItem
или $_
можно ссылаться на текущий обработанный элемент. Когда мы делаем простое совпадение, это значение, $PSItem
которое мы сопоставляем. В следующем разделе я покажу, как выполнить более сложные сопоставления с использованием этой переменной.
Параметры
Уникальная функция PowerShell switch
заключается в том, что он имеет ряд параметров коммутатора, которые изменяют способ его выполнения.
-CaseSensitive
По умолчанию сопоставления выполняются без учета регистра. Если необходимо учитывать регистр, можно использовать параметр -CaseSensitive
. Его можно использовать в сочетании с другими параметрами-переключателями.
-Wildcard
Поддержку подстановочных знаков в switch можно включить с помощью параметра -wildcard
. При этом для каждого соответствия используется та же логика применения подстановочных знаков, что и у оператора -like
.
$Message = 'Warning, out of disk space'
switch -Wildcard ( $message )
{
'Error*'
{
Write-Error -Message $Message
}
'Warning*'
{
Write-Warning -Message $Message
}
default
{
Write-Information $message
}
}
WARNING: Warning, out of disk space
Здесь мы обрабатываем сообщение, а затем выводим его в разных потоках на основе содержимого.
-Regex
Инструкция switch поддерживает сопоставление не только с помощью подстановочных знаков, но и с использованием регулярных выражений.
switch -Regex ( $message )
{
'^Error'
{
Write-Error -Message $Message
}
'^Warning'
{
Write-Warning -Message $Message
}
default
{
Write-Information $message
}
}
У меня есть больше примеров использования regex в другой статье я написал: Многие способы использования regex.
-File
Малоизвестная возможность инструкции switch заключается в том, что при использовании параметра -File
она может обработать файл. Вместо того чтобы присваивать переменное выражение, используйте -file
с путем к файлу.
switch -Wildcard -File $path
{
'Error*'
{
Write-Error -Message $PSItem
}
'Warning*'
{
Write-Warning -Message $PSItem
}
default
{
Write-Output $PSItem
}
}
Принцип действия будет таким же, как при обработке массива. В этом примере я использую этот параметр для сопоставления вместе с подстановочными знаками и $PSItem
. В результате выполняется обработка файла журнала и преобразование его в предупреждения и сообщения об ошибках в зависимости от совпадений, полученных на основе регулярного выражения.
Дополнительные сведения
Теперь, когда вы знаете обо всех этих документированных возможностях, мы можем использовать их в контексте более сложной обработки.
Выражения
Инструкцию switch
можно применять не только к переменной, но и к выражению.
switch ( ( Get-Service | Where status -eq 'running' ).name ) {...}
Результат вычисления выражения и будет тем значением, которое используется для сопоставления.
Несколько совпадений
Возможно, вы уже догадались, что для сопоставления с помощью switch
можно использовать несколько условий. Это особенно актуально при сопоставлении с использованием -wildcard
или -regex
. Вы можете добавить одно и то же условие несколько раз, и все активируются.
switch ( 'Word' )
{
'word' { 'lower case word match' }
'Word' { 'mixed case word match' }
'WORD' { 'upper case word match' }
}
lower case word match
mixed case word match
upper case word match
Здесь сработают все три инструкции. Это свидетельствует о том, что проверяется каждое условие (по порядку). Это справедливо и для обработки массивов, где каждое условие проверяется для каждого элемента.
Продолжить
Как правило, в таких случаях добавляют инструкцию break
, но сначала давайте разберемся, как использовать continue
. Как и в цикле foreach
, continue
переходит к следующему элементу в коллекции или выходит из switch
, если элементов не осталось. Мы можем переписать последний пример с использованием операторов continue, чтобы выполнялась только одна инструкция.
switch ( 'Word' )
{
'word'
{
'lower case word match'
continue
}
'Word'
{
'mixed case word match'
continue
}
'WORD'
{
'upper case word match'
continue
}
}
lower case word match
Вместо того чтобы сопоставлять все три элемента, сопоставляется только первый из них, после чего switch переходит к следующему значению. Так как значений для обработки не осталось, выполнение switch завершается. В следующем примере показано, как подстановочный знак может сопоставляться с несколькими элементами.
switch -Wildcard -File $path
{
'*Error*'
{
Write-Error -Message $PSItem
continue
}
'*Warning*'
{
Write-Warning -Message $PSItem
continue
}
default
{
Write-Output $PSItem
}
}
Так как строка во входном файле может содержать как слово Error
, так и слово Warning
, необходимо, чтобы инструкция выполнялась только для первого из них, а затем продолжалась обработка файла.
Перерыв
Инструкция break
выполняет выход из switch. Для отдельных значений ее поведение такое же, как у continue
. Разница проявляется при обработке массива. break
останавливает всю обработку в switch, а continue
выполняет переход к следующему элементу.
$Messages = @(
'Downloading update'
'Ran into errors downloading file'
'Error: out of disk space'
'Sending email'
'...'
)
switch -Wildcard ($Messages)
{
'Error*'
{
Write-Error -Message $PSItem
break
}
'*Error*'
{
Write-Warning -Message $PSItem
continue
}
'*Warning*'
{
Write-Warning -Message $PSItem
continue
}
default
{
Write-Output $PSItem
}
}
Downloading update
WARNING: Ran into errors downloading file
write-error -message $PSItem : Error: out of disk space
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException
В этом случае при совпадении с любой строкой, начинающейся с Error
, выводится сообщение об ошибке и выполнение инструкции switch останавливается.
Вот что делает инструкция break
. Если слово Error
найдено в середине, а не в начале строки, она записывается как предупреждение. Сделаем то же самое для Warning
. Строка может содержать как слово Error
, так и слово Warning
, но для обработки требуется только одно из них. Этот то, что делает инструкция continue
.
Метки прерывания
Инструкция switch
поддерживает метки break/continue
так же, как foreach
.
:filelist foreach($path in $logs)
{
:logFile switch -Wildcard -File $path
{
'Error*'
{
Write-Error -Message $PSItem
break filelist
}
'Warning*'
{
Write-Error -Message $PSItem
break logFile
}
default
{
Write-Output $PSItem
}
}
}
Мне лично не нравится использовать метки разрыва, но я хочу обратить на них внимание, так как при первом знакомстве с ними могут возникнуть определенные сложности. При наличии нескольких вложенных инструкций switch
или foreach
может потребоваться прерывание на разных уровнях, а не только на самом внутреннем элементе. В switch
можно поместить метку, которая и будет целевым объектом для break
.
Перечисление
В PowerShell 5.0 поддерживаются перечисления, которые можно использовать в switch.
enum Context {
Component
Role
Location
}
$item = [Context]::Role
switch ( $item )
{
Component
{
'is a component'
}
Role
{
'is a role'
}
Location
{
'is a location'
}
}
is a role
Если вы хотите хранить все данные как строго типизированные перечисления, их можно взять в круглые скобки.
switch ($item )
{
([Context]::Component)
{
'is a component'
}
([Context]::Role)
{
'is a role'
}
([Context]::Location)
{
'is a location'
}
}
Здесь круглые скобки необходимы, чтобы инструкция switch не интерпретировала значение [Context]::Location
как литеральную строку.
ScriptBlock
При необходимости для оценки соответствия можно использовать блок ScriptBlock.
$age = 37
switch ( $age )
{
{$PSItem -le 18}
{
'child'
}
{$PSItem -gt 18}
{
'adult'
}
}
'adult'
Такой подход повышает сложность и может затруднить чтение switch
. В большинстве случаев, когда требуется использовать что-то подобное, лучше воспользоваться инструкциями if
и elseif
. Я бы воспользовался этим вариантом, если бы у меня уже была большая инструкция switch и мне нужно было, чтобы два элемента попали в один и тот же блок оценки.
Мне кажется, что для улучшения читаемости блок ScriptBlock можно заключить в круглые скобки.
switch ( $age )
{
({$PSItem -le 18})
{
'child'
}
({$PSItem -gt 18})
{
'adult'
}
}
Он будет выполняться таким же образом, но при этом визуальная разбивка станет удобнее для быстрого просмотра.
Регулярное выражение $matches
Нам нужно вернуться к регулярному выражению, чтобы поработать над одним неочевидным аспектом. При использовании регулярного выражения заполняется переменная $matches
. Использование переменной $matches
подробно рассмотрено в статье о множестве способов использования регулярных выражений. Здесь же приведен краткий пример, демонстрирующий ее в действии при использовании с именованными совпадениями.
$message = 'my ssn is 123-23-3456 and credit card: 1234-5678-1234-5678'
switch -regex ($message)
{
'(?<SSN>\d\d\d-\d\d-\d\d\d\d)'
{
Write-Warning "message contains a SSN: $($matches.SSN)"
}
'(?<CC>\d\d\d\d-\d\d\d\d-\d\d\d\d-\d\d\d\d)'
{
Write-Warning "message contains a credit card number: $($matches.CC)"
}
'(?<Phone>\d\d\d-\d\d\d-\d\d\d\d)'
{
Write-Warning "message contains a phone number: $($matches.Phone)"
}
}
WARNING: message may contain a SSN: 123-23-3456
WARNING: message may contain a credit card number: 1234-5678-1234-5678
$null
Вы можете выполнить сопоставление со значением $null
, которое не обязательно должно быть значением по умолчанию.
$values = '', 5, $null
switch ( $values )
{
$null { "Value '$_' is `$null" }
{ '' -eq $_ } { "Value '$_' is an empty string" }
default { "Value [$_] isn't an empty string or `$null" }
}
Value '' is an empty string
Value [5] isn't an empty string or $null
Value '' is $null
При тестировании пустой строки в switch
операторе важно использовать оператор сравнения, как показано в этом примере вместо необработанного значения ''
. В операторе switch
необработанное значение ''
также совпадает $null
. Например:
$values = '', 5, $null
switch ( $values )
{
$null { "Value '$_' is `$null" }
'' { "Value '$_' is an empty string" }
default { "Value [$_] isn't an empty string or `$null" }
}
Value '' is an empty string
Value [5] isn't an empty string or $null
Value '' is $null
Value '' is an empty string
Кроме того, будьте внимательны при работе с пустыми возвратами из командлетов. Командлеты или конвейеры, у которых нет выходных данных, обрабатываются как пустой массив, который ничего не соответствует, включая default
случай.
$file = Get-ChildItem NonExistantFile*
switch ( $file )
{
$null { '$file is $null' }
default { "`$file is type $($file.GetType().Name)" }
}
# No matches
Константное выражение
Ли Дейли отмечает, что для вычисления элементов [bool]
можно использовать константное выражение $true
.
Представьте, что у нас есть несколько логических проверок, которые необходимо выполнить.
$isVisible = $false
$isEnabled = $true
$isSecure = $true
switch ( $true )
{
$isEnabled
{
'Do-Action'
}
$isVisible
{
'Show-Animation'
}
$isSecure
{
'Enable-AdminMenu'
}
}
Do-Action
Enabled-AdminMenu
Используя такой подход, можно непосредственно оценить состояние нескольких логических полей и выполнить над ними соответствующее действие. Его преимущество в том, что одно совпадение может отражать состояние значения, которое еще не было оценено.
$isVisible = $false
$isEnabled = $true
$isAdmin = $false
switch ( $true )
{
$isEnabled
{
'Do-Action'
$isVisible = $true
}
$isVisible
{
'Show-Animation'
}
$isAdmin
{
'Enable-AdminMenu'
}
}
Do-Action
Show-Animation
Поскольку в этом примере для $isEnabled
задано значение $true
, для $isVisible
также должно быть задано значение $true
.
Тогда при вычислении переменной $isVisible
будет вызываться ее ScriptBlock. Это не совсем интуитивный, но весьма остроумный способ применения этой возможности.
Автоматическая переменная $switch
Когда инструкция switch
обрабатывает значения, она создает перечислитель и вызывает его $switch
. Это автоматически созданная PowerShell переменная, с которой можно работать напрямую.
$a = 1, 2, 3, 4
switch($a) {
1 { [void]$switch.MoveNext(); $switch.Current }
3 { [void]$switch.MoveNext(); $switch.Current }
}
Получаем такие результаты:
2
4
При переходе перечислителя к следующему элементу тот не обрабатывается с помощью switch
, но к его значению можно получить доступ напрямую. Как по мне, это уж слишком.
Прочие ситуации
хэш-таблицы;
Одна из моих самых популярных публикаций посвящена хэш-таблицам. Один из вариантов использования hashtable
— таблица подстановки. Это альтернативный подход к общему шаблону, который switch
оператор часто обращается.
$day = 3
$lookup = @{
0 = 'Sunday'
1 = 'Monday'
2 = 'Tuesday'
3 = 'Wednesday'
4 = 'Thursday'
5 = 'Friday'
6 = 'Saturday'
}
$lookup[$day]
Wednesday
Если я использую switch
лишь для подстановки, я часто применяю hashtable
.
Перечисление
В PowerShell 5.0 появилась функция Enum
, и в данном случае можно использовать также ее.
$day = 3
enum DayOfTheWeek {
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
}
[DayOfTheWeek]$day
Wednesday
Способов решения этой задачи очень много, так что на их обсуждение ушел бы целый день. Я же лишь хотел рассказать о том, что существуют различные варианты.
Заключение
Несмотря на свою кажущуюся простоту, инструкция switch зачастую предоставляет дополнительные возможности, о которых большинство даже и не подозревает. Если использовать их все вместе, получится довольно эффективный инструмент. Надеюсь, вы узнали что-то, о чем раньше и не подозревали.
PowerShell
Обратная связь
https://aka.ms/ContentUserFeedback.
Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделеОтправить и просмотреть отзыв по