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

Como muitos outros idiomas, 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, ela oferece recursos que não são encontrados em outras linguagens. Hoje, vamos nos aprofundar no trabalho com o PowerShell switch.

Observação

A versão original deste artigo foi publicada no blog escrito por @KevinMarquette. A equipe do PowerShell agradece kevin por compartilhar este conteúdo conosco. Confira o blog dele no PowerShellExplained.com.

A instrução if

Uma das primeiras instruções que você aprende é a instrução if. 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 esse é um padrão comum e há muitas maneiras de lidar com isso. Um deles está com um switch.

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, o bloco de instrução 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'

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

Atribuir a uma variável

Podemos escrever esse último exemplo de outra maneira.

$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 instruções if e foreach.

Padrão

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.

Cordas

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

$item = 'Role'

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

Decidi não colocar as correspondências Component, Role e Location entre aspas aqui para destacar que elas são opcionais. O switch os trata como uma cadeia de caracteres 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 nessa coleção.

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

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

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

PSItem

Você pode usar o $PSItem ou o $_ para referenciar o item atual que foi processado. Quando fazemos uma correspondência simples, $PSItem é o valor que estamos tentando igualar. Executarei algumas correspondências avançadas na próxima seção em que essa variável é usada.

Parâmetros

Um recurso exclusivo do PowerShell switch é que ele tem vários [switch] parâmetros que alteram o desempenho dele.

-CaseSensitive

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

-Curinga

Podemos habilitar o suporte curinga com o -Wildcard[switch] parâmetro. Isso usa a mesma lógica curinga que o operador -like para executar 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, gerando-a em fluxos diferentes com base no conteúdo.

-Regex

A instrução switch dá suporte a correspondências regex exatamente como faz com curingas.

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

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

-Arquivo

Um recurso pouco conhecido 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
    }
}

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

Detalhes avançados

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

Expressions

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

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

O que quer que a expressão seja avaliada é o valor usado para a correspondência.

Várias correspondências

Você talvez já tenha percebido, mas switch pode corresponder a várias condições. Isso é especialmente verdadeiro ao usar -Wildcard ou -Regex correspondências. Você pode adicionar a mesma condição várias vezes e todas são ativadas.

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 executadas. 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 apresentaria a break declaração, mas é melhor que aprendamos a usar continue primeiro. Assim como em um foreach loop, continue continua com o próximo item na coleção ou sai do switch se não houver mais itens. Podemos reescrever esse último exemplo com instruções de continuação 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 a opção continua para o próximo valor. Como não há valores a serem processados, a opção é encerrada. 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.

Interromper

Uma break instrução sai da opção. Esse é o mesmo comportamento que continue apresenta para valores únicos. A diferença é mostrada ao processar uma matriz. break interrompe todo o processamento na opção 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

Nesse caso, se atingirmos qualquer linha que comece com Error, receberemos um erro e a execução do switch será interrompida. Isso é o que essa break declaração está fazendo por nós. Se encontrarmos Error dentro da cadeia de caracteres e não apenas no início, a escreveremos como um aviso. Fazemos a mesma coisa por Warning. É possível que uma linha possa ter a palavra Error e Warning, mas só precisamos de uma para processar. Isso é o que a continue declaração está fazendo por nós.

Fragmentar etiquetas

A instrução switch oferece suporte a rótulos break/continue assim 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 labels de break, mas eu queria apontá-los porque eles podem ser confusos se você nunca os viu previamente. Quando você tem várias instruções switch ou foreach aninhadas, talvez queira sair de mais de um item, não apenas do mais interno. Você pode colocar uma etiqueta em um switch que pode ser o alvo do seu break.

Enum

O PowerShell 5.0 nos trouxe 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, poderá 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 a opção não trate o valor [Context]::Location como uma cadeia de caracteres literal.

Scriptblock

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

$age = 37

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

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

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

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

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

$Matches de expressão regular

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

$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 fazer uma correspondência com 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 string vazia em uma switch declaração, é importante usar a declaração de comparação, conforme mostrado neste exemplo, em vez do valor '' bruto. Em uma switch declaraçã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, preste atenção aos 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 caso default.

$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 boolianas que precisam acontecer.

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

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

Essa é uma maneira limpa de avaliar e tomar medidas sobre o status de vários campos boolianos. O interessante disso é que você pode ter uma correspondência invertendo 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

Configurar $isEnabled para $true neste exemplo garante que $isVisible também seja configurado para $true. Em seguida, quando $isVisible é avaliado, seu bloco de instrução é invocado. Isso é um pouco contra-intuitivo, mas é um uso inteligente da mecânica.

$switch variável automática

Quando ele switch está processando seus valores, ele cria um enumerador e o chama $switch. 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 }
}

Isso fornece os resultados de:

2
4

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

Outros padrões

Tabelas de hash

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 instrução switch costuma tratar.

$day = 3

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

$lookup[$day]
Wednesday

Quando eu uso switch apenas como referência, geralmente prefiro usar hashtable.

Enum

O PowerShell 5.0 introduziu o enum, e também é uma opção nesse caso.

$day = 3

enum DayOfTheWeek {
    Sunday
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
}

[DayOfTheWeek]$day
Wednesday

Poderíamos passar o dia todo procurando maneiras diferentes de resolver esse problema. Só queria ter certeza que sabia que tinha opções.

Palavras finais

A instrução switch pode parecer simples à primeira vista, mas oferece alguns recursos avançados que a maioria das pessoas não percebe que existem. A associação desses recursos faz com que esse recurso seja poderoso. Espero que tenha aprendido algo que não tinha percebido antes.