Udostępnij za pośrednictwem


Wszystko, co kiedykolwiek chciałeś wiedzieć o instrukcji switch

Podobnie jak w przypadku wielu innych języków program PowerShell ma polecenia służące do kontrolowania przepływu wykonywania w skryptach. Jedną z tych instrukcji jest switch instrukcji i w programie PowerShell oferuje funkcje, które nie znajdują się w innych językach. Obecnie szczegółowo omówimy pracę z programem PowerShell switch.

Uwaga

Oryginalna wersja tego artykułu pojawiła się na blogu prowadzonym przez @KevinMarquette. Zespół programu PowerShell dziękuje Kevinowi za udostępnienie tej zawartości nam. Zapoznaj się ze swoim blogiem na PowerShellExplained.com.

Instrukcja if

Jedną z pierwszych instrukcji, które poznasz, jest instrukcja if. Umożliwia wykonanie bloku skryptu, jeśli twierdzenie jest $true.

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

Można mieć znacznie bardziej skomplikowaną logikę przy użyciu instrukcji elseif i else. Oto przykład, w którym mam wartość liczbową dla dnia tygodnia i chcę uzyskać nazwę jako ciąg.

$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

Okazuje się, że jest to typowy wzorzec i istnieje wiele sposobów radzenia sobie z tym. Jednym z nich jest "switch".

Instrukcja switch

Instrukcja switch umożliwia podanie zmiennej i listy możliwych wartości. Jeśli wartość odpowiada zmiennej, wówczas zostanie wykonany jej blok skryptowy.

$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'

W tym przykładzie wartość $day jest zgodna z jedną z wartości liczbowych, a następnie prawidłowa nazwa jest przypisywana do $result. W tym przykładzie wykonujemy tylko przypisanie zmiennych, ale w tych blokach skryptu można wykonać dowolny program PowerShell.

Przypisać do zmiennej

Możemy napisać ten ostatni przykład w inny sposób.

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

Umieszczamy wartość w potoku programu PowerShell i przypisujemy ją do $result. Możesz to zrobić za pomocą instrukcji if i foreach.

Wartość domyślna

Możemy użyć słowa kluczowego default, aby zidentyfikować, co powinno się zdarzyć, jeśli nie ma dopasowania.

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

W tym miejscu zwracamy wartość Unknown w przypadku domyślnym.

Ciągi

W tych ostatnich przykładach dopasowywałem liczby, a można również dopasować ciągi.

$item = 'Role'

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

Postanowiłem nie ujmować w cudzysłów dopasowań Component,Role i Location tutaj, aby podkreślić, że są opcjonalne. W większości przypadków switch traktuje to jako ciąg.

Tablice

Jedną z interesujących funkcji PowerShell switch jest sposób obsługi tablic. Jeśli przekażesz switch tablicę, przetworzy każdy element w tej kolekcji.

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

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

Jeśli elementy tablicy są powtarzane, są one dopasowywane wiele razy przez odpowiednią sekcję.

PsItem

Możesz użyć $PSItem lub $_, aby odwołać się do bieżącego przetworzonego elementu. Gdy wykonujemy proste dopasowanie, $PSItem jest wartością, którą porównujemy. Będę wykonywać kilka zaawansowanych dopasowań w następnej sekcji, w której jest używana ta zmienna.

Parametry

Unikatową funkcją switch programu PowerShell jest to, że ma ona wiele parametrów przełącznika, które zmieniają sposób jego działania.

-CaseSensitive

Dopasowania nie są domyślnie uwzględniane w wielkości liter. Jeśli musisz uwzględniać wielkość liter, możesz użyć -CaseSensitive. Może to być używane w połączeniu z innymi parametrami przełącznika.

-Znak wieloznaczny

Możemy włączyć obsługę symboli wieloznacznych za pomocą przełącznika -Wildcard. To wykorzystuje tę samą logikę wieloznaczników co operator -like do każdego dopasowania.

$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

W tym miejscu przetwarzamy komunikat, a następnie generujemy go na różnych kanałach w zależności od jego zawartości.

-Regex

Instrukcja switch obsługuje wyrażenia regularne, tak samo jak symbole wieloznaczne.

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

Mam więcej przykładów używania wyrażeń regularnych w innym artykule, który napisałem: Wiele sposobów użycia wyrażeń regularnych.

-Plik

