Coalescencia nula en powershell

115

¿Hay un operador coalescente nulo en powershell?

Me gustaría poder hacer estos comandos de c # en powershell:

var s = myval ?? "new value";
var x = myval == null ? "" : otherval;
Miqueas
fuente

Respuestas:

133

Powershell 7+

Powershell 7 introduce la fusión de nulos nativos, la asignación condicional nula y los operadores ternarios en Powershell.

Coalescencia nula

$null ?? 100    # Result is 100

"Evaluated" ?? (Expensive-Operation "Not Evaluated")    # Right side here is not evaluated

Asignación condicional nula

$x = $null
$x ??= 100    # $x is now 100
$x ??= 200    # $x remains 100

Operador ternario

$true  ? "this value returned" : "this expression not evaluated"
$false ? "this expression not evaluated" : "this value returned"

Versión anterior:

No necesita las extensiones de la comunidad de Powershell, puede usar las declaraciones estándar de Powershell if como expresión:

variable = if (condition) { expr1 } else { expr2 }

Entonces, para los reemplazos para su primera expresión C # de:

var s = myval ?? "new value";

se convierte en uno de los siguientes (según la preferencia):

$s = if ($myval -eq $null) { "new value" } else { $myval }
$s = if ($myval -ne $null) { $myval } else { "new value" }

o dependiendo de lo que $ myval pueda contener, puede usar:

$s = if ($myval) { $myval } else { "new value" }

y la segunda expresión de C # se asigna de manera similar:

var x = myval == null ? "" : otherval;

se convierte en

$x = if ($myval -eq $null) { "" } else { $otherval }

Ahora, para ser justos, estos no son muy rápidos y no son tan cómodos de usar como los formularios de C #.

También puede considerar envolverlo en una función muy simple para hacer las cosas más legibles:

function Coalesce($a, $b) { if ($a -ne $null) { $a } else { $b } }

$s = Coalesce $myval "new value"

o posiblemente como, IfNull:

function IfNull($a, $b, $c) { if ($a -eq $null) { $b } else { $c } }

$s = IfNull $myval "new value" $myval
$x = IfNull $myval "" $otherval

Como puede ver, una función muy simple puede brindarle bastante libertad de sintaxis.

ACTUALIZACIÓN: Una opción adicional a considerar en la mezcla es una función IsTrue más genérica:

function IfTrue($a, $b, $c) { if ($a) { $b } else { $c } }

$x = IfTrue ($myval -eq $null) "" $otherval

Luego combina esa es la capacidad de Powershell para declarar alias que se parecen un poco a los operadores, terminas con:

New-Alias "??" Coalesce

$s = ?? $myval "new value"

New-Alias "?:" IfTrue

$ans = ?: ($q -eq "meaning of life") 42 $otherval

Claramente, esto no será del gusto de todos, pero puede ser lo que estás buscando.

Como señala Thomas, otra sutil diferencia entre la versión de C # y la anterior es que C # realiza un cortocircuito de los argumentos, pero las versiones de Powershell que involucran funciones / alias siempre evaluarán todos los argumentos. Si esto es un problema, use la ifforma de expresión.

StephenD
fuente
6
El único equivalente verdadero del operador coalescente es usar una instrucción if; el problema es que cualquier otro enfoque evalúa todos los operandos en lugar de cortocircuitarlos. "?? $ myval SomeReallyExpenisveFunction ()" llamará a la función incluso si $ myval no es nulo. Supongo que uno podría retrasar la evaluación usando bloques de script, pero tenga en cuenta que los bloques de script NO son cierres y las cosas comienzan a ponerse torpes.
Thomas S. Trias
No funciona en modo estricto, lanza The variable '$myval' cannot be retrieved because it has not been set..
BrainSlugs83
1
@ BrainSlugs83 El error que está viendo en modo estricto no está relacionado con las opciones de fusión nula presentadas. Es solo el estándar, Powershell verifica que una variable se define primero. Si lo configura $myval = $nullantes de realizar la prueba, el error debería desaparecer.
StephenD
1
Advertencia, peculiaridad de powershell, null siempre debe compararse (torpemente) colocando null primero, es decir, $null -ne $a no se puede encontrar una referencia decente ahora, pero es una práctica de larga data.
dudeNumber4
90

