Tudo o que você sempre quis saber sobre a declaração de switch

Como muitas outras linguagens, o PowerShell tem comandos para controlar o fluxo de execução em seus scripts. Uma dessas instruções é a instrução switch e, no PowerShell, oferece recursos que não são encontrados em outros idiomas. Hoje, mergulhamos fundo no trabalho com o PowerShell switch.

Nota

A versão original deste artigo apareceu no blog escrito por @KevinMarquette. A equipe do PowerShell agradece Kevin por compartilhar esse conteúdo conosco. Por favor, confira seu blog em PowerShellExplained.com.

A if declaração

Uma das primeiras afirmações que você aprende é a if declaração. Ele permite executar um bloco de script se uma instrução for $true.

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

Você pode ter uma lógica muito mais complicada usando elseif e else instruções. Aqui está um exemplo em que tenho um valor numérico para o dia da semana e quero obter o nome como uma cadeia de caracteres.

$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

Acontece que este é um padrão comum e há muitas maneiras de lidar com isso. Um deles é com um switcharquivo .

Instrução Switch

A switch instrução permite que você forneça uma variável e uma lista de valores possíveis. Se o valor corresponder à variável, seu scriptblock será executado.

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

Neste exemplo, o valor de $day corresponde a um dos valores numéricos e, em seguida, o nome correto é atribuído a $result. Estamos fazendo apenas uma atribuição variável neste exemplo, mas qualquer PowerShell pode ser executado nesses blocos de script.

Atribuir a uma variável

Podemos escrever este último exemplo de outra forma.

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

Estamos colocando o valor no pipeline do PowerShell e atribuindo-o ao $result. Você pode fazer a mesma coisa com as if declarações e foreach .

Predefinido

Podemos usar a default palavra-chave para identificar o que deve acontecer se não houver correspondência.

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

Aqui retornamos o valor Unknown no caso padrão.

Cadeias

Eu estava combinando números nesses últimos exemplos, mas você também pode combinar strings.

$item = 'Role'

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

Decidi não embrulhar o Component,Role e Location as correspondências entre aspas aqui para destacar que elas são opcionais. O switch trata esses como uma corda na maioria dos casos.

Matrizes

Um dos recursos interessantes do PowerShell switch é a maneira como ele lida com matrizes. Se você fornecer uma switch matriz, ela processará cada elemento dessa coleção.

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

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

Se você tiver itens repetidos em sua matriz, eles serão correspondidos várias vezes pela seção apropriada.

PSItem

Você pode usar o $PSItem ou $_ para fazer referência ao item atual que foi processado. Quando fazemos uma partida simples, $PSItem é o valor que estamos a corresponder. Vou realizar algumas partidas avançadas na próxima seção onde essa variável é usada.

Parâmetros

Um recurso exclusivo do PowerShell switch é que ele tem vários parâmetros de switch que alteram seu desempenho.

-Sensível a maiúsculas e minúsculas

As correspondências não diferenciam maiúsculas de minúsculas por padrão. Se precisar diferenciar maiúsculas de minúsculas, você pode usar -CaseSensitiveo . Isso pode ser usado em combinação com os outros parâmetros do switch.

-Curinga

Podemos ativar o suporte a curingas com o -wildcard switch. Isso usa a mesma lógica curinga que o -like operador para fazer cada correspondência.

$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

Aqui estamos processando uma mensagem e, em seguida, enviando-a em diferentes fluxos com base no conteúdo.

-Regex

A instrução switch suporta correspondências regex da mesma forma que curinga.

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

Tenho mais exemplos de uso de regex em outro artigo que escrevi: As muitas maneiras de usar regex.

-Arquivo

Uma característica pouco conhecida da instrução switch é que ele pode processar um arquivo com o -File parâmetro. Você usa -file com um caminho para um arquivo em vez de dar-lhe uma expressão variável.

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

Funciona como processar uma matriz. Neste exemplo, combino-o com a correspondência curinga e uso o $PSItem. Isso processaria um arquivo de log e o converteria em mensagens de aviso e erro, dependendo das correspondências de regex.

Detalhes avançados

Agora que você está ciente de todos esses recursos documentados, podemos usá-los no contexto de processamento mais avançado.

Expressões

O switch pode estar em uma expressão em vez de uma variável.

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

Qualquer que seja a avaliação da expressão é o valor usado para a correspondência.

Correspondências múltiplas

Você pode já ter percebido isso, mas um switch pode corresponder a várias condições. Isto é especialmente verdadeiro quando se usa -wildcard ou -regex combina. Você pode adicionar a mesma condição várias vezes e todas são acionadas.

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

Todas essas três declarações são demitidas. Isso mostra que todas as condições são verificadas (em ordem). Isso vale para matrizes de processamento em que cada item verifica cada condição.

Continuar

Normalmente, é aqui que eu introduziria a break declaração, mas é melhor que aprendamos a usar continue primeiro. Assim como com um foreach loop, continue continua para o próximo item da coleção ou sai do switch se não houver mais itens. Podemos reescrever esse último exemplo com instruções continue para que apenas uma instrução seja executada.

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

Em vez de corresponder aos três itens, o primeiro é correspondido e o switch continua para o próximo valor. Como não há mais valores para processar, o switch é encerrado. Este próximo exemplo mostra como um curinga pode corresponder a vários itens.

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

