Todo lo que le interesa sobre la instrucción switch
Al igual que muchos otros lenguajes, PowerShell tiene comandos para controlar el flujo de ejecución dentro de los scripts. Una de esas instrucciones es switch, que, en PowerShell, ofrece características que no existen en otros lenguajes. Hoy, vamos a profundizar en cómo trabajar con switch
de PowerShell.
Nota
La versión original de este artículo apareció en el blog escrito por @KevinMarquette. El equipo de PowerShell agradece a Kevin que comparta este contenido con nosotros. Visite su blog en PowerShellExplained.com.
Instrucción if
Una de las primeras instrucciones que se aprenden es if
. Permite ejecutar un bloque de script si una instrucción es $true
.
if ( Test-Path $Path )
{
Remove-Item $Path
}
Puede tener una lógica mucho más complicada mediante las instrucciones elseif
y else
. A continuación, se muestra un ejemplo en el que tengo un valor numérico para el día de la semana y quiero obtener el nombre como una cadena.
$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
Se trata de un patrón común y hay muchas maneras de abordarlo. Una de ellas es con switch
.
Instrucción switch
La instrucción switch
permite proporcionar una variable y una lista de valores posibles. Si el valor coincide con la variable, se ejecuta su bloque de script.
$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'
En este ejemplo, el valor de $day
coincide con uno de los valores numéricos y, después, se asigna el nombre correcto a $result
. En este ejemplo, solo se realiza la asignación a una variable, pero cualquier comando de PowerShell puede ejecutarse en esos bloques de script.
Asignación a una variable
Podemos escribir ese último ejemplo de otra manera.
$result = switch ( $day )
{
0 { 'Sunday' }
1 { 'Monday' }
2 { 'Tuesday' }
3 { 'Wednesday' }
4 { 'Thursday' }
5 { 'Friday' }
6 { 'Saturday' }
}
Se coloca el valor en la canalización de PowerShell y se asigna a $result
. Puede hacer lo mismo con las instrucciones if
y foreach
.
Valor predeterminado
Podemos usar la palabra clave default
para identificar qué debe ocurrir si no hay ninguna coincidencia.
$result = switch ( $day )
{
0 { 'Sunday' }
# ...
6 { 'Saturday' }
default { 'Unknown' }
}
Aquí se devuelve el valor Unknown
en el caso predeterminado.
Cadenas
Creé correspondencias con los números en los últimos ejemplos, pero también se puede establecer una coincidencia entre cadenas.
$item = 'Role'
switch ( $item )
{
Component
{
'is a component'
}
Role
{
'is a role'
}
Location
{
'is a location'
}
}
is a role
Decidí no encapsular aquí las coincidencias Component
, Role
y Location
entre comillas para resaltar que son opcionales. switch
las trata como una cadena en la mayoría de los casos.
Matrices
Una de las características interesantes de la instrucción switch
de PowerShell es la forma en que trata las matrices. Si proporciona una matriz a una instrucción switch
, esta procesa cada elemento de dicha colección.
$roles = @('WEB','Database')
switch ( $roles ) {
'Database' { 'Configure SQL' }
'WEB' { 'Configure IIS' }
'FileServer' { 'Configure Share' }
}
Configure IIS
Configure SQL
Si hay elementos repetidos en la matriz, la sección correspondiente establece coincidencias entre ellos varias veces.
PSItem
Puede usar $PSItem
o $_
para hacer referencia al elemento actual que se procesó. Cuando establecemos una coincidencia simple, $PSItem
es el valor con el que se crea la coincidencia. Realizaré algunas coincidencias avanzadas en la sección siguiente donde se usa esta variable.
Parámetros
Una característica exclusiva de switch
de PowerShell es que tiene una serie de Parámetros de modificador que cambian la forma en que se ejecuta.
-CaseSensitive
Las coincidencias no distinguen mayúsculas de minúsculas de forma predeterminada. Si necesita que se distingan mayúsculas de minúsculas, puede usar -CaseSensitive
. Se puede utilizar en combinación con los otros parámetros switch.
-Wildcard
Se puede habilitar la compatibilidad con los caracteres comodín mediante el modificador -wildcard
. Usa la misma lógica del carácter comodín que el operador -like
para establecer cada coincidencia.
$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
Aquí se procesa un mensaje y, después, se transmite en secuencias diferentes en función del contenido.
-Regex
La instrucción switch admite las coincidencias regex del mismo modo que los caracteres comodín.
switch -Regex ( $message )
{
'^Error'
{
Write-Error -Message $Message
}
'^Warning'
{
Write-Warning -Message $Message
}
default
{
Write-Information $message
}
}
Hay más ejemplos de uso de regex en otro artículo que escribí: Las distintas formas de usar regex.
-File
Una característica poco conocida de la instrucción switch es que puede procesar un archivo con el parámetro -File
. Utilice -file
con una ruta de acceso a un archivo, en lugar de asignarle una expresión variable.
switch -Wildcard -File $path
{
'Error*'
{
Write-Error -Message $PSItem
}
'Warning*'
{
Write-Warning -Message $PSItem
}
default
{
Write-Output $PSItem
}
}
Funciona igual que el procesamiento de una matriz. En este ejemplo, se combina con la coincidencia con caracteres comodín y se usa $PSItem
. Esto procesaría un archivo de registro y lo convertiría en mensajes de advertencia y error en función de las coincidencias regex.
Detalles avanzados
Ahora que conoce todas estas características documentadas, podemos usarlas en el contexto de un procesamiento más avanzado.
Expresiones
switch
puede estar en una expresión en lugar de en una variable.
switch ( ( Get-Service | Where status -eq 'running' ).name ) {...}
Cualquiera que sea la expresión evaluada, será el valor que se utilizará para la coincidencia.
Varias coincidencias
Puede que ya lo sepa, pero switch
puede coincidir con varias condiciones. Esto sucede especialmente cuando se usan las coincidencias -wildcard
o -regex
. Puede agregar la misma condición varias veces, y todas se activarán.
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
Estas tres instrucciones se activan. Esto muestra que se comprueba cada condición (en orden). Esto se aplica al procesamiento de matrices en las que cada elemento comprueba cada condición.
Continuar
Normalmente, aquí es donde se introduciría la instrucción break
, pero es mejor aprender a usar continue
primero. Al igual que con un bucle foreach
, continue
continúa en el siguiente elemento de la colección o sale de switch
si no hay más elementos. Podemos reescribir ese último ejemplo con instrucciones continue, para que solo se ejecute una instrucción.
switch ( 'Word' )
{
'word'
{
'lower case word match'
continue
}
'Word'
{
'mixed case word match'
continue
}
'WORD'
{
'upper case word match'
continue
}
}
lower case word match
En lugar de buscar coincidencias con los tres elementos, se establece una coincidencia con el primero, y el modificador continúa hasta el valor siguiente. Dado que ya no queda ningún valor por procesar, el modificador se cierra. En el ejemplo siguiente se muestra cómo un carácter comodín podría coincidir con varios elementos.
switch -Wildcard -File $path
{
'*Error*'
{
Write-Error -Message $PSItem
continue
}
'*Warning*'
{
Write-Warning -Message $PSItem
continue
}
default
{
Write-Output $PSItem
}
}
Dado que una línea del archivo de entrada podría contener las palabras Error
y Warning
, solo deseamos que se ejecute la primera y que, después, se siga procesando el archivo.
Break
Una instrucción break
cierra el modificador. Este es el mismo comportamiento que continue
presenta para los valores únicos. La diferencia se muestra al procesar una matriz. break
detiene todo el procesamiento del modificador y continue
pasa al siguiente elemento.
$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
En este caso, si se alcanzan las líneas que comienzan con Error
, se obtiene un error y el modificador se detiene.
Esto es lo que hace la instrucción break
. Si encontramos Error
dentro de la cadena y no solo al principio, lo escribimos como una advertencia. Haremos lo mismo con Warning
. Es posible que una línea pueda tener las palabras Error
y Warning
, pero solo se necesita una para procesar. Esto es lo que hace la instrucción continue
.
Etiquetas de interrupción
La instrucción switch
admite 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
}
}
}
Personalmente, no me gusta usar las etiquetas de interrupción, pero quería destacarlas porque son confusas si no se han visto antes. Si tiene varias instrucciones switch
o foreach
anidadas, puede que quiera ir más allá del elemento más interno. Puede colocar una etiqueta en switch
que puede ser el destino de break
.
Enum
PowerShell 5.0 proporcionó enumeraciones que podemos usar en un modificador.
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
Si desea conservarlas todas como enumeraciones fuertemente tipadas, puede colocarlas entre paréntesis.
switch ($item )
{
([Context]::Component)
{
'is a component'
}
([Context]::Role)
{
'is a role'
}
([Context]::Location)
{
'is a location'
}
}
Los paréntesis son necesarios aquí para que el modificador no trate el valor [Context]::Location
como una cadena literal.
Bloque de script
Se puede usar un bloque de script para realizar la evaluación de una coincidencia, si es necesario.
$age = 37
switch ( $age )
{
{$PSItem -le 18}
{
'child'
}
{$PSItem -gt 18}
{
'adult'
}
}
'adult'
Esto agrega complejidad y puede hacer que switch
sea difícil de leer. En la mayoría de los casos en los que usaría algo parecido a esto, sería mejor usar instrucciones if
y elseif
. Sería conveniente usar esto si ya existiera un modificador grande y se necesitaran dos elementos para alcanzar el mismo bloque de evaluación.
Un aspecto que considero que ayuda con la legibilidad es colocar el bloque de script entre paréntesis.
switch ( $age )
{
({$PSItem -le 18})
{
'child'
}
({$PSItem -gt 18})
{
'adult'
}
}
Todavía se ejecuta de la misma manera y ofrece una mejor interrupción visual cuando se consulta rápidamente.
$matches en regex
Es necesario revisar la información relativa a regex para tratar algo que, a primera vista, no resulta obvio. El uso de regex rellena la variable $matches
. Voy a usar $matches
más cuando hablo de Las distintas formas de usar regex. A continuación, se muestra un ejemplo rápido para mostrarlo en acción mediante coincidencias con nombre.
$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
Puede establecer una coincidencia con un valor $null
que no tenga que ser el predeterminado.
$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
Al probar una cadena vacía en una instrucción switch
, es importante usar la instrucción de comparación como se muestra en este ejemplo en lugar del valor sin formato ''
. En una instrucción switch
, el valor sin formato ''
también coincide con $null
. Por ejemplo:
$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
Además, debe tener cuidado con las devoluciones vacías de los cmdlets. Los cmdlets o las canalizaciones que no tienen salida se tratan como una matriz vacía que no coincide con nada, incluido el caso default
.
$file = Get-ChildItem NonExistantFile*
switch ( $file )
{
$null { '$file is $null' }
default { "`$file is type $($file.GetType().Name)" }
}
# No matches
Expresión constante
Lee Dailey señaló que se puede usar una expresión $true
constante para evaluar los elementos [bool]
.
Imagine que hay varias comprobaciones booleanas que deben realizarse.
$isVisible = $false
$isEnabled = $true
$isSecure = $true
switch ( $true )
{
$isEnabled
{
'Do-Action'
}
$isVisible
{
'Show-Animation'
}
$isSecure
{
'Enable-AdminMenu'
}
}
Do-Action
Enabled-AdminMenu
Se trata de una forma sencilla de evaluar y tomar medidas en relación con el estado de varios campos booleanos. Lo más interesante en este sentido es que puede haber una variable que cambie el estado de un valor que aún no se ha evaluado.
$isVisible = $false
$isEnabled = $true
$isAdmin = $false
switch ( $true )
{
$isEnabled
{
'Do-Action'
$isVisible = $true
}
$isVisible
{
'Show-Animation'
}
$isAdmin
{
'Enable-AdminMenu'
}
}
Do-Action
Show-Animation
Al establecer $isEnabled
en $true
, se garantiza que $isVisible
también se establezca en $true
.
Después, cuando se evalúa $isVisible
, se invoca su bloque de script. Esto resulta algo contradictorio, pero supone un uso inteligente de la mecánica.
Variable automática $switch
Cuando switch
procesa sus valores, crea un enumerador y lo llama $switch
. Se trata de una variable automática creada por PowerShell, que se puede manipular directamente.
$a = 1, 2, 3, 4
switch($a) {
1 { [void]$switch.MoveNext(); $switch.Current }
3 { [void]$switch.MoveNext(); $switch.Current }
}
Aquí se presentan los resultados de:
2
4
Al avanzar con el enumerador, switch
no procesa el siguiente elemento, pero se puede acceder a dicho valor directamente. Yo lo consideraría una locura.
Otros patrones
Tablas hash
Una de mis publicaciones más populares es la que hice sobre tablas hash. Uno de los casos de uso de hashtable
es que se trate de una tabla de búsqueda. Se trata de un enfoque alternativo a un patrón común que una instrucción switch
suele abordar.
$day = 3
$lookup = @{
0 = 'Sunday'
1 = 'Monday'
2 = 'Tuesday'
3 = 'Wednesday'
4 = 'Thursday'
5 = 'Friday'
6 = 'Saturday'
}
$lookup[$day]
Wednesday
Si solo utilizo switch
como una búsqueda, a menudo uso hashtable
en su lugar.
Enum
PowerShell 5.0 presentó Enum
y también es una opción en este caso.
$day = 3
enum DayOfTheWeek {
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
}
[DayOfTheWeek]$day
Wednesday
Podríamos pasar todo el día examinando diferentes formas de solucionar este problema. Solo quería asegurarme de que sabía que tenía opciones.
Conclusiones
La instrucción switch es sencilla a primera vista, pero ofrece algunas características avanzadas que la mayoría de las personas no saben que están disponibles. La combinación de esas características convierte esto en una característica eficaz. Espero que haya aprendido algo que antes no sabía.
Comentarios
https://aka.ms/ContentUserFeedback.
Próximamente: A lo largo de 2024 iremos eliminando gradualmente GitHub Issues como mecanismo de comentarios sobre el contenido y lo sustituiremos por un nuevo sistema de comentarios. Para más información, vea:Enviar y ver comentarios de