PowerShell 7 y posterior

PowerShell 7 presenta muchas características nuevas y migra de .NET Framework a .NET Core. A mediados de 2020, no ha reemplazado por completo las versiones heredadas de PowerShell debido a la dependencia de .NET Core, pero Microsoft ha indicado que tienen la intención de que la familia Core eventualmente reemplace a la familia Framework heredada. Para cuando lea esto, es posible que una versión compatible de PowerShell venga preinstalada en su sistema; de lo contrario, consulte https://github.com/powershell/powershell .

Según la documentación , los siguientes operadores son compatibles de forma inmediata en PowerShell 7.0:

  1. Coalescente nulo :??
  2. Asignación de coalescencia nula :??=
  3. Ternario :... ? ... : ...

Estos funcionan como cabría esperar para la fusión nula:

$x = $a ?? $b ?? $c ?? 'default value'
$y ??= 'default value'

Dado que se introdujo un operador ternario, ahora es posible lo siguiente, aunque no es necesario dada la adición de un operador de fusión nulo:

$x = $a -eq $null ? $b : $a

A partir de 7.0, lo siguiente también está disponible si la PSNullConditionalOperatorsfunción opcional está habilitada , como se explica en los documentos ( 1 , 2 ):

  1. Acceso de miembro condicional nulo para miembros: ?.
  2. Acceso a miembros condicional nulo para matrices et al: ?[]

Estos tienen algunas advertencias:

  1. Dado que son experimentales, están sujetos a cambios. Es posible que ya no se consideren experimentales cuando lea esto, y es posible que la lista de advertencias haya cambiado.
  2. Las variables deben encerrarse ${}si van seguidas de uno de los operadores experimentales porque se permiten signos de interrogación en los nombres de las variables. No está claro si este será el caso cuando las funciones pasen del estado experimental (consulte el número 11379 ). Por ejemplo, ${x}?.Test()usa el operador new, pero se $x?.Test()ejecuta Test()en una variable llamada $x?.
  3. No hay un ?(operador como cabría esperar si viene de TypeScript. Lo siguiente no funcionará:$x.Test?()

PowerShell 6 y versiones anteriores

Las versiones de PowerShell anteriores a la 7 tienen un operador de fusión nulo real, o al menos un operador que es capaz de tal comportamiento. Ese operador es -ne:

# Format:
# ($a, $b, $c -ne $null)[0]
($null, 'alpha', 1 -ne $null)[0]

# Output:
alpha

Es un poco más versátil que un operador de fusión nulo, ya que crea una matriz de todos los elementos no nulos:

$items = $null, 'alpha', 5, 0, '', @(), $null, $true, $false
$instances = $items -ne $null
[string]::Join(', ', ($instances | ForEach-Object -Process { $_.GetType() }))

# Result:
System.String, System.Int32, System.Int32, System.String, System.Object[],
System.Boolean, System.Boolean

-eq funciona de manera similar, lo cual es útil para contar entradas nulas:

($null, 'a', $null -eq $null).Length

# Result:
2

Pero de todos modos, aquí hay un caso típico para reflejar el ??operador de C # :

'Filename: {0}' -f ($filename, 'Unknown' -ne $null)[0] | Write-Output

Explicación

Esta explicación se basa en una sugerencia de edición de un usuario anónimo. ¡Gracias, quienquiera que seas!

Según el orden de las operaciones, esto funciona en el siguiente orden:

  1. El ,operador crea una matriz de valores para probar.
  2. El -neoperador filtra los elementos de la matriz que coinciden con el valor especificado, en este caso, nulo. El resultado es una matriz de valores no nulos en el mismo orden que la matriz creada en el Paso 1.
  3. [0] se utiliza para seleccionar el primer elemento de la matriz filtrada.

Simplificando eso:

  1. Cree una matriz de valores posibles, en orden preferido
  2. Excluir todos los valores nulos de la matriz
  3. Toma el primer elemento de la matriz resultante

Advertencias

A diferencia del operador de fusión nula de C #, se evaluarán todas las expresiones posibles, ya que el primer paso es crear una matriz.

Zenexer
fuente
Terminé usando una versión modificada de su respuesta en la mía , porque necesitaba permitir que todas las instancias en la fusión fueran nulas, sin lanzar una excepción. Muy buena forma de hacerlo, aprendí mucho. :)
Johny Skovdal
Este método evita los cortocircuitos. Defina function DidItRun($a) { Write-Host "It ran with $a"; return $a }y corra ((DidItRun $null), (DidItRun 'alpha'), 1 -ne $null)[0]para ver esto.
jpmc26
@ jpmc26 Sí, eso es por diseño.
Zenexer
Cualquier intento de definir una función de fusión también es probable que elimine el cortocircuito, ¿no es así?
Chris F Carroll
8
¡Ahhhh, la precedencia del operador de PowerShell me mata! Siempre olvido que el operador de coma ,tiene una prioridad tan alta. Para cualquiera que esté confundido sobre cómo funciona esto, ($null, 'alpha', 1 -ne $null)[0]es lo mismo que (($null, 'alpha', 1) -ne $null)[0]. De hecho, los únicos dos operadores de "guión" con mayor precendencia son -splity -join(y guión unario, es decir, -4o -'56').
Plutón
15

