Operatore ternario in PowerShell


214

Da quello che so, PowerShell non sembra avere un'espressione integrata per il cosiddetto operatore ternario .

Ad esempio, nel linguaggio C, che supporta l'operatore ternario, potrei scrivere qualcosa del tipo:

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

Se questo non esiste davvero in PowerShell, quale sarebbe il modo migliore (cioè facile da leggere e da mantenere) per ottenere lo stesso risultato?


5
Dai un'occhiata a github.com/nightroman/PowerShellTraps/tree/master/Basic/… . Se questo è quello che stai cercando, posso farne una risposta.
Roman Kuzmin,

2
È un operatore condizionale o ternario se . Non è "l'operatore ternario" poiché tutto ciò che significa è un operatore ( qualsiasi operatore) che accetta tre argomenti.
Damien_The_Unbeliever,

7
@Damien_The_Unbeliever Questo è tecnicamente vero, ma spesso viene chiamato operatore ternario. "Poiché questo operatore è spesso l'unico operatore ternario esistente nella lingua, a volte viene semplicemente indicato come" l'operatore ternario ". In alcune lingue, questo operatore viene indicato come" l'operatore condizionale ". Operazione
ternaria

Visual Basic non ha un vero operatore ternario ma considera IF e IFF funzionalmente equivalenti.
Matt,

@Matt È errato. La IIF funzione valuterà sempre entrambi gli operandi. La Ifdichiarazione non sarà - vedi stackoverflow.com/questions/1220411/... (versioni più recenti VB.NET aggiunto un'espressione ternario: If (x, y, z))
user2864740

Risposte:


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

Tutto il resto è complessità accidentale e quindi da evitare.

Per l'uso in o come espressione, non solo come compito, includilo $(), quindi:

write-host  $(If ($condition) {"true"} Else {"false"}) 

3
Funziona sul lato destro di un uguale, ma non esattamente come ci si aspetterebbe da un operatore ternario - questi falliscono: "a" + If ($ condition) {"true"} Else {"false"} e "a" + (If ($ condition) {"true"} Else {"false"}) Funziona (non sono ancora sicuro del perché): "a" + $ (If ($ condition) {"true"} Else {"false "})
Lamarth,

12
@Lamarth - Funziona perché il $()wrapper forza la valutazione dell'istruzione come espressione, restituendo così il valore reale del valore falso, proprio come ci si aspetterebbe da un operatore ternario. Questo è il più vicino che otterrai in PowerShell AFAIK.
KeithS

4
A differenza di alcune delle altre risposte "non funzionali", ciò garantirà anche l' esecuzione di un solo ramo.
user2864740

63

Il costrutto PowerShell più vicino che ho potuto inventare per emulare è:

@({'condition is false'},{'condition is true'})[$condition]

15
({true}, {false})[!$condition]è leggermente migliore (forse): a) ordine tradizionale di parti vere e false; b) $conditionnon deve essere solo 0 o 1 o $ false, $ true. L'operatore lo !converte secondo necessità. Vale a $conditiondire, ad esempio, 42:! 42 ~ $ false ~ 0 ~ prima espressione.
Roman Kuzmin,

1
Non del tutto identico, @RomanKuzmin. L'esempio di mjolinor restituisce una stringa. Ma gli stessi valori di stringa del suo esempio collegati alla tua espressione restituiscono un blocco di script. :-(
Michael Sorens,

5
Con {true}e {false}intendo <true expression>e <false expression>non blocchi di script. Scusa se non sono preciso.
Roman Kuzmin,

1
Grazie per il chiarimento, @ RomanKuzmin - ora vedo il valore nel tuo suggerimento.
Michael Sorens,

12
Questo costringerà una valutazione avida delle opzioni sinistra e destra: questo è molto diverso da una corretta operazione ternaria.
user2864740


24

Per questo post sul blog di PowerShell , puoi creare un alias per definire un ?:operatore:

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

Usalo in questo modo:

$total = ($quantity * $price ) * (?:  {$quantity -le 10} {.9} {.75})

