Operador ternario en PowerShell

214

Por lo que sé, PowerShell no parece tener una expresión integrada para el llamado operador ternario .

Por ejemplo, en el lenguaje C, que admite el operador ternario, podría escribir algo como:

<condition> ? <condition-is-true> : <condition-is-false>;

Si eso realmente no existe en PowerShell, ¿cuál sería la mejor manera (es decir, fácil de leer y mantener) para lograr el mismo resultado?

mguassa
fuente
55
Echa un vistazo a github.com/nightroman/PowerShellTraps/tree/master/Basic/… . Si esto es lo que está buscando, puedo responderlo.
Roman Kuzmin
2
Es un operador condicional o ternario si . No es "el operador ternario" ya que todo lo que significa es un operador ( cualquier operador) que toma tres argumentos.
Damien_The_Unbeliever
77
@Damien_The_Unbeliever Eso es técnicamente cierto, pero a menudo se llama operador ternario. "Dado que este operador es a menudo el único operador ternario existente en el idioma, a veces se lo denomina simplemente" el operador ternario ". En algunos idiomas, este operador se conoce como" el operador condicional ". Operación ternaria
mguassa
Visual Basic no tiene un operador ternario verdadero, pero considera que el IF y el IFF son funcionalmente equivalentes.
Matt
@ Matt Eso es incorrecto. La IIF función siempre evaluará ambos operandos. La Ifdeclaración no será - ver stackoverflow.com/questions/1220411/... (las versiones más recientes de VB.NET añaden una expresión ternaria: If (x, y, z))
user2864740

Respuestas:

320
$result = If ($condition) {"true"} Else {"false"}

Todo lo demás es complejidad incidental y, por lo tanto, debe evitarse.

Para usar en o como una expresión, no solo una asignación, envuélvala $(), así:

write-host  $(If ($condition) {"true"} Else {"false"}) 
fbehrens
fuente
3
Eso funciona en el lado derecho de un igual, pero no exactamente como cabría esperar de un operador ternario: estos fallan: "a" + If ($ condición) {"verdadero"} Else {"falso"} y "a" + (If ($ condition) {"true"} Else {"false"}) Esto funciona (aún no estoy seguro de por qué): "a" + $ (If ($ condition) {"true"} Else {"false "})
Lamarth
12
@Lamarth: eso funciona porque el $()contenedor fuerza la evaluación de la declaración como una expresión, devolviendo así el valor verdadero del valor falso, al igual que se esperaría de un operador ternario. Esto es lo más cerca que estará en PowerShell AFAIK.
KeithS
44
A diferencia de algunas de las otras respuestas "sin función", esto también asegurará que solo se ejecute una rama.
user2864740
63

La construcción de PowerShell más cercana que he podido inventar para emular es:

@({'condition is false'},{'condition is true'})[$condition]
mjolinor
fuente
15
({true}, {false})[!$condition]es ligeramente mejor (quizás): a) orden tradicional de partes verdaderas y falsas; b) $conditionno tiene que ser solo 0 o 1 o $ false, $ true. El operador lo !convierte según sea necesario. Es decir $condition, por ejemplo, 42:! 42 ~ $ false ~ 0 ~ primera expresión.
Roman Kuzmin
1
No es exactamente idéntico, @RomanKuzmin. El ejemplo de mjolinor devuelve una cadena. Pero los mismos valores de cadena de su ejemplo conectados a su expresión devuelven un bloque de script. :-(
Michael Sorens
55
Por {true}y {false}quiero decir <true expression>y <false expression>, no bloques de guiones. Perdón por no ser exacto.
Roman Kuzmin
1
Gracias por la aclaración, @ RomanKuzmin: ahora veo el valor en su sugerencia.
Michael Sorens
12
Esto obligará a evaluar ansiosamente las opciones izquierda y derecha: esto es muy diferente de una operación ternaria adecuada.
user2864740
24

Según esta publicación de blog de PowerShell , puede crear un alias para definir un ?:operador:

set-alias ?: Invoke-Ternary -Option AllScope -Description "PSCX filter alias"
filter Invoke-Ternary ([scriptblock]$decider, [scriptblock]$ifTrue, [scriptblock]$ifFalse) 
{
   if (&$decider) { 
      &$ifTrue
   } else { 
      &$ifFalse 
   }
}

Úselo así:

$total = ($quantity * $price ) * (?:  {$quantity -le 10} {.9} {.75})
Edward Brey
fuente
No parece haber una razón por la cual 'decidir' sea un bloque de script. Si ?:alguna vez se evalúa requerirá que se evalúen los argumentos. Sin un bloque de script tendría un uso similar, por ejemplo. (?: ($quantity -le 10) {.9} {.75})
usuario2864740
Esa es una característica interesante, pero puede hacer que su script no sea estándar, por ejemplo, su script o partes de él pueden no portarse a menos que la definición de alias también se porte con él. Básicamente, estás creando tu propio sub-idioma en este momento. Todo esto puede conducir a un mayor costo de mantenimiento debido a una mayor complejidad y una menor legibilidad.
Vance McCorkle
1
@VanceMcCorkle Punto sólido. La brevedad personalizada vale su costo si es útil a menudo. Pero si la situación solo ocurre raramente, me quedaría con una expresión "si".
Edward Brey
21

Yo también busqué una mejor respuesta, y aunque la solución en la publicación de Edward es "ok", encontré una solución mucho más natural en esta publicación de blog.

Corto y dulce:

# ---------------------------------------------------------------------------
# Name:   Invoke-Assignment
# Alias:  =
# Author: Garrett Serack (@FearTheCowboy)
# Desc:   Enables expressions like the C# operators: 
#         Ternary: 
#             <condition> ? <trueresult> : <falseresult> 
#             e.g. 
#                status = (age > 50) ? "old" : "young";
#         Null-Coalescing 
#             <value> ?? <value-if-value-is-null>
#             e.g.
#                name = GetName() ?? "No Name";
#             
# Ternary Usage:  
#         $status == ($age > 50) ? "old" : "young"
#
# Null Coalescing Usage:
#         $name = (get-name) ? "No Name" 
# ---------------------------------------------------------------------------

# returns the evaluated value of the parameter passed in, 
# executing it, if it is a scriptblock   
function eval($item) {
    if( $item -ne $null ) {
        if( $item -is "ScriptBlock" ) {
            return & $item
        }
        return $item
    }
    return $null
}

# an extended assignment function; implements logic for Ternarys and Null-Coalescing expressions
function Invoke-Assignment {
    if( $args ) {
        # ternary
        if ($p = [array]::IndexOf($args,'?' )+1) {
            if (eval($args[0])) {
                return eval($args[$p])
            } 
            return eval($args[([array]::IndexOf($args,':',$p))+1]) 
        }

        # null-coalescing
        if ($p = ([array]::IndexOf($args,'??',$p)+1)) {
            if ($result = eval($args[0])) {
                return $result
            } 
            return eval($args[$p])
        } 

        # neither ternary or null-coalescing, just a value  
        return eval($args[0])
    }
    return $null
}

# alias the function to the equals sign (which doesn't impede the normal use of = )
set-alias = Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment."

Lo que facilita hacer cosas como (más ejemplos en la publicación del blog):

$message == ($age > 50) ? "Old Man" :"Young Dude" 
Garrett Serack
fuente
1
Esto es bastante asombroso. Simplemente no me gusta usar =como alias, porque ==se usa en C # y en muchos otros lenguajes para verificar la igualdad. Tener cierta experiencia en C # es bastante común entre los powerhellers y eso hace que el código resultante sea un poco confuso. :posiblemente sería un mejor alias. Usarlo en conjunción con la asignación de variables =:podría recordarle a alguien la asignación utilizada en Pascal, pero no tiene ningún equivalente inmediato (que yo sepa) en VB.NET ni C #.
nohwnd
Puede establecer esto en el alias que desee. : o ~o lo que sea que flota tu bote. : D set-alias : Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment." # ternary $message =: ($age > 50) ? "Old Man" :"Young Dude" # null coalescing $message =: $foo ?? "foo was empty"`
Garrett Serack
Lo sé, y lo hice, solo comentaba por qué usaría otro alias.
nohwnd
15

Pruebe la instrucción switch de powershell como alternativa, especialmente para la asignación de variables: varias líneas, pero legible.

Ejemplo,

$WinVer = switch ( Test-Path $Env:windir\SysWOW64 ) {
  $true    { "64-bit" }
  $false   { "32-bit" }
}
"This version of Windows is $WinVer"
nudl
fuente
1
Esto puede ser un poco más detallado, pero hay beneficios significativos sobre muchas de las otras respuestas. En particular, no hay dependencias en el código externo, ni en las funciones que se copian / pegan en un script o módulo.
bshacklett
9

Dado que generalmente se usa un operador ternario al asignar un valor, debe devolver un valor. Esta es la forma en que puede funcionar:

$var=@("value if false","value if true")[[byte](condition)]

Estúpido, pero trabajando. Además, esta construcción se puede utilizar para convertir rápidamente un int en otro valor, simplemente agregue elementos de matriz y especifique una expresión que devuelva valores no negativos basados ​​en 0.

Vesper
fuente
66
Esto obligará a evaluar ansiosamente las opciones izquierda y derecha: esto es muy diferente de una operación ternaria adecuada.
user2864740
6

Como ya lo he usado muchas veces y no lo vi listado aquí, agregaré mi pieza:

$var = @{$true="this is true";$false="this is false"}[1 -eq 1]

más feo de todos!

un poco fuente

sauce
fuente
44
Esto obligará a evaluar ansiosamente las opciones izquierda y derecha: esto es muy diferente de una operación ternaria adecuada.
user2864740
@ user2864740 eso se debe a que no hay una operación ternaria adecuada en PS. Esta es una variante sintética.
Viggo Lundén
2
@ ViggoLundén La falta de un operador ternario adecuado no reduce la exactitud del comentario. Esta "variante sintética" tiene un comportamiento diferente .
user2864740
Tienes razón, y después de volver a leer tu comentario, entiendo cómo puede marcar una verdadera diferencia. Gracias.
sodawillow
5

Recientemente he mejorado (abrir PullRequest) los operadores ternarios condicionales y de fusión nula en PoweShell lib 'Pscx'
Pls eche un vistazo a mi solución.


Mi rama de tema de github : UtilityModule_Invoke-Operators

Funciones:

Invoke-Ternary
Invoke-TernaryAsPipe
Invoke-NullCoalescing
NullCoalescingAsPipe

Alias

Set-Alias :?:   Pscx\Invoke-Ternary                     -Description "PSCX alias"
Set-Alias ?:    Pscx\Invoke-TernaryAsPipe               -Description "PSCX alias"
Set-Alias :??   Pscx\Invoke-NullCoalescing              -Description "PSCX alias"
Set-Alias ??    Pscx\Invoke-NullCoalescingAsPipe        -Description "PSCX alias"

Uso

<condition_expression> |?: <true_expression> <false_expression>

<variable_expression> |?? <alternate_expression>

Como expresión puede pasar:
$ null, un literal, una variable, una expresión 'externa' ($ b -eq 4) o un bloque de script {$ b -eq 4}

Si una variable en la expresión variable es $ null o no existe , la expresión alternativa se evalúa como salida.

andiDo
fuente
4

PowerShell actualmente no tiene un If Inline nativo (o If ternario ) pero podría considerar usar el cmdlet personalizado:

IIf <condition> <condition-is-true> <condition-is-false>
Consulte: PowerShell en línea si (IIf)

hierro
fuente
La publicación vinculada muestra cómo crear una IIffunción. Sin embargo, no existe una IIffunción / cmdlet estándar de PowerShell .
user2864740
1
@ user2864740, ¿y qué? ¿eso excluye la respuesta como una posible respuesta utilizable a la pregunta: " cuál sería la mejor manera (es decir, fácil de leer y mantener) para lograr el mismo resultado?" Pero aparte de eso, el enlace se refiere a la pregunta IIf (publicada el 5 de septiembre '14) que es muy similar a esta (¿duplicado?). El resto de las respuestas en la pregunta vinculada también podría verse como un valor agregado (y donde no lo hacen, es principalmente porque son duplicados).
IRON
Un poco de explicación es muy útil, y esta es la razón por la cual se desalientan los enlaces desnudos , ya que no se cierra por duplicados si no hay información adicional agregada. Considere que una explicación muy pequeña aumentaría significativamente la calidad de una respuesta de enlace simple: "Powershell no admite un 'ternario' directo (^ pero vea otras respuestas). Sin embargo, es posible escribir una función como (^ usando este método, o ver otras respuestas) .. ", pero ese no es mi trabajo para agregar. Las respuestas se pueden modificar / actualizar / etc. según corresponda.
user2864740
@ user2864740, gracias por sus comentarios, he realizado ajustes a la respuesta en consecuencia.
iRon
1

Aquí hay un enfoque alternativo de función personalizada:

function Test-TernaryOperatorCondition {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [bool]$ConditionResult
        ,
        [Parameter(Mandatory = $true, Position = 0)]
        [PSObject]$ValueIfTrue
        ,
        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateSet(':')]
        [char]$Colon
        ,
        [Parameter(Mandatory = $true, Position = 2)]
        [PSObject]$ValueIfFalse
    )
    process {
        if ($ConditionResult) {
            $ValueIfTrue
        }
        else {
            $ValueIfFalse
        }
    }
}
set-alias -Name '???' -Value 'Test-TernaryOperatorCondition'

