Opérateur ternaire dans PowerShell

214

D'après ce que je sais, PowerShell ne semble pas avoir d'expression intégrée pour ce que l'on appelle l' opérateur ternaire .

Par exemple, dans le langage C, qui prend en charge l'opérateur ternaire, je pourrais écrire quelque chose comme:

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

Si cela n'existe pas vraiment dans PowerShell, quelle serait la meilleure façon (c'est-à-dire facile à lire et à entretenir) pour obtenir le même résultat?

mguassa
la source
5
Jetez un œil sur github.com/nightroman/PowerShellTraps/tree/master/Basic/… . Si c'est ce que vous cherchez, je peux en faire une réponse.
Roman Kuzmin
2
C'est un opérateur conditionnel ou ternaire si . Ce n'est pas "l'opérateur ternaire" puisque tout ce que cela signifie est un opérateur ( n'importe quel opérateur) qui prend trois arguments.
Damien_The_Unbeliever
7
@Damien_The_Unbeliever C'est techniquement vrai, mais il est souvent appelé opérateur ternaire. "Puisque cet opérateur est souvent le seul opérateur ternaire existant dans la langue, il est parfois simplement appelé" l'opérateur ternaire ". Dans certaines langues, cet opérateur est appelé" l'opérateur conditionnel ". Opération ternaire
mguassa
Visual Basic n'a pas de véritable opérateur ternaire mais considère que IF et IFF sont fonctionnellement équivalents.
Matt
@Matt C'est incorrect. La IIF fonction évaluera toujours les deux opérandes. La Ifdéclaration ne sera pas - voir stackoverflow.com/questions/1220411/... (nouvelles versions VB.NET ajouté une expression ternaire: If (x, y, z))
user2864740

Réponses:

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

Tout le reste est une complexité fortuite et donc à éviter.

Pour l'utiliser dans ou en tant qu'expression, pas seulement une affectation, enveloppez-la $(), ainsi:

write-host  $(If ($condition) {"true"} Else {"false"}) 
fbehrens
la source
3
Cela fonctionne sur le côté droit d'un égal, mais pas tout à fait comme on pourrait s'y attendre d'un opérateur ternaire - ceux-ci échouent: "a" + If ($ condition) {"true"} Sinon {"false"} et "a" + (If ($ condition) {"true"} Else {"false"}) Cela fonctionne (je ne sais pas encore pourquoi): "a" + $ (If ($ condition) {"true"} Else {"false "})
Lamarth
12
@Lamarth - Cela fonctionne parce que le $()wrapper force l'évaluation de l'instruction en tant qu'expression, retournant ainsi soit la vraie valeur de la fausse valeur, un peu comme un opérateur ternaire serait censé le faire. C'est le plus proche que vous obtiendrez dans PowerShell AFAIK.
KeithS
4
Contrairement à certaines des autres réponses "non fonction", cela garantira également qu'une seule branche est exécutée.
user2864740
63

La construction PowerShell la plus proche que j'ai pu imaginer est:

@({'condition is false'},{'condition is true'})[$condition]
mjolinor
la source
15
({true}, {false})[!$condition]est légèrement meilleur (peut-être): a) ordre traditionnel des parties vraies et fausses; b) $conditionne doit pas être simplement 0 ou 1 ou $ false, $ true. L'opérateur le !convertit selon les besoins. Cela $conditionpeut être, disons, 42:! 42 ~ $ false ~ 0 ~ première expression.
Roman Kuzmin
1
Pas tout à fait identique, @RomanKuzmin. L'exemple de mjolinor renvoie une chaîne. Mais les mêmes valeurs de chaîne de son exemple connectées à votre expression retournent un bloc de script. :-(
Michael Sorens
5
Par {true}et {false}je veux dire <true expression>et <false expression>pas des blocs de script. Désolé d'être inexact.
Roman Kuzmin
1
Merci pour la clarification, @ RomanKuzmin - maintenant je vois la valeur de votre suggestion.
Michael Sorens,
12
Cela forcera une évaluation avide des options gauche et droite: c'est très différent d'une opération ternaire appropriée.
user2864740
24

Par ce billet de blog PowerShell , vous pouvez créer un alias pour définir un ?:opérateur:

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