Non sembra esserci una ragione per cui "decider" sia uno scriptblock. Se ?:mai valutato, richiederà la valutazione degli argomenti. Senza un blocco di script avrebbe un utilizzo simile, ad es. (?: ($quantity -le 10) {.9} {.75})
user2864740,

Questa è una funzionalità interessante, ma può rendere lo script come non standard, ad esempio lo script o parti di esso potrebbero non essere portati a meno che anche la definizione di alias non sia trasferita con esso. Fondamentalmente stai creando la tua sotto-lingua a questo punto. Tutto ciò può comportare un aumento dei costi di manutenzione a causa della maggiore complessità e della minore leggibilità.
Vance McCorkle,

1
@VanceMcCorkle Punto solido. La terseness personalizzato vale il suo costo se utile spesso. Ma se la situazione si verifica solo raramente, rimarrei con un'espressione "if".
Edward Brey,

21

Anch'io, ho cercato una risposta migliore, e mentre la soluzione nel post di Edward è "ok", ho trovato una soluzione molto più naturale in questo post del blog

Breve e dolce:

# ---------------------------------------------------------------------------
# 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."

Il che rende facile fare cose come (altri esempi nel post del blog):

$message == ($age > 50) ? "Old Man" :"Young Dude" 

1
è davvero fantastico. Non mi piace usare =come alias, perché ==è usato in C # e in molte altre lingue per il controllo dell'uguaglianza. Avere una certa esperienza in C # è abbastanza comune tra i powerheller e questo rende il codice risultante un po 'confuso. :sarebbe forse un alias migliore. Usarlo in combinazione con un compito variabile =:potrebbe ricordare a qualcuno il compito usato in Pascal, ma non ha alcun equivalente immediato (che io conosca) in VB.NET né in C #.
Nohwnd,

Puoi impostarlo su qualsiasi alias desideri. : o ~qualunque cosa galleggi la tua barca. : 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 so, e l'ho fatto, stavo solo commentando perché avrei usato un altro alias.
Nohwnd,

15

Prova l' istruzione switch di PowerShell in alternativa, soprattutto per l'assegnazione di variabili: più righe, ma leggibile.

Esempio,

$WinVer = switch ( Test-Path $Env:windir\SysWOW64 ) {
  $true    { "64-bit" }
  $false   { "32-bit" }
}
"This version of Windows is $WinVer"

1
Questo può essere leggermente più dettagliato, ma ci sono benefici significativi su molte delle altre risposte. In particolare, non vi sono dipendenze dal codice esterno, né dalle funzioni che vengono copiate / incollate in uno script o in un modulo.
bshacklett,

9

Poiché un operatore ternario viene solitamente utilizzato quando si assegna un valore, dovrebbe restituire un valore. Questo è il modo in cui può funzionare:

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

Stupido, ma funzionante. Inoltre questa costruzione può essere utilizzata per trasformare rapidamente un int in un altro valore, basta aggiungere elementi di array e specificare un'espressione che restituisca valori non negativi basati su 0.


6
Questo costringerà una valutazione avida delle opzioni sinistra e destra: questo è molto diverso da una corretta operazione ternaria.
user2864740

6

Dato che l'ho già usato molte volte e non l'ho visto elencato qui, aggiungerò il mio pezzo:

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

il più brutto di tutti!

un po 'fonte


4
Questo costringerà una valutazione avida delle opzioni sinistra e destra: questo è molto diverso da una corretta operazione ternaria.
user2864740

@ user2864740 questo perché non esiste una corretta operazione ternaria in PS. Questa è una variante sintetica.
Viggo Lundén,

2
@ ViggoLundén La mancanza di un adeguato operatore ternario non riduce la correttezza del commento. Questa "variante sintetica" ha un comportamento diverso .
user2864740

Hai ragione, e dopo aver riletto il tuo commento capisco come può fare davvero la differenza. Grazie.
sodawillow,

5