Mało znaną funkcją instrukcji switch jest to, że może przetworzyć plik za pomocą parametru -File. Należy użyć -File ze ścieżką do pliku zamiast nadać mu wyrażenie zmiennej.

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

Działa tak samo jak przetwarzanie tablicy. W tym przykładzie łączę go z dopasowaniem wieloznacznym i używam $PSItem. Spowoduje to przetworzenie pliku dziennika i przekonwertowanie go na komunikaty ostrzegawcze i komunikaty o błędach w zależności od dopasowań wyrażeń regularnych.

Szczegóły zaawansowane

Teraz, gdy znasz wszystkie te udokumentowane funkcje, możemy ich używać w kontekście bardziej zaawansowanego przetwarzania.

Wyrażenia

switch może znajdować się w wyrażeniu zamiast zmiennej.

switch ( ( Get-Service | where Status -EQ 'running' ).Name ) {...}

Każda wartość, do której zostanie wyrażenie wyliczone, jest wartością używaną do dopasowania.

Wiele dopasowań

Być może już to zauważyłeś, ale switch może pasować do wielu warunków. Jest to szczególnie istotne w przypadku używania dopasowań -Wildcard lub -Regex. Ten sam warunek można dodać wiele razy i wszystkie są wyzwalane.

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

Wszystkie trzy z tych instrukcji są wyzwalane. Pokazuje to, że każdy warunek jest sprawdzany (w kolejności). Dotyczy to przetwarzania tablic, w których każdy element sprawdza każdy warunek.

Kontynuuj

Zwykle jest to miejsce, w którym wprowadziłbym oświadczenie break, ale lepiej jest nauczyć się używać continue najpierw. Podobnie jak w przypadku pętli foreach, continue przechodzi do następnego elementu w kolekcji lub opuszcza switch, jeśli nie ma więcej elementów. Możemy ponownie napisać ten ostatni przykład z instrukcjami continue, aby wykonać tylko jedną instrukcję.

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

Zamiast dopasowywać wszystkie trzy elementy, pierwszy z nich jest dopasowywany, a przełącznik przechodzi do następnej wartości. Ponieważ nie ma wartości do przetworzenia, przełącznik kończy działanie. W następnym przykładzie pokazano, jak symbol wieloznaczny może dopasować się do wielu elementów.

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

Ponieważ wiersz w pliku wejściowym może zawierać zarówno wyraz Error, jak i Warning, chcemy wykonać tylko pierwszy wiersz, a następnie kontynuować przetwarzanie pliku.

Przerwa

Instrukcja break kończy przełączanie. Jest to takie samo zachowanie, które continue przedstawia dla pojedynczych wartości. Różnica jest wyświetlana podczas przetwarzania tablicy. break zatrzymuje wszystkie operacje przetwarzania w przełączniku i continue przechodzi na następny element.

$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

W takim przypadku, jeśli trafimy na wiersze rozpoczynające się od Error zostanie wyświetlony błąd i przełącznik zostanie zatrzymany. To jest to, co to break oświadczenie robi dla nas. Jeśli znajdziemy Error wewnątrz ciągu, a nie tylko na początku, zapisujemy go jako ostrzeżenie. Robimy to samo dla Warning. Możliwe, że linia może zawierać zarówno słowo Error, jak i Warning, ale potrzebujemy tylko jednego do przetworzenia. To jest to, co robi dla nas deklaracja continue.

Podział etykiet

Instrukcja switch obsługuje etykiety break/continue tak samo jak 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
        }
    }
}

Osobiście nie lubię używania etykiet break, ale chciałem je wskazać, ponieważ są one mylące, jeśli nigdy wcześniej ich nie widziałeś. Jeśli masz wiele instrukcji switch lub foreach, które są zagnieżdżone, możesz chcieć przerwać więcej niż tylko wewnętrzny element. Etykietę można umieścić na switch, która może być celem dla Twojego break.

Wyliczenie

Program PowerShell 5.0 dał nam wyliczenia i możemy użyć ich w przełączniku.

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

Jeśli chcesz zachować wszystko jako mocno typizowane wyliczenia, możesz umieścić je w nawiasach.

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

Nawiasy są tutaj potrzebne, aby przełącznik nie traktował wartości [Context]::Location jako literału tekstowego.

ScriptBlock