Utilisez-le comme ceci:

$total = ($quantity * $price ) * (?:  {$quantity -le 10} {.9} {.75})
Edward Brey
la source
Il ne semble pas y avoir de raison pour laquelle «décideur» est un bloc de script. Si ?:jamais il est évalué, il faudra que les arguments soient évalués. Sans bloc de script, il aurait une utilisation similaire, par exemple. (?: ($quantity -le 10) {.9} {.75})
user2864740
C'est une fonctionnalité intéressante mais elle peut rendre votre script non standard, par exemple votre script ou des parties de celui-ci peuvent ne pas être portés à moins que la définition d'alias ne soit également portée avec. À ce stade, vous créez essentiellement votre propre sous-langue. Tout cela peut entraîner une augmentation des coûts de maintenance en raison d'une complexité accrue et d'une lisibilité réduite.
Vance McCorkle
1
@VanceMcCorkle Solid point. La concision personnalisée vaut son coût si elle est souvent utile. Mais si la situation ne se produit que rarement, je m'en tiendrai à une expression "si".
Edward Brey
21

Moi aussi, j'ai cherché une meilleure réponse, et bien que la solution dans le post d'Edward soit "ok", j'ai trouvé une solution beaucoup plus naturelle dans ce post de blog

Court et doux:

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

Ce qui facilite les choses comme (plus d'exemples dans le blog):

$message == ($age > 50) ? "Old Man" :"Young Dude" 
Garrett Serack
la source
1
c'est assez génial. Je n'aime tout simplement pas utiliser =comme alias, car il ==est utilisé en C # et dans beaucoup d'autres langages pour la vérification de l'égalité. Avoir une certaine expérience en C # est assez courant chez les PowerShellers et cela rend le code résultant un peu déroutant. :serait peut-être un meilleur alias. L'utiliser en conjonction avec l'affectation de variables =:peut rappeler à quelqu'un l'affectation utilisée en Pascal, mais n'a pas d'équivalent immédiat (que je sache) en VB.NET ni en C #.
2017
Vous pouvez définir cela sur l'alias que vous souhaitez. : ou ~ou tout ce qui fait flotter votre bateau. : 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
Je sais, et je l'ai fait, je commentais simplement pourquoi j'utiliserais un autre alias.
2017
15

Essayez l' instruction switch de powershell comme alternative, en particulier pour l'affectation des variables - plusieurs lignes, mais lisible.

Exemple,

$WinVer = switch ( Test-Path $Env:windir\SysWOW64 ) {
  $true    { "64-bit" }
  $false   { "32-bit" }
}
"This version of Windows is $WinVer"
nudl
la source
1
Cela peut être légèrement plus détaillé, mais il y a des avantages significatifs par rapport à la plupart des autres réponses. En particulier, il n'y a pas de dépendances sur le code externe, ni sur les fonctions copiées / collées dans un script ou un module.
bshacklett
9

Puisqu'un opérateur ternaire est généralement utilisé lors de l'attribution d'une valeur, il doit renvoyer une valeur. Voici comment cela peut fonctionner:

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

Stupide, mais fonctionnant. Cette construction peut également être utilisée pour transformer rapidement un int en une autre valeur, ajoutez simplement des éléments de tableau et spécifiez une expression qui renvoie des valeurs non négatives basées sur 0.

Vesper
la source
6
Cela forcera une évaluation avide des options gauche et droite: c'est très différent d'une opération ternaire appropriée.
user2864740
6

Comme je l'ai déjà utilisé plusieurs fois et que je ne l'ai pas vu ici, je vais ajouter ma pièce:

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

le plus laid de tous!

un peu la source

sodawillow
la source
4
Cela forcera une évaluation avide des options gauche et droite: c'est très différent d'une opération ternaire appropriée.
user2864740
@ user2864740 c'est parce qu'il n'y a pas d'opération ternaire correcte dans PS. Il s'agit d'une variante synthétique.
Viggo Lundén
2
@ ViggoLundén L'absence d'un opérateur ternaire approprié ne réduit pas l'exactitude du commentaire. Cette "variante synthétique" a un comportement différent .
user2864740
Vous avez raison et après avoir relu votre commentaire, je comprends comment cela peut faire une réelle différence. Merci.
sodawillow
5

J'ai récemment amélioré (ouvrez PullRequest) les opérateurs conditionnels ternaires et coalescents nuls dans les libs PoweShell 'Pscx'. Cherchez
ma solution.


Ma branche de sujet github: UtilityModule_Invoke-Operators

Les fonctions:

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"

Usage

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

<variable_expression> |?? <alternate_expression>

En tant qu'expression, vous pouvez passer:
$ null, un littéral, une variable, une expression 'externe' ($ b -eq 4) ou un scriptblock {$ b -eq 4}

Si une variable dans l'expression variable est $ null ou n'existe pas , l'expression alternative est évaluée en sortie.

et je fais
la source
4

PowerShell ne dispose pas actuellement d'un If intégré natif (ou If ternaire ), mais vous pouvez envisager d'utiliser l'applet de commande personnalisée:

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

le fer
la source
La publication liée montre comment créer une IIffonction. Cependant, il n'existe pas de IIffonction / applet de commande PowerShell standard .
user2864740
1
@ user2864740, alors quoi? cela exclut-il la réponse en tant que réponse utilisable possible à la question: " quelle serait la meilleure façon (c'est-à-dire facile à lire et à entretenir) d'obtenir le même résultat?" Mais cela mis à part, le lien renvoie à la question IIf (postée le 5 septembre 2014 ) qui est très similaire à celle-ci (en double?). Les autres réponses de la question liée peuvent également être considérées comme une valeur ajoutée (et là où elles ne le sont pas, c'est principalement parce qu'il s'agit de doublons).
iRon
Un peu d'explication va un long chemin, et c'est pourquoi les liens nus sont découragés, tout comme la fermeture pour les doublons s'il n'y a pas d'informations supplémentaires supplémentaires. Considérez qu'une très petite explication augmenterait considérablement la qualité d'une telle réponse de lien nu : "Powershell ne prend pas en charge un 'ternaire' direct (^ mais voir d'autres réponses). Cependant, il est possible d'écrire une fonction telle que (^ en utilisant cette méthode, ou voir d'autres réponses) .. ", mais ce n'est pas mon travail d'ajouter. Les réponses peuvent être modifiées / mises à jour / etc., le cas échéant.
user2864740
@ user2864740, merci pour vos commentaires, j'ai ajusté la réponse en conséquence.
iRon
1