Esta es solo la mitad de una respuesta a la primera mitad de la pregunta, por lo que un cuarto de respuesta si lo desea, pero hay una alternativa mucho más simple al operador de fusión nula siempre que el valor predeterminado que desea usar sea en realidad el valor predeterminado para el tipo :

string s = myval ?? "";

Puede escribirse en Powershell como:

([string]myval)

O

int d = myval ?? 0;

se traduce en Powershell:

([int]myval)

Encontré el primero de estos útil al procesar un elemento xml que podría no existir y que, si existiera, podría tener espacios en blanco no deseados alrededor:

$name = ([string]$row.td[0]).Trim()

La conversión a cadena protege contra el elemento nulo y evita cualquier riesgo de Trim()falla.

Duncan
fuente
([string]$null)-> ""parece un "efecto secundario útil, pero desafortunado" :(
user2864740
11

$null, $null, 3 | Select -First 1

devoluciones

3

Chris F Carroll
fuente
1
Pero el comentario de @ thomas-s-trias sobre el cortocircuito sigue siendo válido.
Chris F Carroll
9

Si instala el módulo de extensiones de la comunidad de Powershell , puede usar:

?? es el alias de Invoke-NullCoalescing.

$s = ?? {$myval}  {"New Value"}

?: es el alias de Invoke-Ternary.

$x = ?: {$myval -eq $null} {""} {$otherval}
EBGreen
fuente
En realidad, esos no son comandos de PowerShell. Los tienes junto con pscx:?: -> Invoke-Ternary
BartekB
... código real perdido y resultado ..;) Get-Command -Module pscx -CommandType alias | donde {$ _. Name -match '\ ?.' } | foreach {"{0}: {1}" -f $ _. Nombre, $ _. Definición}?:: Invoke-Ternary ?? : Invoke-NullCoalescing
BartekB
Vaya ... tienes toda la razón. A menudo olvido que incluso tengo ese módulo cargando.
EBGreen
3
@($null,$null,"val1","val2",5) | select -First 1
staticrysis
fuente
2
function coalesce {
   Param ([string[]]$list)
   #$default = $list[-1]
   $coalesced = ($list -ne $null)
   $coalesced[0]
 }
 function coalesce_empty { #COALESCE for empty_strings

   Param ([string[]]$list)
   #$default = $list[-1]
   $coalesced = (($list -ne $null) -ne '')[0]
   $coalesced[0]
 }
Shawn
fuente
2

