Condividi tramite


Tutto ciò che hai mai voluto sapere sull'istruzione "switch"

Analogamente a molti altri linguaggi, PowerShell include comandi per controllare il flusso di esecuzione all'interno degli script. Una di queste istruzioni è l'istruzione switch e in PowerShell offre funzionalità non disponibili in altri linguaggi. Oggi viene illustrato in modo approfondito l'uso di PowerShell switch.

Nota

La versione originale di questo articolo è apparsa nel blog scritto da @KevinMarquette. Il team di PowerShell ringrazia Kevin per aver condiviso questo contenuto con noi. Consultare il suo blog all'indirizzo PowerShellExplained.com.

Istruzione if

Una delle prime istruzioni che si imparano è la dichiarazione if. Consente di eseguire un blocco di script se un'istruzione è $true.

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

È possibile avere una logica molto più complessa usando elseif e istruzioni else. Di seguito è riportato un esempio in cui è presente un valore numerico per il giorno della settimana e si vuole ottenere il nome come stringa.

$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

Si scopre che questo è un modello comune e ci sono molti modi per gestire questo problema. Uno di essi è con un switch.

Istruzione switch

L'istruzione switch consente di fornire una variabile e un elenco di valori possibili. Se il valore corrisponde alla variabile, viene eseguito il relativo 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'

Per questo esempio, il valore di $day corrisponde a uno dei valori numerici, quindi il nome corretto viene assegnato a $result. In questo esempio viene eseguita solo un'assegnazione di variabile, ma qualsiasi PowerShell può essere eseguito in tali blocchi di script.

Assegnare a una variabile

È possibile scrivere l'ultimo esempio in un altro modo.

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

Si inserisce il valore nella pipeline di PowerShell e lo si assegna al $result. È possibile eseguire questa stessa operazione con le istruzioni if e foreach.

Impostazione predefinita

È possibile usare la parola chiave default per identificare ciò che deve accadere se non esiste alcuna corrispondenza.

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

In questo caso viene restituito il valore Unknown nel caso predefinito.

Stringhe

Stavo abbinando numeri in quegli ultimi esempi, ma puoi anche abbinare stringhe.

$item = 'Role'

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

Ho deciso di non inserire tra virgolette le corrispondenze Component,Role e Location qui per evidenziare che sono facoltative. La switch considera tali come una stringa nella maggior parte dei casi.

Matrici

Una delle funzionalità interessanti del switch di PowerShell è il modo in cui gestisce le matrici. Se dai al switch una matrice, elabora ogni elemento di quella raccolta.

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

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

Se nella matrice sono presenti elementi ripetuti, questi vengono confrontati più volte dalla sezione appropriata.

PSItem

Puoi utilizzare il $PSItem o il $_ per fare riferimento all'elemento corrente che è stato elaborato. Quando facciamo un semplice confronto, $PSItem è il valore che stiamo confrontando. Eseguirò alcuni confronti avanzati nella sezione successiva in cui viene utilizzata questa variabile.

Parametri

Una funzionalità univoca del switch di PowerShell è che include diversi parametri switch che cambiano il modo in cui opera.

-CaseSensitive

Le corrispondenze non fanno distinzione tra maiuscole e minuscole per impostazione predefinita. Se è necessario fare distinzione tra maiuscole e minuscole, è possibile usare -CaseSensitive. Può essere usato in combinazione con gli altri parametri switch.

-Carattere jolly

È possibile abilitare il supporto per i caratteri jolly con l'interruttore -Wildcard. Questo utilizza la stessa logica dei caratteri jolly dell'operatore -like per effettuare ogni corrispondenza.

$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

In questo caso viene elaborato un messaggio e quindi restituito su flussi diversi in base al contenuto.

-Regex

L'istruzione switch supporta le corrispondenze con le espressioni regolari (regex) esattamente come avviene con i caratteri jolly.

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

Ho più esempi di uso di regex in un altro articolo ho scritto: I molti modi per usare regex.

-File

Una funzionalità poco nota dell'istruzione switch è che può elaborare un file con il parametro -File. È possibile usare -File con un percorso di un file anziché assegnargli un'espressione di variabile.

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

Funziona esattamente come l'elaborazione di una matrice. In questo esempio, lo combino con la corrispondenza con caratteri jolly e utilizzo il $PSItem. Questo processo elabora un file di log e lo converte in messaggi di avviso e di errore a seconda delle corrispondenze regex.

Dettagli avanzati

Ora che si è a conoscenza di tutte queste funzionalità documentate, è possibile usarle nel contesto di elaborazione più avanzata.

Espressioni

Il switch può trovarsi in un'espressione anziché in una variabile.

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

Il valore a cui l'espressione viene valutata è utilizzato per il confronto.

Più corrispondenze

Potresti aver già notato che un switch può corrispondere a più condizioni. Ciò è particolarmente vero quando si usano le corrispondenze -Wildcard o -Regex. È possibile aggiungere la stessa condizione più volte e tutti vengono attivati.

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

Tutte e tre queste affermazioni sono licenziate. Ciò mostra che ogni condizione viene controllata (in ordine). Questo vale per l'elaborazione di matrici in cui ogni elemento controlla ogni condizione.

Continuare

In genere, è qui che introdurrei l'istruzione break, ma è meglio imparare a usare continue prima. Proprio come con un ciclo di foreach, continue continua con l'elemento successivo nella raccolta o esce dal switch se non sono presenti altri elementi. È possibile riscrivere l'ultimo esempio con istruzioni continue in modo che venga eseguita una sola istruzione.

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

Anziché abbinare tutti e tre gli elementi, il primo viene abbinato e il passaggio continua al valore successivo. Poiché non sono presenti valori lasciati per l'elaborazione, l'opzione viene chiusa. Questo prossimo esempio mostra come un carattere jolly possa corrispondere a più elementi.

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

