Allt du någonsin velat veta om switch-instruktionen

Liksom många andra språk har PowerShell kommandon för att styra körningsflödet i dina skript. En av dessa instruktioner är switch-instruktionen och i PowerShell erbjuder den funktioner som inte finns på andra språk. Idag tar vi en djupdykning i att arbeta med PowerShell switch.

Anteckning

Den ursprungliga versionen av den här artikeln visades på bloggen skriven av @KevinMarquette. PowerShell-teamet tackar Kevin för att han har delat det här innehållet med oss. Kolla in hans blogg på PowerShellExplained.com.

Instruktionen if

En av de första instruktionerna som du lär dig är -instruktionen if . Det gör att du kan köra ett skriptblock om en -instruktion är $true.

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

Du kan ha mycket mer komplicerad logik med hjälp elseif av och else -instruktioner. Här är ett exempel där jag har ett numeriskt värde för veckodagen och jag vill hämta namnet som en sträng.

$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

Det visar sig att detta är ett vanligt mönster och det finns många sätt att hantera detta. En av dem är med en switch.

Switch-instruktion

Med instruktionen switch kan du ange en variabel och en lista över möjliga värden. Om värdet matchar variabeln körs dess 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'

I det här exemplet matchar värdet $day för ett av de numeriska värdena och sedan tilldelas rätt namn till $result. Vi utför bara en variabeltilldelning i det här exemplet, men alla PowerShell-objekt kan köras i dessa skriptblock.

Tilldela till en variabel

Vi kan skriva det sista exemplet på ett annat sätt.

$result = switch ( $day )
{
    0 { 'Sunday'    }
    1 { 'Monday'    }
    2 { 'Tuesday'   }
    3 { 'Wednesday' }
    4 { 'Thursday'  }
    5 { 'Friday'    }
    6 { 'Saturday'  }
}

Vi placerar värdet på PowerShell-pipelinen och tilldelar det till $result. Du kan göra samma sak med instruktionen if och foreach .

Standardvärde

Vi kan använda nyckelordet default för att identifiera vad som ska hända om det inte finns någon matchning.

$result = switch ( $day )
{
    0 { 'Sunday' }
    # ...
    6 { 'Saturday' }
    default { 'Unknown' }
}

Här returnerar vi värdet Unknown i standardfallet.

Strängar

Jag matchade nummer i de sista exemplen, men du kan också matcha strängar.

$item = 'Role'

switch ( $item )
{
    Component
    {
        'is a component'
    }
    Role
    {
        'is a role'
    }
    Location
    {
        'is a location'
    }
}
is a role

Jag bestämde mig för att inte omsluta Component,Role och Location matchar i citattecken här för att markera att de är valfria. Behandlar switch dem som en sträng i de flesta fall.

Matriser

En av de coola funktionerna i PowerShell switch är hur den hanterar matriser. Om du ger en switch matris bearbetas varje element i samlingen.

$roles = @('WEB','Database')

switch ( $roles ) {
    'Database'   { 'Configure SQL' }
    'WEB'        { 'Configure IIS' }
    'FileServer' { 'Configure Share' }
}
Configure IIS
Configure SQL

Om du har upprepade objekt i matrisen matchas de flera gånger av lämpligt avsnitt.

PSItem

Du kan använda $PSItem eller $_ för att referera till det aktuella objektet som bearbetades. När vi gör en enkel matchning $PSItem är det värde som vi matchar. Jag kommer att utföra några avancerade matchningar i nästa avsnitt där den här variabeln används.

Parametrar

En unik funktion i PowerShell switch är att den har ett antal växelparametrar som ändrar hur det fungerar.

-CaseSensitive

Matchningarna är inte skiftlägeskänsliga som standard. Om du behöver vara skiftlägeskänslig kan du använda -CaseSensitive. Detta kan användas i kombination med de andra växelparametrarna.

-Jokertecken

Vi kan aktivera stöd för jokertecken med växeln -wildcard . Detta använder samma logik för jokertecken som operatorn -like för att utföra varje matchning.

$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

Här bearbetar vi ett meddelande och matar sedan ut det på olika strömmar baserat på innehållet.

-Regex

Switch-instruktionen stöder regexmatchningar precis som jokertecken.

switch -Regex ( $message )
{
    '^Error'
    {
        Write-Error -Message $Message
    }
    '^Warning'
    {
        Write-Warning -Message $Message
    }
    default
    {
        Write-Information $message
    }
}