Como uma linha no arquivo de entrada pode conter a palavra Error e Warning, queremos apenas que a primeira seja executada e, em seguida, continue processando o arquivo.

Pausa

Uma break instrução sai do switch. Este é o mesmo comportamento que continue se apresenta para valores únicos. A diferença é mostrada ao processar uma matriz. break interrompe todo o processamento no interruptor e continue passa para o próximo item.

$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

Neste caso, se acertarmos qualquer linha que comece com Error , então obtemos um erro e o interruptor para. É isso que essa break declaração está a fazer por nós. Se encontrarmos Error dentro da corda e não apenas no início, escrevemos como um aviso. Fazemos a mesma coisa para Warning. É possível que uma linha possa ter a palavra Error e Warning, mas só precisamos de uma para processar. É isso que a declaração está a continue fazer por nós.

Quebrar rótulos

A switch declaração suporta break/continue rótulos como 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
        }
    }
}

Eu pessoalmente não gosto do uso de rótulos de quebra, mas eu queria apontá-los porque eles são confusos se você nunca os viu antes. Quando você tem várias switch instruções ou foreach que estão aninhadas, você pode querer quebrar de mais do que o item mais interno. Você pode colocar um rótulo em um switch que pode ser o alvo do seu break.

Enumeração

O PowerShell 5.0 nos deu enums e podemos usá-los em um 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

Se você quiser manter tudo como enums fortemente tipados, então você pode colocá-los entre parênteses.

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

Os parênteses são necessários aqui para que o switch não trate o valor [Context]::Location como uma cadeia de caracteres literal.

ScriptBlock

Podemos usar um scriptblock para realizar a avaliação de uma correspondência, se necessário.

$age = 37

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

Isso aumenta a complexidade e pode dificultar a switch leitura. Na maioria dos casos em que você usaria algo assim, seria melhor usar if e elseif declarações. Eu consideraria usar isso se eu já tivesse um grande interruptor no lugar e eu precisasse de dois itens para acertar o mesmo bloco de avaliação.

Uma coisa que eu acho que ajuda na legibilidade é colocar o scriptblock entre parênteses.

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

Ele ainda executa da mesma maneira e dá uma melhor pausa visual ao olhar rapidamente para ele.

Regex $matches

Precisamos revisitar o regex para tocar em algo que não é imediatamente óbvio. O uso de regex preenche a $matches variável. Eu entro no uso de mais quando eu falo sobre as muitas maneiras de $matches usar regex. Aqui está um exemplo rápido para mostrá-lo em ação com correspondências nomeadas.

$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

Você pode corresponder a um $null valor que não precisa ser o padrão.

$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

Ao testar uma cadeia de caracteres vazia em uma switch instrução, é importante usar a instrução de comparação como mostrado neste exemplo em vez do valor ''bruto. Em uma switch instrução, o valor '' bruto também corresponde a $null. Por exemplo:

$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

Além disso, tenha cuidado com retornos vazios de cmdlets. Os cmdlets ou pipelines que não têm saída são tratados como uma matriz vazia que não corresponde a nada, incluindo o default caso.

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

Expressão constante

Lee Dailey apontou que podemos usar uma expressão constante $true para avaliar [bool] itens. Imagine se tivermos várias verificações booleanas que precisam acontecer.

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

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

Esta é uma maneira limpa de avaliar e agir sobre o status de vários campos booleanos. O legal disso é que você pode fazer com que uma correspondência inverta o status de um valor que ainda não foi avaliado.

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

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

A configuração $isEnabled como $true neste exemplo garante que $isVisible também esteja definida como $true. Em seguida, quando $isVisible é avaliado, seu scriptblock é invocado. Isso é um pouco contraintuitivo, mas é um uso inteligente da mecânica.

$switch variável automática

Quando o switch está processando seus valores, ele cria um enumerador e o $switchchama de . Essa é uma variável automática criada pelo PowerShell e você pode manipulá-la diretamente.

$a = 1, 2, 3, 4

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

Isto dá-lhe os resultados de:

2
4

Ao mover o enumerador para frente, o próximo item não é processado switch pelo mas você pode acessar esse valor diretamente. Eu chamaria isso de loucura.

Outros padrões

Hashtables

Um dos meus posts mais populares é o que fiz em hashtables. Um dos casos de uso para um hashtable é ser uma tabela de pesquisa. Essa é uma abordagem alternativa a um padrão comum que uma switch declaração geralmente aborda.

$day = 3

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

$lookup[$day]
Wednesday

Se eu estiver usando apenas um switch como uma pesquisa, eu costumo usar um hashtable em vez disso.

Enumeração

O PowerShell 5.0 introduziu o Enum e também é uma opção neste caso.

$day = 3

enum DayOfTheWeek {
    Sunday
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
}

[DayOfTheWeek]$day
Wednesday

Poderíamos ir o dia todo procurando diferentes maneiras de resolver esse problema. Eu só queria ter certeza de que você sabia que tinha opções.

Palavras finais

A instrução switch é simples na superfície, mas oferece alguns recursos avançados que a maioria das pessoas não percebe que estão disponíveis. Encadear esses recursos juntos torna esse um recurso poderoso. Espero que você tenha aprendido algo que você não tinha percebido antes.