Todo lo que siempre has querido saber 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 la instrucción switch y, en PowerShell, ofrece características que no se encuentran en otros lenguajes. En la actualidad, se profundiza en cómo trabajar con PowerShell switch.

Nota:

La versión original de este artículo apareció en el blog escrito por @KevinMarquette. El equipo de PowerShell agradece a Kevin por compartir este contenido con nosotros. Consulte su blog en PowerShellExplained.com.

Instrucción if

Una de las primeras instrucciones que aprende es la if instrucción . Permite ejecutar un bloque de script si una instrucción es $true.

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

Puede implementar una lógica mucho más compleja utilizando instrucciones de elseif y else. Este es 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

Resulta que se trata de un patrón común y hay muchas maneras de tratar con esto. Uno de ellos es con un switch.

Instrucción switch

La switch instrucción permite proporcionar una variable y una lista de valores posibles. Si el valor coincide con la variable, se ejecuta su bloque de instrucciones.

$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, a continuación, el nombre correcto se asigna a $result. En este ejemplo solo se realiza una asignación de variables, pero se puede ejecutar cualquier PowerShell en esos bloques de script.

Asignar 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'  }
}

Colocamos el valor en la canalización de PowerShell y lo asignamos a $result. Puede hacer esto mismo con las instrucciones if y foreach .

Predeterminado

Podemos usar la default palabra clave para identificar lo que 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.

Instrumentos de cuerda

Estaba haciendo coincidir números en esos últimos ejemplos, pero también puedes hacer coincidir cadenas.

$item = 'Role'

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

Decidí no envolver las coincidencias Component, Role y Location entre comillas aquí para resaltar que son opcionales. switch los trata como una cadena de texto en la mayoría de los casos.

Matrices

Una de las características interesantes de PowerShell switch es la forma en que controla las matrices. Si asigna una switch matriz, procesa cada elemento de esa colección.

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

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

Si tiene elementos repetidos en su arreglo, la sección adecuada los empareja varias veces.

PSItem

Puede usar el $PSItem o el $_ para referenciar el elemento actual que se procesó. Cuando hacemos una coincidencia simple, $PSItem es el valor con el que estamos haciendo la coincidencia. Voy a realizar algunas coincidencias avanzadas en la sección siguiente donde se usa esta variable.

Parámetros

Una característica única de PowerShell switch es que tiene una serie de [switch] parámetros que cambian el rendimiento.

-Sensible a mayúsculas y minúsculas

Las coincidencias no distinguen mayúsculas de minúsculas de forma predeterminada. Si necesita distinguir mayúsculas de minúsculas, puede usar -CaseSensitive. Esto se puede usar en combinación con los demás [switch] parámetros.

-Comodín

Podemos habilitar la compatibilidad con caracteres comodín con el -Wildcard[switch] parámetro . Esto utiliza la misma lógica de caracteres comodín que el operador -like para realizar 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, a continuación, se genera en secuencias diferentes en función del contenido.

-Regex

La instrucción switch admite coincidencias regex igual que sí con caracteres comodín.

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

Tengo más ejemplos de uso de regex en otro artículo que escribí: Las muchas maneras de usar regex.

-Archivo

Una característica poco conocida de la instrucción switch es que puede procesar un archivo con el -File parámetro . Se usa -File con una ruta de acceso a un archivo en lugar de darle 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 procesar una matriz. En este ejemplo, lo combino con la coincidencia de caracteres comodín y hago uso de .$PSItem Esto procesaría un archivo de registro y lo convertiría en mensajes de advertencia y error en función de las coincidencias de regex.

Detalles avanzados

Ahora que conoce todas estas características documentadas, podemos usarlas en el contexto del procesamiento más avanzado.

Expressions

switch puede estar en una expresión en lugar de en una variable.

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

Sea cual sea la expresión que se evalúe, es el valor que se utiliza para la coincidencia.

Varias coincidencias

Es posible que ya te hayas dado cuenta de esto, pero switch puede coincidir con varias condiciones. Esto es especialmente cierto cuando se usan coincidencias de -Wildcard o de -Regex. Puede agregar la misma condición varias veces y se desencadenan todas.

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

Se desencadenan todas las tres declaraciones. Esto muestra que se comprueba cada condición (en orden). Esto es cierto para procesar arreglos donde cada elemento verifica cada condición.

Continuar

