共用方式為


關於 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 語句可讓您提供變數和可能值的清單。 如果值符合變數,則會執行其 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。 您可以使用 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 的唯一功能是它有一些參數,可變更其執行方式。

-CaseSensitive

根據預設,相符專案不會區分大小寫。 如果您需要區分大小寫,可以使用 -CaseSensitive。 這可以與其他開關參數搭配使用。

-通配符

我們可以使用 -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的許多方式。

-檔

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 -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
        }
    }
}

我個人不喜歡使用「中斷標籤」,但我想指出它們,因為如果你以前從未見過,它們可能會讓人感到困惑。 當您有多個巢狀 switchforeach 語句時,您可能會想要跳出不僅是最內層的語句。 您可以將標籤放在 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'
    }
}

此處需要括弧,以避免開關將 [Context]::Location 的值視為字串。

ScriptBlock

我們可以使用「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'
    }
}

它仍然以相同的方式運行,但在快速查看時能提供更好的視覺效果。

Regex $Matches

我們需要重新流覽 regex,以接觸一些不明顯的東西。 regex 的使用會填入 $Matches 變數。 當我談論 使用 regex的多種方式時,我會更深入地探討 $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 被評估時,其 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 語句在表面上很簡單,但它提供了一些大多數人無法意識到的進階功能。 將這些功能串在一起會使此功能成為功能強大的功能。 我希望你學到了你以前沒有意識到的東西。