Voici une autre approche de fonction personnalisée:

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'

Exemple

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

Différences expliquées

  • Pourquoi s'agit-il de 3 points d'interrogation au lieu d'un?
    • Le ?personnage est déjà un alias pour Where-Object.
    • ?? est utilisé dans d'autres langues comme un opérateur de coalescence nul, et je voulais éviter toute confusion.
  • Pourquoi avons-nous besoin du tuyau avant la commande?
    • Puisque j'utilise le pipeline pour évaluer cela, nous avons toujours besoin de ce caractère pour canaliser la condition dans notre fonction
  • Que se passe-t-il si je passe dans un tableau?
    • Nous obtenons un résultat pour chaque valeur; ie -2..2 |??? 'match' : 'nomatch'donne: match, match, nomatch, match, match(c'est-à-dire que tout entier non nul est évalué à true; tandis que zéro est évalué à false).
    • Si vous ne le souhaitez pas, convertissez le tableau en booléen; ([bool](-2..2)) |??? 'match' : 'nomatch'(ou tout simplement: [bool](-2..2) |??? 'match' : 'nomatch')
JohnLBevan
la source
1

Si vous cherchez simplement un moyen syntaxiquement simple d'affecter / renvoyer une chaîne ou un numérique basé sur une condition booléenne, vous pouvez utiliser l'opérateur de multiplication comme ceci:

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

Si le résultat ne vous intéresse que lorsque quelque chose est vrai, vous pouvez simplement omettre complètement la fausse partie (ou vice versa), par exemple un simple système de notation:

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

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

Notez que la valeur booléenne ne doit pas être le terme principal dans la multiplication, c'est-à-dire que $ condition * "true" etc. ne fonctionnera pas.

nimbell
la source
1

À partir de PowerShell version 7, l'opérateur ternaire est intégré à PowerShell.

1 -gt 2 ? "Yes" : "No"
Trevor Sullivan
la source