Tudo o que sempre quis saber sobre a instrução switch
Tal como muitas outras linguagens, o PowerShell tem comandos para controlar o fluxo de execução nos scripts. Uma dessas instruções é a instrução switch e, no PowerShell, oferece funcionalidades que não se encontram noutros idiomas. Hoje, vamos aprofundar o trabalho com o PowerShell switch
.
Nota
A versão original deste artigo apareceu no blogue escrito por @KevinMarquette. A equipa do PowerShell agradece ao Kevin por partilhar este conteúdo connosco. Veja o blogue dele no PowerShellExplained.com.
A if
instrução
Uma das primeiras afirmações que aprende é a if
instrução. Permite-lhe executar um bloco de script se uma instrução for $true
.
if ( Test-Path $Path )
{
Remove-Item $Path
}
Pode ter lógicas elseif
e else
instruções muito mais complicadas. Eis um exemplo em que tenho um valor numérico para o dia da semana e quero obter o nome como uma cadeia.
$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 formas de lidar com isto. Um deles é com um switch
.
Mudar instrução
A switch
instrução permite-lhe fornecer uma variável e uma lista de valores possíveis. Se o valor corresponder à variável, o scriptblock é 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 e, em seguida, o nome correto é atribuído a $result
. Neste exemplo, estamos apenas a fazer uma atribuição de variáveis, 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 a colocar o valor no pipeline do PowerShell e a atribuí-lo ao $result
. Pode fazer o mesmo com as if
instruções e foreach
.
Predefinição
Podemos utilizar a default
palavra-chave para identificar o que deve acontecer se não existir correspondência.
$result = switch ( $day )
{
0 { 'Sunday' }
# ...
6 { 'Saturday' }
default { 'Unknown' }
}
Aqui, devolvemos o valor Unknown
no caso predefinido.
Cadeias
Estava a corresponder números nesses últimos exemplos, mas também podes corresponder às cadeias.
$item = 'Role'
switch ( $item )
{
Component
{
'is a component'
}
Role
{
'is a role'
}
Location
{
'is a location'
}
}
is a role
Decidi não embrulhar as Component
Role
correspondências e Location
aspas aqui para realçar que são opcionais. Trata-os switch
como uma cadeia na maioria dos casos.
Matrizes
Uma das funcionalidades interessantes do PowerShell switch
é a forma como processa as matrizes. Se fornecer uma switch
matriz, esta processa cada elemento nessa coleção.
$roles = @('WEB','Database')
switch ( $roles ) {
'Database' { 'Configure SQL' }
'WEB' { 'Configure IIS' }
'FileServer' { 'Configure Share' }
}
Configure IIS
Configure SQL
Se tiver itens repetidos na sua matriz, estes serão correspondidos várias vezes pela secção adequada.
PSItem
Pode utilizar o $PSItem
ou $_
para referenciar o item atual que foi processado. Quando fazemos uma correspondência simples, $PSItem
é o valor que estamos a corresponder. Vou realizar algumas correspondências avançadas na próxima secção onde esta variável é utilizada.
Parâmetros
Uma funcionalidade exclusiva do PowerShell switch
é que tem vários parâmetros de comutador que alteram o seu desempenho.
-CaseSensitive
As correspondências não são sensíveis a maiúsculas e minúsculas por predefinição. Se precisar de ser sensível a maiúsculas e minúsculas, pode utilizar -CaseSensitive
. Isto pode ser utilizado em combinação com os outros parâmetros de comutador.
-Carateres universais
Podemos ativar o suporte de carateres universais com o -wildcard
comutador. Esta ação utiliza a mesma lógica universal 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 a processar uma mensagem e, em seguida, a transmiti-la em diferentes fluxos com base nos conteúdos.
-Regex
A instrução switch suporta correspondências regex tal como os carateres universais.
switch -Regex ( $message )
{
'^Error'
{
Write-Error -Message $Message
}
'^Warning'
{
Write-Warning -Message $Message
}
default
{
Write-Information $message
}
}
Tenho mais exemplos de utilização do regex noutro artigo que escrevi: As muitas formas de utilizar o regex.
-Ficheiro
Uma funcionalidade pouco conhecida da instrução switch é que pode processar um ficheiro com o -File
parâmetro . -file
Utilize com um caminho para um ficheiro em vez de lhe dar 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 correspondência de carateres universais e utilizo o $PSItem
. Isto processaria um ficheiro de registo e converteria-o em mensagens de aviso e erro, consoante as correspondências regex.
Detalhes avançados
Agora que tem conhecimento de todas estas funcionalidades documentadas, podemos utilizá-las no contexto de um processamento mais avançado.
Expressions (Expressões)
O switch
pode estar numa expressão em vez de numa variável.
switch ( ( Get-Service | Where status -eq 'running' ).name ) {...}
O que quer que a expressão avalie é o valor utilizado para a correspondência.
Várias correspondências
Pode já ter apanhado isto, mas uma switch
pode corresponder a múltiplas condições. Isto é especialmente verdade ao utilizar -wildcard
ou -regex
corresponder. 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 estas três declarações foram despedidas. Isto mostra que todas as condições são verificadas (por ordem). Isto aplica-se ao processamento de matrizes em que cada item verifica cada condição.
Continuar
Normalmente, é aqui que introduzo a break
declaração, mas é melhor aprendermos a usar continue
primeiro. Tal como acontece com um foreach
ciclo, continue
continua para o item seguinte na coleção ou sai do switch
se não existirem 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 o comutador continua para o valor seguinte. Uma vez que não existem valores para processar, o comutador sai. Este exemplo seguinte mostra como um caráter universal 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
}
}
Uma vez que uma linha no ficheiro de entrada pode conter a palavra Error
e Warning
, só queremos que a primeira seja executada e, em seguida, continue a processar o ficheiro.
Divisão
Uma break
instrução sai do comutador. Este é o mesmo comportamento que continue
apresenta para valores únicos. A diferença é apresentada ao processar uma matriz. break
para todo o processamento no comutador e continue
passa para o item seguinte.
$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 atingirmos quaisquer linhas que comecem com Error
, obteremos um erro e o comutador para.
Isto é o que essa break
afirmação está a fazer por nós. Se encontrarmos Error
dentro da cadeia e não apenas no início, escrevemos como um aviso. Fazemos o mesmo por Warning
. É possível que uma linha possa ter a palavra Error
e Warning
, mas só precisamos de uma para processar. Isto é o que a continue
declaração está a fazer por nós.
Quebrar etiquetas
A switch
instrução suporta etiquetas break/continue
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 etiquetas de quebra, mas queria apontá-las porque são confusas se nunca as viste antes. Quando tiver múltiplas switch
ou foreach
instruções aninhadas, poderá querer sair de mais do que o item mais interno. Pode colocar uma etiqueta numa switch
que pode ser o destino do seu break
.
Enumeração
O PowerShell 5.0 deu-nos números e podemos utilizá-los num comutador.
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 quiser manter tudo como números escritos de forma segura, pode colocá-los em 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 comutador não trate o valor [Context]::Location
como uma cadeia literal.
ScriptBlock
Podemos utilizar um scriptblock para efetuar a avaliação de uma correspondência, se necessário.
$age = 37
switch ( $age )
{
{$PSItem -le 18}
{
'child'
}
{$PSItem -gt 18}
{
'adult'
}
}
'adult'
Isto adiciona complexidade e pode dificultar a sua switch
leitura. Na maioria dos casos em que utilizaria algo assim, seria melhor utilizar if
e elseif
instruções. Consideraria utilizá-lo se já tivesse uma grande mudança e precisasse de dois itens para atingir o mesmo bloco de avaliação.
Uma coisa que acho que ajuda na legibilidade é colocar o scriptblock em parênteses.
switch ( $age )
{
({$PSItem -le 18})
{
'child'
}
({$PSItem -gt 18})
{
'adult'
}
}
Continua a ser executado da mesma forma e proporciona uma melhor pausa visual ao olhar rapidamente para o mesmo.
Regex $matches
Temos de revisitar o Regex para tocar em algo que não seja imediatamente óbvio. A utilização do regex preenche a $matches
variável. $matches
Utilizo mais quando falo das muitas formas de utilizar o regex. Segue-se 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
Pode corresponder a um $null
valor que não tem de ser a predefiniçã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 vazia numa switch
instrução, é importante utilizar a instrução de comparação, conforme mostrado neste exemplo, em vez do valor ''
não processado . Numa switch
instrução, o valor ''
não processado também corresponde $null
a . 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 as devoluções vazias dos 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 salientou que podemos usar uma expressão constante $true
para avaliar [bool]
itens.
Imagine se tivermos várias verificações booleanas que precisam de acontecer.
$isVisible = $false
$isEnabled = $true
$isSecure = $true
switch ( $true )
{
$isEnabled
{
'Do-Action'
}
$isVisible
{
'Show-Animation'
}
$isSecure
{
'Enable-AdminMenu'
}
}
Do-Action
Enabled-AdminMenu
Esta é uma forma limpa de avaliar e tomar medidas sobre o estado de vários campos booleanos. O mais interessante é que pode fazer com que uma correspondência inverta o estado 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 definição $isEnabled
para $true
neste exemplo garante que $isVisible
também está definido como $true
.
Em seguida, quando $isVisible
for avaliado, o scriptblock é invocado. Isto é um pouco contra-intuitivo, mas é um uso inteligente da mecânica.
$switch variável automática
Quando o switch
está a processar os respetivos valores, cria um enumerador e chama-lhe $switch
. Esta é uma variável automática criada pelo PowerShell e 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 a frente, o item seguinte não é processado pelo switch
, mas pode aceder diretamente a esse valor. Chamar-lhe-ia loucura.
Outros padrões
Tabelas hash
Um dos meus cargos mais populares foi o que fiz em hash. Um dos casos de utilização de um hashtable
é ser uma tabela de referência. Esta é uma abordagem alternativa a um padrão comum que uma switch
instrução está frequentemente a abordar.
$day = 3
$lookup = @{
0 = 'Sunday'
1 = 'Monday'
2 = 'Tuesday'
3 = 'Wednesday'
4 = 'Thursday'
5 = 'Friday'
6 = 'Saturday'
}
$lookup[$day]
Wednesday
Se estiver a utilizar apenas uma switch
pesquisa, utilizo frequentemente uma hashtable
.
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
Podemos passar o dia todo a ver diferentes formas de resolver este problema. Só queria ter a certeza que sabias que tinhas opções.
Palavras finais
A instrução switch é simples na superfície, mas oferece algumas funcionalidades avançadas que a maioria das pessoas não percebe que estão disponíveis. Juntar essas funcionalidades faz com que esta seja uma funcionalidade avançada. Espero que tenha aprendido algo que nunca tinha percebido antes.