A menudo encuentro que también necesito tratar la cadena vacía como nula cuando uso coalesce. Terminé escribiendo una función para esto, que usa la solución de Zenexer para fusionar para la fusión nula simple, y luego usé la de Keith Hill para verificar nulos o vacíos, y lo agregué como una bandera para que mi función pudiera hacer ambas cosas.

Una de las ventajas de esta función es que también maneja tener todos los elementos nulos (o vacíos), sin lanzar una excepción. También se puede usar para muchas variables de entrada arbitrarias, gracias a cómo PowerShell maneja las entradas de la matriz.

function Coalesce([string[]] $StringsToLookThrough, [switch]$EmptyStringAsNull) {
  if ($EmptyStringAsNull.IsPresent) {
    return ($StringsToLookThrough | Where-Object { $_ } | Select-Object -first 1)
  } else {
    return (($StringsToLookThrough -ne $null) | Select-Object -first 1)
  }  
}

Esto produce los siguientes resultados de la prueba:

Null coallesce tests:
1 (w/o flag)  - empty/null/'end'                 : 
1 (with flag) - empty/null/'end'                 : end
2 (w/o flag)  - empty/null                       : 
2 (with flag) - empty/null                       : 
3 (w/o flag)  - empty/null/$false/'end'          : 
3 (with flag) - empty/null/$false/'end'          : False
4 (w/o flag)  - empty/null/"$false"/'end'        : 
4 (with flag) - empty/null/"$false"/'end'        : False
5 (w/o flag)  - empty/'false'/null/"$false"/'end': 
5 (with flag) - empty/'false'/null/"$false"/'end': false

Código de prueba:

Write-Host "Null coalesce tests:"
Write-Host "1 (w/o flag)  - empty/null/'end'                 :" (Coalesce '', $null, 'end')
Write-Host "1 (with flag) - empty/null/'end'                 :" (Coalesce '', $null, 'end' -EmptyStringAsNull)
Write-Host "2 (w/o flag)  - empty/null                       :" (Coalesce('', $null))
Write-Host "2 (with flag) - empty/null                       :" (Coalesce('', $null) -EmptyStringAsNull)
Write-Host "3 (w/o flag)  - empty/null/`$false/'end'          :" (Coalesce '', $null, $false, 'end')
Write-Host "3 (with flag) - empty/null/`$false/'end'          :" (Coalesce '', $null, $false, 'end' -EmptyStringAsNull)
Write-Host "4 (w/o flag)  - empty/null/`"`$false`"/'end'        :" (Coalesce '', $null, "$false", 'end')
Write-Host "4 (with flag) - empty/null/`"`$false`"/'end'        :" (Coalesce '', $null, "$false", 'end' -EmptyStringAsNull)
Write-Host "5 (w/o flag)  - empty/'false'/null/`"`$false`"/'end':" (Coalesce '', 'false', $null, "$false", 'end')
Write-Host "5 (with flag) - empty/'false'/null/`"`$false`"/'end':" (Coalesce '', 'false', $null, "$false", 'end' -EmptyStringAsNull)
Johny Skovdal
fuente
1

Lo más cerca que puedo conseguir es: $Val = $MyVal |?? "Default Value"

Implementé el operador de fusión nula para lo anterior de esta manera:

function NullCoalesc {
    param (
        [Parameter(ValueFromPipeline=$true)]$Value,
        [Parameter(Position=0)]$Default
    )

    if ($Value) { $Value } else { $Default }
}

Set-Alias -Name "??" -Value NullCoalesc

El operador ternario condicional podría implementarse de manera similar.

function ConditionalTernary {
    param (
        [Parameter(ValueFromPipeline=$true)]$Value,
        [Parameter(Position=0)]$First,
        [Parameter(Position=1)]$Second
    )

    if ($Value) { $First } else { $Second }
}

Set-Alias -Name "?:" -Value ConditionalTernary

Y usado como: $Val = $MyVal |?: $MyVal "Default Value"

Elon Mallin
fuente