通过


有关 switch 语句的所有信息

与许多其他语言一样,PowerShell 具有用于控制脚本内执行流的命令。 其中一个语句是 switch 语句,在 PowerShell 中,它提供在其他语言中找不到的功能。 今天,我们深入探讨如何使用 PowerShell switch

注释

本文 的原始版本 出现在 @KevinMarquette撰写的博客上。 PowerShell 团队感谢 Kevin 与我们共享此内容。 请在 PowerShellExplained.com 查看他的博客。

if 语句

你学习的首批语句之一是 if。 它允许您在语句为 $true 时执行脚本块。

if ( Test-Path $Path )
{
    Remove-Item $Path
}

可以使用elseifelse语句实现更复杂的逻辑。 下面是一个示例,其中我有一个用数字表示一周的某一天的值,我想以字符串形式获取其名称。

$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。 你可以对 ifforeach 语句执行相同的操作。

违约

可以使用 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

我决定不将ComponentRoleLocation 用引号括起来,以强调它们是可选的。 在大多数情况下,这些 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
    }
}

由于输入文件中的一行可以同时包含单词 ErrorWarning因此我们只需要执行第一行,然后继续处理文件。

中断

语句 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 也做同样的事情。 一行可能同时包含单词 ErrorWarning,但我们只需要其中之一进行处理。 这就是 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标签,但我想提到它们,因为如果你以前从未见过它们可能会感到困惑。 当您有多个 switchforeach 语句嵌套时,您可能需要跳出超过最内层项的嵌套。 可以将标签 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 难以阅读。 在大多数情况下,如果要使用类似的内容,最好使用ifelseif 语句。 如果我已经安装了一个大型交换机,并且需要两个项目进入相同的评估模块,那么我会考虑使用它。

我认为有助于易读的一件事是将脚本块放在括号中。

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 语句在表面上很简单,但它提供了大多数人无法实现的一些高级功能。 将这些功能串在一起会使此功能成为一项强大的功能。 我希望你学到了以前没有意识到的东西。