Jag har fler exempel på att använda regex i en annan artikel jag skrev: De många sätt att använda regex.

-Fil

En lite känd funktion i switch-instruktionen är att den kan bearbeta en fil med parametern -File . Du använder -file med en sökväg till en fil i stället för att ge den ett variabeluttryck.

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

Det fungerar precis som att bearbeta en matris. I det här exemplet kombinerar jag det med jokerteckenmatchning och använder $PSItem. Detta skulle bearbeta en loggfil och konvertera den till varnings- och felmeddelanden beroende på regex-matchningar.

Avancerad information

Nu när du är medveten om alla dessa dokumenterade funktioner kan vi använda dem i samband med mer avancerad bearbetning.

Uttryck

switch Kan finnas i ett uttryck i stället för en variabel.

switch ( ( Get-Service | Where status -eq 'running' ).name ) {...}

Vad uttrycket än utvärderas till är det värde som används för matchningen.

Flera matchningar

Du kanske redan har snappat upp detta, men en switch kan matcha flera villkor. Detta gäller särskilt när du använder -wildcard eller -regex matchar. Du kan lägga till samma villkor flera gånger och alla utlöses.

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

Alla tre av dessa påståenden utlöses. Detta visar att varje villkor är markerat (i ordning). Detta gäller för bearbetning av matriser där varje objekt kontrollerar varje villkor.

Fortsätt

Normalt är det här jag skulle presentera break uttalandet, men det är bättre att vi lär oss att använda continue först. Precis som med en foreach loop continue fortsätter du till nästa objekt i samlingen eller avslutar switch om det inte finns fler objekt. Vi kan skriva om det sista exemplet med continue-instruktioner så att endast en -instruktion körs.

switch ( 'Word' )
{
    'word'
    {
        'lower case word match'
        continue
    }
    'Word'
    {
        'mixed case word match'
        continue
    }
    'WORD'
    {
        'upper case word match'
        continue
    }
}
lower case word match

I stället för att matcha alla tre objekten matchas den första och växeln fortsätter till nästa värde. Eftersom det inte finns några värden kvar att bearbeta avslutas växeln. I nästa exempel visas hur ett jokertecken kan matcha flera objekt.

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

Eftersom en rad i indatafilen kan innehålla både ordet Error och Warningvill vi bara att den första ska köras och sedan fortsätta bearbeta filen.

Bryt ned

En break instruktion avslutar växeln. Det här är samma beteende som continue visas för enskilda värden. Skillnaden visas vid bearbetning av en matris. break stoppar all bearbetning i växeln och continue flyttar till nästa objekt.

$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

I det här fallet, om vi träffar några rader som börjar med Error så får vi ett fel och växeln stoppas. Detta är vad det break uttalandet gör för oss. Om vi hittar Error i strängen och inte bara i början skriver vi den som en varning. Vi gör samma sak för Warning. Det är möjligt att en rad kan ha både ordet Error och Warning, men vi behöver bara en för att bearbeta. Det här är vad uttalandet continue gör för oss.

Bryt etiketter

-instruktionen switch stöder break/continue etiketter precis som 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
        }
    }
}

Personligen gillar jag inte användningen av brytetiketter men jag ville påpeka dem eftersom de är förvirrande om du aldrig har sett dem förut. När du har flera switch eller foreach -instruktioner som är kapslade kanske du vill bryta dig ur mer än det inre objektet. Du kan placera en etikett på en switch som kan vara målet för din break.

Enum

PowerShell 5.0 gav oss uppräkningar och vi kan använda dem i en växel.

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

Om du vill behålla allt som starkt skrivna uppräkningar kan du placera dem i parenteser.

switch ($item )
{
    ([Context]::Component)
    {
        'is a component'
    }
    ([Context]::Role)
    {
        'is a role'
    }
    ([Context]::Location)
    {
        'is a location'
    }
}

Parenteserna behövs här så att växeln inte behandlar värdet [Context]::Location som en literalsträng.

ScriptBlock

Vi kan använda en scriptblock för att utföra utvärderingen för en matchning om det behövs.

$age = 37

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

Detta ökar komplexiteten och kan göra det switch svårt att läsa. I de flesta fall där du skulle använda något liknande skulle det vara bättre att använda if och elseif instruktioner. Jag skulle överväga att använda detta om jag redan hade en stor växel på plats och jag behövde två objekt för att träffa samma utvärderingsblock.