Poiché una riga nel file di input può contenere sia la parola Error che Warning, si vuole eseguire solo la prima e quindi continuare l'elaborazione del file.

Pausa

Un'istruzione break esce dal switch. Si tratta dello stesso comportamento che continue presenta per i singoli valori. La differenza viene mostrata durante l'elaborazione di una matrice. break arresta tutti i processi nel switch e continue passa all'elemento successivo.

$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

In questo caso, se si raggiungono linee che iniziano con Error, viene visualizzato un errore e l'opzione si arresta. Questo è ciò che l'affermazione break sta facendo per noi. Se troviamo Error all'interno della stringa e non solo all'inizio, lo scriviamo come avviso. Si esegue la stessa operazione per Warning. È possibile che una riga abbia sia la parola Error che Warning, ma ne è necessaria una sola da elaborare. Questo è ciò che l'affermazione continue sta facendo per noi.

Dividi etichette

L'istruzione switch supporta etichette break/continue esattamente come 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
        }
    }
}

Personalmente non mi piace l'uso di etichette di interruzione, ma volevo segnalarle perché sono confuse se non le hai mai viste prima. Quando si dispone di più istruzioni switch o foreach annidate, è possibile che si voglia uscire più dell'elemento più interno.When you have multiple switch or foreach statement that are nested, you may want to break out of more than the inner most item. È possibile applicare un'etichetta su un switch che può essere destinato al tuo break.

Enum

PowerShell 5.0 ha fornito enumerazioni ed è possibile usarle in un commutatore.

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

Se si desidera mantenere tutto come enumerazioni fortemente tipate, è possibile inserirli tra parentesi.

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

Le parentesi sono necessarie qui in modo che l'opzione non consideri il valore [Context]::Location come stringa letterale.

ScriptBlock

È possibile usare uno scriptblock per eseguire la valutazione per una corrispondenza, se necessario.

$age = 37

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

Questo aggiunge complessità e può rendere il tuo switch difficile da leggere. Nella maggior parte dei casi in cui si userebbe qualcosa del genere, sarebbe preferibile utilizzare le istruzioni if e elseif. Prenderei in considerazione l'uso di questo se avessi già un grande interruttore sul posto e avevo bisogno di due elementi per raggiungere lo stesso blocco di valutazione.

Una cosa che penso aiuta con la leggibilità è inserire il blocco di script tra parentesi.

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

Viene comunque eseguito allo stesso modo e offre un'interruzione visiva migliore quando lo si esamina rapidamente.

Regex $Matches

Dobbiamo rivedere regex per toccare qualcosa che non è immediatamente ovvio. L'uso di regex popola la variabile $Matches. Si passa all'uso di $Matches di più quando si parla di I molti modi per usare regex. Ecco un esempio rapido per mostrarlo in azione con corrispondenze nominate.

$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

È possibile associare un valore $null che non deve essere quello predefinito.

$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

Quando si testa una stringa vuota in un'istruzione switch, è importante usare l'istruzione di confronto come illustrato in questo esempio anziché il valore non elaborato ''. In un'istruzione switch il valore non elaborato '' corrisponde anche a $null. Per esempio:

$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

Prestare inoltre attenzione ai risultati vuoti dei cmdlet. I cmdlet o le pipeline senza output vengono considerati come una matrice vuota che non corrisponde a nulla, incluso il caso default.

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

Espressione costante

Lee Dailey ha sottolineato che è possibile usare un'espressione di $true costante per valutare [bool] elementi. Immagina di avere diversi controlli booleani che devono essere eseguiti.

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

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

Si tratta di un modo pulito per valutare e intervenire sullo stato di diversi campi booleani. L'aspetto interessante di questo è che si può far sì che una corrispondenza capovolga lo stato di un valore che non è ancora stato valutato.

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

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

L'impostazione di $isEnabled su $true in questo esempio garantisce che $isVisible sia impostato anche su $true. Quindi, quando $isVisible viene valutato, viene richiamato il relativo blocco di script. Si tratta di un po' controintuitivo, ma è un uso intelligente delle meccaniche.

variabile automatica $switch

Quando l'switch elabora i valori, crea un enumeratore e lo chiama $switch. Si tratta di una variabile automatica creata da PowerShell ed è possibile modificarla direttamente.

$a = 1, 2, 3, 4

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

In questo modo si ottengono i risultati di:

2
4

Spostando l'enumeratore in avanti, l'elemento successivo non viene elaborato dal switch, ma è possibile accedere direttamente a tale valore. Lo chiamerei follia.

Altri modelli

Tabelle hash

Uno dei miei post più popolari è quello che ho fatto sulle tabelle di hash . Uno dei casi d'uso per un hashtable consiste nell'essere una tabella di ricerca. Si tratta di un approccio alternativo a un modello comune che un'istruzione switch spesso tratta.

$day = 3

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

$lookup[$day]
Wednesday

Se utilizzo solo un switch per la ricerca, spesso uso un hashtable.

Enum

PowerShell 5.0 ha introdotto il enum ed è anche un'opzione in questo caso.

$day = 3

enum DayOfTheWeek {
    Sunday
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
}

[DayOfTheWeek]$day
Wednesday

Potremmo andare tutti i giorni a guardare diversi modi per risolvere questo problema. Volevo solo assicurarti che sapessi che avevi delle opzioni.

Parole finali

L'istruzione switch è semplice in superficie, ma offre alcune funzionalità avanzate che la maggior parte delle persone non sa essere disponibili. Collegare insieme queste funzionalità le rende una caratteristica potente. Spero che tu avessi imparato qualcosa che non avevi capito prima.