Normalmente, aquí es donde presentaría la break declaración, pero es mejor que aprendamos a usar continue primero. Al igual que con un foreach bucle, continue continúa en el siguiente elemento de la colección o sale de switch si no hay más elementos. Podemos volver a escribir 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 hacer coincidir los tres elementos, el primero coincide y el interruptor pasa al siguiente valor. Dado que no quedan valores para procesar, el conmutador sale. En el siguiente ejemplo se muestra cómo un 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 la palabra Error y Warning, solo queremos que se ejecute la primera y, a continuación, siga procesando el archivo.

Interrupción

Una break instrucción sale del switch. Este es el mismo comportamiento que presenta continue para valores únicos. La diferencia se muestra al procesar una matriz. break detiene todo el procesamiento en el conmutador y continue se mueve 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 encontramos líneas que comienzan por Error, obtenemos un error y el interruptor se detiene. Esto es lo que hace esa break declaración para nosotros. Si encontramos Error dentro de la cadena y no solo al principio, lo escribiremos como una advertencia. Hacemos lo mismo para Warning. Es posible que una línea pueda tener la palabra Error y Warning, pero solo necesitamos una para procesar. Esto es lo que hace la continue declaración para nosotros.

Interrumpir etiquetas

La switch instrucción admite break/continue etiquetas 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 el uso de etiquetas de interrupción, pero quería señalarlas porque son confusas si nunca las has visto antes. Cuando tenga varias switch instrucciones o foreach anidadas, es posible que desee interrumpir más que el elemento más interno. Puede colocar una etiqueta en un switch que puede ser el destino de su break.

Enum

PowerShell 5.0 nos dio enumeraciones y podemos usarlas en una instrucción 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

Si desea mantener todo como enumeraciones fuertemente tipadas, puede colocarlos 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.

ScriptBlock

Podemos usar un scriptblock para realizar la evaluación de 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 declaraciones if y elseif. Consideraría usar esto si ya tuviera un gran conmutador instalado y necesitara que dos elementos impactaran el mismo bloque de evaluación.

Una cosa que creo que ayuda con la legibilidad es colocar el scriptblock entre paréntesis.

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

Sigue funcionando de la misma manera y proporciona un mejor salto visual cuando lo examina rápidamente.

Regex $Matches

Tenemos que revisar de nuevo las expresiones regulares para tratar algo que no resulta inmediatamente obvio. El uso de regex rellena la $Matches variable . Voy a usar $Matches más cuando hablo de Las muchas maneras de usar regex. Este es un ejemplo rápido para demostrarlo en acción con coincidencias nominadas.

$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 emparejar un valor $null que no tiene que ser 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 switch instrucción, es importante utilizar la instrucción de comparación que se muestra en este ejemplo en lugar del valor '' sin procesar. En una switch declaración, 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, tenga cuidado con los retornos vacíos de los cmdlets. Los cmdlets o canalizaciones que no tienen salida se tratan como una matriz vacía que no coincide con nada, incluido el default caso.

$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 podemos usar una expresión constante $true para evaluar [bool] los elementos. Imagina que tenemos 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 manera limpia de evaluar y tomar medidas sobre el estado de varios campos booleanos. Lo interesante de esto es que puede tener una coincidencia voltear 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 en este ejemplo, se asegura de que $isVisible también esté establecido en $true. A continuación, cuando $isVisible se evalúa, se invoca su bloque de instrucciones. Esto es un poco contraintuitivo, pero es un uso inteligente de las mecánicas.

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 y puede manipularla directamente.

$a = 1, 2, 3, 4

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

Esto le proporciona los resultados de:

2
4

Al mover el enumerador hacia delante, el elemento siguiente no se procesa mediante , switch pero puede acceder directamente a ese valor. Lo llamaría locura.

Otros patrones

Tablas de dispersión

Una de mis publicaciones más populares es aquella que hice sobre tablas de hash. Uno de los casos de uso de hashtable es ser 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 uso switch como una consulta, a menudo uso hashtable en su lugar.

Enum

PowerShell 5.0 introdujo 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 ir todo el día examinando diferentes formas de resolver este problema. Sólo quería asegurarme de que sabías que tenías opciones.

Palabras finales

La instrucción switch es sencilla a simple vista, pero ofrece algunas características avanzadas de las que la mayoría de las personas no se percatan. Al encadenar esas características, se convierte en una característica eficaz. Espero que hayas aprendido algo que no te habías dado cuenta antes.