与许多其他语言一样,PowerShell 具有用于控制脚本内执行流的命令。 其中一个语句是 switch 语句,在 PowerShell 中,它提供在其他语言中找不到的功能。 今天,我们深入探讨如何使用 PowerShell switch。
注释
本文 的原始版本 出现在 @KevinMarquette撰写的博客上。 PowerShell 团队感谢 Kevin 与我们共享此内容。 请在 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 语句允许你提供一个变量和一个可能的值列表。 如果值与变量匹配,则执行其语句块。
$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 的一个独特功能是,它具有许多 [switch] 参数来更改其执行方式。
-大小写敏感
默认情况下,匹配项不区分大小写。 如果需要区分大小写,可以使用 -CaseSensitive。 这可以与其他 [switch] 参数结合使用。
-通配符
可以通过-Wildcard[switch]参数启用通配符支持。 这使用与-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
}
}
我在另一篇文章中提供了更多使用正则表达式的示例: 使用正则表达式的多种方式。
-文件
switch 语句的一个鲜为人知的功能是,它可以使用 -File 参数处理文件。 使用 -File 时,应提供文件路径,而不是变量表达式。
switch -Wildcard -File $path
{
'Error*'
{
Write-Error -Message $PSItem
}
'Warning*'
{
Write-Warning -Message $PSItem
}
default
{
Write-Output $PSItem
}
}
它的工作方式就像处理数组一样。 在此示例中,我将其与通配符匹配结合使用,并使用$PSItem。 这将处理日志文件,并根据正则表达式匹配项将其转换为警告和错误消息。
高级详细信息
现在,你已了解所有这些记录的功能,我们可以在更高级的处理上下文中使用这些功能。
Expressions
可以在表达式而不是变量上使用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 -Wildcard -File $path
{
'*Error*'
{
Write-Error -Message $PSItem
continue
}
'*Warning*'
{
Write-Warning -Message $PSItem
continue
}
default
{
Write-Output $PSItem
}
}
由于输入文件中的一行可以同时包含单词 Error , Warning因此我们只需要执行第一行,然后继续处理文件。
中断
语句 break 退出开关。 这是 continue 针对单个值所表现的相同行为。 处理数组时会显示差异。
break 停止开关中的所有处理,并 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开头的行,就会产生错误,并且程序停止运行。
这就是声明 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
}
}
}
我个人不喜欢使用break标签,但我想提到它们,因为如果你以前从未见过它们可能会感到困惑。 当您有多个 switch 或 foreach 语句嵌套时,您可能需要跳出超过最内层项的嵌套。 可以将标签 switch 放置在可以是目标 break的位置上。
Enum
PowerShell 5.0 提供了枚举,我们可以在交换机中使用它们。
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'
}
}
此处需要括号,以便开关不会将值 [Context]::Location 视为文本字符串。
ScriptBlock
如果需要,可以使用脚本块来进行匹配评估。
$age = 37
switch ( $age )
{
{$PSItem -le 18}
{
'child'
}
{$PSItem -gt 18}
{
'adult'
}
}
'adult'
这增加了复杂性,并且可能使你的 switch 难以阅读。 在大多数情况下,如果要使用类似的内容,最好使用if 和 elseif 语句。 如果我已经安装了一个大型交换机,并且需要两个项目进入相同的评估模块,那么我会考虑使用它。
我认为有助于易读的一件事是将脚本块放在括号中。
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
可以匹配一个值,这个值不必是默认值。
$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
请注意 cmdlet 的空返回值。 没有输出的 Cmdlet 或管道被视为不匹配任何内容的空数组,包括 default 情况。
$file = Get-ChildItem NonExistantFile*
switch ( $file )
{
$null { '$file is $null' }
default { "`$file is type $($file.GetType().Name)" }
}
# No matches
常数表达式
李戴利指出,可以使用常量 $true 表达式来计算 [bool] 项。
假设我们有一些需要执行的布尔检查。
$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 时,调用其语句块。 这有些违反直觉,但却巧妙地利用了机制原理。
$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。
Enum
PowerShell 5.0 引入了此功能 enum ,在本例中也是一个选项。
$day = 3
enum DayOfTheWeek {
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
}
[DayOfTheWeek]$day
Wednesday
我们可以整天研究解决这个问题的不同方法。 我只是想确保你知道你有选择。
最后的话
switch 语句在表面上很简单,但它提供了大多数人无法实现的一些高级功能。 将这些功能串在一起会使此功能成为一项强大的功能。 我希望你学到了以前没有意识到的东西。