En sak som jag tror hjälper med läsbarhet är att placera scriptblock i parenteser.

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

Den körs fortfarande på samma sätt och ger en bättre visuell paus när du snabbt tittar på den.

Regex $matches

Vi måste gå tillbaka till regex för att beröra något som inte är omedelbart uppenbart. Användningen av regex fyller i variabeln $matches . Jag går in i användningen av $matches mer när jag talar om De många sätt att använda regex. Här är ett snabbt exempel för att visa den i praktiken med namngivna matchningar.

$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

Du kan matcha ett $null värde som inte behöver vara standardvärdet.

$values = '', 5, $null
switch ( $values )
{
    $null   { "Value '$_' is `$null or an empty string" }
    ''      { "Value '$_' is an empty string" }
    default { "Value [$_] is not an empty string or `$null" }
}

Observera att det tomma strängvärdet inte matchar $null utan $null matchar både $null och en tom sträng.

Value '' is an empty string
Value [5] is not an empty string or $null
Value '' is $null or an empty string
Value '' is an empty string

Var också försiktig med tomma returer från cmdletar. Cmdletar eller pipelines som inte har några utdata behandlas som en tom matris som inte matchar något, inklusive ärendet default .

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

Konstant uttryck

Lee Dailey påpekade att vi kan använda ett konstant $true uttryck för att utvärdera [bool] objekt. Tänk dig om vi har flera booleska kontroller som måste göras.

$isVisible = $false
$isEnabled = $true
$isSecure = $true

switch ( $true )
{
    $isEnabled
    {
        'Do-Action'
    }
    $isVisible
    {
        'Show-Animation'
    }
    $isSecure
    {
        'Enable-AdminMenu'
    }
}
Do-Action
Enabled-AdminMenu

Det här är ett rent sätt att utvärdera och vidta åtgärder för status för flera booleska fält. Det coola med det här är att du kan ha en matchning som vänder statusen för ett värde som inte har utvärderats ännu.

$isVisible = $false
$isEnabled = $true
$isAdmin = $false

switch ( $true )
{
    $isEnabled
    {
        'Do-Action'
        $isVisible = $true
    }
    $isVisible
    {
        'Show-Animation'
    }
    $isAdmin
    {
        'Enable-AdminMenu'
    }
}
Do-Action
Show-Animation

Om du anger $isEnabled i det här exemplet ser du till $true att $isVisible även är inställt på $true. $isVisible När sedan utvärderas anropas dess skriptblockering. Detta är lite kontraintuitivt men är en smart användning av mekaniken.

$switch automatisk variabel

switch När bearbetar dess värden skapar den en uppräkning och anropar den $switch. Det här är en automatisk variabel som skapats av PowerShell och du kan ändra den direkt.

$a = 1, 2, 3, 4

switch($a) {
    1 { [void]$switch.MoveNext(); $switch.Current }
    3 { [void]$switch.MoveNext(); $switch.Current }
}

Detta ger dig resultatet av:

2
4

Genom att flytta uppräknaren framåt bearbetas inte nästa objekt av switch men du kan komma åt det värdet direkt. Jag skulle kalla det galenskap.

Andra mönster

Hashtables

En av mina mest populära inlägg är den jag gjorde på hashtables. Ett av användningsfallen för en hashtable är att vara en uppslagstabell. Det är en alternativ metod för ett vanligt mönster som en switch instruktion ofta hanterar.

$day = 3

$lookup = @{
    0 = 'Sunday'
    1 = 'Monday'
    2 = 'Tuesday'
    3 = 'Wednesday'
    4 = 'Thursday'
    5 = 'Friday'
    6 = 'Saturday'
}

$lookup[$day]
Wednesday

Om jag bara använder en switch som en sökning använder jag ofta en hashtable istället.

Enum

PowerShell 5.0 introducerade Enum och det är också ett alternativ i det här fallet.

$day = 3

enum DayOfTheWeek {
    Sunday
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
}

[DayOfTheWeek]$day
Wednesday

Vi kan gå hela dagen och titta på olika sätt att lösa det här problemet. Jag ville bara vara säker på att du visste att du hade alternativ.

Sista ord

Switch-instruktionen är enkel på ytan, men den erbjuder vissa avancerade funktioner som de flesta inte inser är tillgängliga. Om du kombinerar dessa funktioner blir det här en kraftfull funktion. Jag hoppas att du har lärt dig något som du inte hade insett förut.