Di recente ho migliorato (apri PullRequest) gli operatori ternari condizionali e a coalescenza nulla nella libreria PoweShell 'Pscx'
Pls danno un'occhiata alla mia soluzione.


Il mio argomento argomento github: UtilityModule_Invoke-Operators

funzioni:

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>

Come espressione puoi passare:
$ null, un valore letterale, una variabile, un'espressione "esterna" ($ b -eq 4) o uno scriptblock {$ b -eq 4}

Se una variabile nell'espressione variabile è $ null o non esiste , l'espressione alternativa viene valutata come output.


4

Al momento PowerShell non ha un If (o ternario If ) nativo ma potresti prendere in considerazione l'uso del cmdlet personalizzato:

IIf <condition> <condition-is-true> <condition-is-false>
Vedere: PowerShell Inline If (IIf)


Il post collegato mostra come creare una IIffunzione. Tuttavia, non esiste una IIffunzione / cmdlet standard di PowerShell .
user2864740

1
@ user2864740, E allora? questo esclude la risposta come possibile risposta utilizzabile alla domanda: " quale sarebbe il modo migliore (cioè facile da leggere e da mantenere) per ottenere lo stesso risultato?" Ma a parte questo, il collegamento si riferisce alla domanda IIf (pubblicata il 5 settembre 14) che è molto simile a questa (duplicato?). Il resto delle risposte nella domanda collegata potrebbe anche essere visto come un valore aggiunto (e dove no, è principalmente perché sono duplicati).
iRon,

Un po 'di spiegazione fa molta strada, ed è per questo che i link nudi sono scoraggiati, poiché non si sta chiudendo per i duplicati se non ci sono ulteriori informazioni aggiunte. Si consideri che una piccola spiegazione potrebbe aumentare in modo significativo la qualità di un tale collegamento nuda risposta: "Powershell non supporta un 'ternario' diretta (^ ma vedere altre risposte) Tuttavia, è possibile scrivere una funzione come (^ usando. questo metodo, o vedi altre risposte) .. ", ma non è il mio lavoro da aggiungere. Le risposte possono essere modificate / aggiornate / ecc. Come applicabile.
user2864740

@utente2864740, grazie per il tuo feedback, ho apportato le modifiche alla risposta di conseguenza.
iRon,

1

Ecco un approccio alternativo alle funzioni personalizzate:

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'

Esempio

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

Spiegazioni sulle differenze

  • Perché sono 3 punti interrogativi anziché 1?
    • Il ?personaggio è già un alias per Where-Object.
    • ?? è usato in altre lingue come operatore a coalescenza nulla e volevo evitare confusione.
  • Perché abbiamo bisogno della pipa prima del comando?
    • Dal momento che sto usando la pipeline per valutare questo, abbiamo ancora bisogno di questo personaggio per convogliare la condizione nella nostra funzione
  • Cosa succede se passo in un array?
    • Otteniamo un risultato per ogni valore; ie -2..2 |??? 'match' : 'nomatch'dà: match, match, nomatch, match, match(cioè poiché qualsiasi int diverso da zero valuta true; mentre zero valuta in false).
    • Se non lo desideri, converti l'array in un bool; ([bool](-2..2)) |??? 'match' : 'nomatch'(o semplicemente: [bool](-2..2) |??? 'match' : 'nomatch')

1

Se stai solo cercando un modo sintatticamente semplice per assegnare / restituire una stringa o un valore numerico basato su una condizione booleana, puoi utilizzare l'operatore di moltiplicazione in questo modo:

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

Se sei sempre interessato al risultato quando qualcosa è vero, puoi semplicemente omettere completamente la parte falsa (o viceversa), ad esempio un semplice sistema di punteggio:

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

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

Si noti che il valore booleano non dovrebbe essere il termine principale nella moltiplicazione, ovvero $ condition * "true" ecc. Non funzionerà.


1

A partire dalla versione 7 di PowerShell, l'operatore ternario è integrato in PowerShell.

1 -gt 2 ? "Yes" : "No"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.