switch ステートメントについて知りたかったことのすべて

他の多くの言語と同様に、PowerShell には、スクリプト内の実行フローを制御するためのコマンドが用意されています。 このようなステートメントの 1 つが switch ステートメントであり、PowerShell 内では他の言語にはない機能を提供します。 本日は、PowerShell switch の使用について詳しく説明します。

注意

この記事のオリジナル バージョンは、@KevinMarquette 氏のブログに掲載されました。 このコンテンツを共有してくださった Kevin 氏に、PowerShell チームより感謝を申し上げます。 PowerShellExplained.com のブログをご確認ください。

if ステートメント

最初に学習するステートメントの 1 つは、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

これは一般的なパターンであり、これを処理する方法は多数あります。 その 1 つに 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

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

ここでは、一致項目 ComponentRole、および Location を引用符で囲まないことにしました。引用符は省略可能であることを示すためです。 switch では、ほとんどの場合、これらは文字列として扱われます。

配列

PowerShell switch の優れた機能の 1 つは、配列の処理方法です。 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

これら 3 つのステートメントがすべてトリガーされています。 これは、すべての条件が (順に) チェックされることを示しています。 これは配列を処理する場合にも当てはまり、各項目で各条件がチェックされます。

Continue

通常は、ここで break ステートメントを紹介しますが、まず continue を使用する方法を学習する方がよいでしょう。 foreach ループと同様に、continue はコレクション内の次の項目に進み続け、それ以上項目がなくなると switch から抜けます。 ステートメントが 1 つだけ実行されるように、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

3 つの項目をすべて照合するのではなく、最初の項目が一致したところで switch は次の値に進みます。 処理する値が残っていないため、switch は終了します。 次の例は、ワイルドカードが複数の項目とどのように一致するかを示しています。

switch -Wildcard -File $path
{
    '*Error*'
    {
        Write-Error -Message $PSItem
        continue
    }
    '*Warning*'
    {
        Write-Warning -Message $PSItem
        continue
    }
    default
    {
        Write-Output $PSItem
    }
}

入力ファイル内の行に ErrorWarning の両方の語が含まれている可能性があるため、最初のものだけを実行してから、ファイルの処理を続行します。

Break

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 の両方の語が含まれている可能性ありますが、処理に必要なのは 1 つだけです。 これは、continue ステートメントが行う処理です。

break ラベル

foreach と同様に、switch ステートメントでは break/continue ラベルがサポートされています。

: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 ステートメントが入れ子になっているときに、最も内側から抜けるだけでなく、さらに外へ抜け出したい場合があります。 break のターゲットとなるラベルを switch に配置できます。

列挙型

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 がリテラル文字列として処理されないようにするために、かっこが必要です。

スクリプト ブロック

必要に応じて、スクリプト ブロックを使用して評価を実行して照合に利用できます。

$age = 37

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

これにより、複雑さが増し、switch が読みにくくなる可能性があります。 ほとんどの場合、このようなステートメントを使用するところでは、if および elseif ステートメントを使用する方が適しています。 既に大規模な switch があり、同じ評価ブロックにヒットさせるために 2 つの項目が必要になるような場合は、これを使用することを検討します。

読みやすくするために、スクリプト ブロックをかっこ内に配置することをお勧めします。

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

定数式

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

これは、複数のブール値フィールドのステータスを評価してアクションを実行するためのクリーンな方法です。 これのおもしろい点は、まだ評価されていない値のステータスを 1 つの照合で反転させられることです。

$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 によって処理されませんが、その値に直接アクセスすることができます。 私には狂気に思えます。

その他のパターン

ハッシュテーブル

私の投稿の中で最も人気のあるものの 1 つは、ハッシュテーブルで行ったものです。 hashtable のユース ケースの 1 つはルックアップ テーブルです。 これは、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 ステートメントは表面上は単純ですが、あまり知られていない高度な機能を持っています。 これらの機能を組み合わせることで、強力な機能になります。 これまで気付がなかったことを学んでいただければ幸いです。