W razie potrzeby możemy użyć bloku skryptu, aby przeprowadzić ocenę dopasowania.

$age = 37

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

To zwiększa złożoność i może sprawić, że switch staje się trudne do odczytania. W większości przypadków, w których należy użyć czegoś takiego, lepiej byłoby użyć instrukcji if i elseif. Rozważyłbym użycie tego, jeśli miałbym już duży przełącznik i potrzebowałbym dwóch elementów, aby trafiły do tego samego bloku oceny.

Jedną z rzeczy, która moim zdaniem poprawia czytelność, jest umieszczenie "scriptblock" w nawiasach.

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

Nadal wykonuje to samo działanie i daje lepszą przerwę wizualną podczas szybkiego przeglądania.

Dopasowania wyrażeń regularnych $Matches

Musimy ponownie zwrócić się do regex, aby dotknąć czegoś, co nie jest natychmiast oczywiste. Użycie wyrażenia regularnego wypełnia zmienną $Matches. Zajmuję się użyciem $Matches bardziej szczegółowo, gdy mówię o wielu sposobach użycia regex. Oto szybki przykład ilustrujący jego działanie z nazwanymi grupami dopasowań.

$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

Możesz dopasować wartość $null, która nie musi być wartością domyślną.

$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

Podczas testowania pustego ciągu w instrukcji switch należy użyć instrukcji porównania, jak pokazano w tym przykładzie zamiast wartości pierwotnej ''. W instrukcji switch wartość nieprzetworzona '' jest również zgodna z $null. Na przykład:

$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

Ponadto należy zachować ostrożność przy użyciu pustych wyników z cmdlet. Polecenia cmdlet lub potoki bez danych wyjściowych są traktowane jako pusta tablica, która nie pasuje do niczego, łącznie z przypadkiem default.

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

Wyrażenie stałe

Lee Dailey zwrócił uwagę, że możemy użyć stałego wyrażenia $true do oceny elementów [bool]. Wyobraź sobie, że mamy kilka sprawdzeń logicznych, które muszą zostać przeprowadzone.

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

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

Jest to czysty sposób oceniania i podejmowania działań na temat stanu kilku pól logicznych. Fajną rzeczą w tym jest to, że jedno porównanie może zmienić stan wartości, która nie została jeszcze oceniona.

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

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

Ustawienie $isEnabled na $true w tym przykładzie gwarantuje, że $isVisible jest również ustawiona na wartość $true. Następnie, gdy $isVisible zostanie oceniony, zostanie wywołany jego blok skryptu. Jest to nieco nieintuicyjne, ale stanowi sprytne zastosowanie mechaniki.

$Switch zmienna automatyczna

Gdy switch przetwarza swoje wartości, tworzy moduł wyliczający i wywołuje go $switch. Jest to zmienna automatyczna utworzona przez program PowerShell i można nią manipulować bezpośrednio.

$a = 1, 2, 3, 4

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

Daje to wyniki:

2
4

Przenosząc moduł wyliczający do przodu, następny element nie jest przetwarzany przez switch, ale możesz uzyskać bezpośredni dostęp do tej wartości. Nazwałbym to szaleństwem.

Inne wzorce

Tabele skrótów

Jednym z moich najpopularniejszych postów jest ten, który zrobiłem na hashtables. Jednym z przypadków użycia hashtable jest tabela przeglądowa. Jest to alternatywne podejście do wspólnego wzorca, do którego często adresuje się instrukcja switch.

$day = 3

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

$lookup[$day]
Wednesday

Jeśli używam tylko switch do wyszukiwania, często używam hashtable.

Enum

Program PowerShell 5.0 wprowadził funkcję enum, która jest także dostępna w tym przypadku.

$day = 3

enum DayOfTheWeek {
    Sunday
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
}

[DayOfTheWeek]$day
Wednesday

Moglibyśmy spędzić cały dzień, przyglądając się różnym sposobom rozwiązania tego problemu. Chciałem tylko upewnić się, że wiesz, że masz opcje.

Końcowe wyrazy

Instrukcja switch na pierwszy rzut oka jest prosta, ale oferuje zaawansowane funkcje, których istnienia większość ludzi sobie nie uświadamia. Łączenie tych funkcji sprawia, że jest to zaawansowana funkcja. Mam nadzieję, że nauczyłeś się czegoś, czego wcześniej nie zdałeś sobie sprawy.