Compartilhar via


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

Como muitas outras linguagens, o PowerShell tem comandos para controlar o fluxo de execução dentro dos 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 sobre como trabalhar com o switch do PowerShell.

Observação

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

A instrução if

Uma das primeiras instruções que você aprende é a instrução if. Ela permitirá que você execute 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 as instruções elseif e else. A seguir está um exemplo no qual 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

Esse é um padrão comum e há várias maneiras de lidar com isso. Uma delas é com um switch.

Instrução switch

A instrução switch permite que você forneça uma variável e uma lista de valores possíveis. Se o valor corresponder à variável, o bloco de script 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'

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

Atribuir a uma variável

Podemos gravar 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 palavra-chave default para identificar o que deve acontecer se não houver nenhuma correspondência.

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

Aqui, retornamos o valor Unknown no caso padrão.

Cadeias de caracteres

Estava correspondendo números nesses últimos exemplos, mas você também pode corresponder 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 encapsular as correspondências Component,Role e Location entre aspas aqui para realçar que elas são opcionais. O switch trata elas como uma cadeia de caracteres na maioria dos casos.

Matrizes

Um dos recursos interessantes de switch do PowerShell é a maneira como ele lida com matrizes. Se você fornecer uma matriz a um switch, ele 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 itens repetidos na sua matriz, eles serão correspondidos várias vezes pela seção apropriada.

PSItem

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

Parâmetros

Um recurso exclusivo de switch do PowerShell é que ele tem um número de parâmetros de opção que alteram a forma como ele é executado.

-CaseSensitive

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

-Wildcard

Podemos habilitar o suporte a curinga com a switch -wildcard. Isso usa a mesma lógica de curinga que o operador -like 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 colocando a saída dela em fluxos diferentes com base no conteúdo.

-Regex

A instrução switch dá suporte a correspondências de expressão regular, exatamente como acontece com curingas.

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

Há mais exemplos de uso de expressões regulares em outro artigo que escrevi: As várias maneiras de usar regex.

-File

Um recurso pouco conhecido da instrução switch é que ela pode processar um arquivo com o parâmetro -File. Você usa -file com um caminho para um arquivo em vez de dar a ele 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 exatamente como processar uma matriz. Nesse exemplo, eu o combino com uma correspondência de curinga e uso o $PSItem. Isso processaria um arquivo de log e o converteria em mensagens erro e de aviso, dependendo das correspondências de expressão regular.

Detalhes avançados

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

Expressões

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

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

Independentemente de como a expressão é avaliada, ela é o valor usado para a correspondência.

Múltiplas correspondências

Talvez você já tenha percebido, mas um switch pode corresponder a várias condições. Isso é especialmente verdadeiro ao usar as correspondências -wildcard ou -regex. Você pode adicionar a mesma condição várias vezes e todas elas serem disparadas.

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

Essas três instruções são disparadas. Isso mostra que todas as condições são verificadas (em ordem). Isso é verdadeiro para matrizes de processamento, nas quais cada item verifica cada condição.

Continue

Normalmente, aqui que eu apresentaria a instrução break, mas é melhor que aprendemos a usar a continue primeiro. Assim como com um loop de foreach, o continue continua no 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 todos os três itens, o primeiro é correspondido e a switch continua para o próximo valor. Como não há valores restantes para processar, switch é fechada. 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 as palavras Error e Warning, queremos que apenas a primeira seja executada e, em seguida, continue processando o arquivo.

Interromper

Uma instrução break sai da switch. Esse é o mesmo comportamento que o continue apresenta para valores únicos. A diferença é mostrada durante o processamento de uma matriz. break interrompe todo o processamento na switch 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 em alguma linha que começa com Error, obteremos um erro e a switch será interrompida. Isso é o que essa instrução break está fazendo para nós. Se encontrarmos Error dentro da cadeia de caracteres e não apenas no início, vamos gravá-lo como um aviso. Faremos a mesma coisa para Warning. É possível que uma linha tenha ambas as palavras Error e Warning, mas precisamos apenas de uma para processar. Isso é o que a instrução continue está fazendo para nós.

Rótulos de intervalo

A instrução switch dá 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
        }
    }
}

Pessoalmente, não gosto do uso de rótulos de intervalo, mas queria destacá-los porque eles são confusos se você nunca os viu antes. Quando você tiver várias instruções switch ou foreach aninhadas, talvez queira interromper mais do que o item mais interno. Você pode colocar um rótulo em um switch que pode ser o destino do seu break.

Enum

O PowerShell 5.0 forneceu enumerações e podemos usá-las em uma 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ê desejar manter tudo como enumerações fortemente tipadas, poderá colocá-las 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 switch não trate o valor [Context]::Location como uma cadeia de caracteres literal.

Bloco de script

Podemos usar um bloco de script 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 aumenta a complexidade e pode tornar o seu switch difícil de ler. Na maioria dos casos em que você usaria algo semelhante a isso, seria melhor usar as instruções if e elseif. Eu consideraria o uso desse recurso se já tivesse uma instrução switch muito longa em vigor e precisasse que dois itens atingissem o mesmo bloco de avaliação.

Uma coisa que acho que me ajuda com a legibilidade é colocar o bloco de script entre parênteses.

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

Ele ainda é executado da mesma maneira e fornece uma quebra visual melhor ao olhar rapidamente.

$matches de expressão regular

Precisamos revisitar as expressões regulares para discutir algo que não é imediatamente óbvio. O uso de expressões regulares popula a variável $matches. Eu me aprofundo mais sobre o uso do $matches ao falar sobre As várias maneiras de usar regex. A seguir 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 um valor $null 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 instrução switch, é importante usar a instrução de comparação, conforme mostrado neste exemplo, em vez do valor bruto ''. Em uma instrução switch, 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 os retornos vazios dos cmdlets. 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 de constante

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

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

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

Essa é um modo limpo de avaliar e agir sobre o status de vários campos boolianos. O interessante nisso é que uma correspondência pode inverter 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 de $isEnabled para $true nesse exemplo garante que $isVisible também seja definido como $true. Em seguida, quando $isVisible é avaliado, o bloco de script dele é invocado. Isso é um pouco contraintuitivo, mas é um uso inteligente da mecânica.

Variável automática $switch

Quando o switch estiver processando os valores, ele criará um enumerador e o chamará de $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

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

Outros padrões

Tabelas de hash

Uma das minhas postagens mais populares é aquela que fiz sobre tabelas de hash. Um dos casos de uso de um hashtable é ser uma tabela de pesquisa. Essa é uma abordagem alternativa para um padrão comum que uma instrução switch geralmente aborda.

$day = 3

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

$lookup[$day]
Wednesday

Se estiver usando um switch apenas como uma pesquisa, geralmente usarei um hashtable em vez disso.

Enum

O PowerShell 5.0 apresentou o Enum e ele 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 analisando diferentes maneiras de resolver esse problema. Queria apenas me certificar de que você sabe que tem opções.

Conclusão

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