Ejemplo

1 -eq 1 |??? 'match' : 'nomatch'
1 -eq 2 |??? 'match' : 'nomatch'

Diferencias explicadas

  • ¿Por qué son 3 signos de interrogación en lugar de 1?
    • El ?personaje ya es un alias para Where-Object.
    • ?? se usa en otros idiomas como operador de fusión nula, y quería evitar confusiones.
  • ¿Por qué necesitamos la tubería antes del comando?
    • Como estoy utilizando la canalización para evaluar esto, todavía necesitamos este personaje para canalizar la condición en nuestra función
  • ¿Qué sucede si paso en una matriz?
    • Obtenemos un resultado para cada valor; es decir, -2..2 |??? 'match' : 'nomatch'da: match, match, nomatch, match, match(es decir, dado que cualquier int que no sea cero se evalúa como true; mientras que cero se evalúa como false).
    • Si no quieres eso, convierte la matriz a bool; ([bool](-2..2)) |??? 'match' : 'nomatch'(o simplemente: [bool](-2..2) |??? 'match' : 'nomatch')
JohnLBevan
fuente
1

Si solo está buscando una forma sintácticamente simple de asignar / devolver una cadena o un valor numérico basado en una condición booleana, puede usar el operador de multiplicación de esta manera:

"Condition is "+("true"*$condition)+("false"*!$condition)
(12.34*$condition)+(56.78*!$condition)

Si solo le interesa el resultado cuando algo es cierto, puede omitir la parte falsa por completo (o viceversa), por ejemplo, un sistema de puntuación simple:

$isTall = $true
$isDark = $false
$isHandsome = $true

$score = (2*$isTall)+(4*$isDark)+(10*$isHandsome)
"Score = $score"
# or
# "Score = $((2*$isTall)+(4*$isDark)+(10*$isHandsome))"

Tenga en cuenta que el valor booleano no debe ser el término principal en la multiplicación, es decir, $ condition * "true", etc. no funcionará.

nimbell
fuente
1

A partir de PowerShell versión 7, el operador ternario está integrado en PowerShell.

1 -gt 2 ? "Yes" : "No"
Trevor Sullivan
fuente