添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

与许多其他语言一样,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'
        '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 的一项独特特性是它有许多可更改其执行方式的开关参数。

-CaseSensitive

默认情况下,匹配项不区分大小写。 如果需要区分大小写,可以使用 -CaseSensitive。 这可以与其他开关参数结合使用。

-Wildcard

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

在我撰写的另一篇文章中有更多使用正则表达式的示例:使用正则表达式的多种方式

-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

因为输入文件中的一行可能同时包含单词 ErrorWarning,所以我们只希望执行第一个,然后继续处理文件。

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 也是如此。 一行中可能同时包含单词 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

我个人不喜欢使用中断标签,之所以提及的原因是,如果你之前从未见过它们,可能会感到困惑。 如果有多个嵌套的 switchforeach 语句,则可能需要中断的不只是最内层的项。 可以在 switch 上放置一个标签,作为 break 的目标。

PowerShell 5.0 为我们提供了可以在 switch 中使用的枚举。

enum Context {
    Component
    Location
$item = [Context]::Role
switch ( $item )
    Component
        'is a component'
        '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 视为文本字符串。

如果需要,我们可以使用脚本块来执行匹配的计算。

$age = 37
switch ( $age )
    {$PSItem -le 18}
        'child'
    {$PSItem -gt 18}
        'adult'
'adult'

这会增加复杂性,并且会使 switch 很难读。 在大多数情况下,使用类似语句时,最好使用 ifelseif 语句。 如果已经有一个较大的 switch,并且需要两个项来命中同一计算块,我会考虑使用这个。

我认为有助于增强可读性的一点是,将脚本块放在圆括号内。

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

此外,请注意 cmdlet 返回的空值。 没有输出的 cmdlet 或管道被视为与任何内容都不匹配的空数组,包括 default 事例。

$file = Get-ChildItem NonExistantFile*
switch ( $file )
    $null   { '$file is $null' }
    default { "`$file is type $($file.GetType().Name)" }
# No matches

常数表达式

Lee Dailey 指出,我们可以使用恒定的 $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 }

这会为你提供以下结果:

向前移动枚举器,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 语句表面上看起来很简单,但它提供了一些大多数人都不知道的高级功能。 这些功能结合在一起使它非常强大。 我希望你学到了一些之前不知道的内容。