Ternärer Operator in PowerShell

214

Soweit ich weiß, scheint PowerShell keinen integrierten Ausdruck für den sogenannten ternären Operator zu haben .

In der C-Sprache, die den ternären Operator unterstützt, könnte ich beispielsweise Folgendes schreiben:

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

Wenn dies in PowerShell nicht wirklich vorhanden ist, wie kann das Ergebnis am besten erzielt werden (dh einfach zu lesen und zu warten)?

mguassa
quelle
5
Schauen Sie sich github.com/nightroman/PowerShellTraps/tree/master/Basic/… an . Wenn Sie danach suchen, kann ich Ihnen eine Antwort geben.
Roman Kuzmin
2
Es ist ein bedingter Operator oder ein ternärer Operator, wenn . Es ist nicht "der ternäre Operator", da dies nur einen Operator (einen beliebigen Operator) bedeutet, der drei Argumente akzeptiert.
Damien_The_Unbeliever
7
@Damien_The_Unbeliever Das ist technisch gesehen richtig, wird aber oft als ternärer Operator bezeichnet. "Da dieser Operator häufig der einzige existierende ternäre Operator in der Sprache ist, wird er manchmal einfach als" ternärer Operator "bezeichnet. In einigen Sprachen wird dieser Operator als" bedingter Operator "bezeichnet. Ternäre Operation
mguassa
Visual Basic hat keinen echten ternären Operator, betrachtet jedoch IF und IFF als funktional äquivalent.
Matt
@Matt Das ist falsch. Die IIF Funktion wertet immer beide Operanden aus. Die IfAnweisung wird nicht - siehe stackoverflow.com/questions/1220411/... (neuere Versionen hinzugefügt VB.NET einen ternären Ausdruck: If (x, y, z))
user2864740

Antworten:

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

Alles andere ist zufällige Komplexität und daher zu vermeiden.

Um es in oder als Ausdruck zu verwenden, nicht nur als Aufgabe, wickeln Sie es ein $(), also:

write-host  $(If ($condition) {"true"} Else {"false"}) 
fbehrens
quelle
3
Das funktioniert auf der rechten Seite eines Gleichen, aber nicht ganz so, wie Sie es von einem ternären Operator erwarten würden - diese schlagen fehl: "a" + If ($ condition) {"true"} Else {"false"} und "a" + (If ($ condition) {"true"} Else {"false"}) Dies funktioniert (ich bin mir noch nicht sicher warum): "a" + $ (If ($ condition) {"true"} Else {"false "})
Lamarth
12
@Lamarth - Das funktioniert, weil der $()Wrapper die Auswertung der Anweisung als Ausdruck erzwingt und somit entweder den wahren Wert des falschen Werts zurückgibt , ähnlich wie es von einem ternären Operator erwartet wird. Dies ist die nächstgelegene Option in PowerShell AFAIK.
KeithS
4
Im Gegensatz zu einigen anderen "Nicht-Funktions" -Antworten wird dadurch auch sichergestellt, dass nur ein Zweig ausgeführt wird.
user2864740
63

Das nächste PowerShell-Konstrukt, das ich entwickeln konnte, um es zu emulieren, ist:

@({'condition is false'},{'condition is true'})[$condition]
mjolinor
quelle
15
({true}, {false})[!$condition]ist (vielleicht) etwas besser: a) traditionelle Reihenfolge von wahren und falschen Teilen; b) $conditionmuss nicht nur 0 oder 1 oder $ false, $ true sein. Der Bediener !konvertiert es nach Bedarf. Dh $conditionkann beispielsweise 42 sein :! 42 ~ $ false ~ 0 ~ erster Ausdruck.
Roman Kuzmin
1
Nicht ganz identisch, @RomanKuzmin. Das Beispiel von mjolinor gibt eine Zeichenfolge zurück. Dieselben Zeichenfolgenwerte aus seinem Beispiel, die in Ihren Ausdruck eingefügt wurden, geben jedoch einen Skriptblock zurück. :-(
Michael Sorens
5
Mit {true}und {false}ich meine <true expression>und <false expression>nicht Skriptblöcke. Entschuldigung für die Ungenauigkeit.
Roman Kuzmin
1
Vielen Dank für die Klarstellung, @ RomanKuzmin - jetzt sehe ich den Wert in Ihrem Vorschlag.
Michael Sorens
12
Dies wird eine eifrige Bewertung der linken und rechten Optionen erzwingen: Dies unterscheidet sich stark von einer ordnungsgemäßen ternären Operation.
user2864740
24

In diesem PowerShell-Blogbeitrag können Sie einen Alias ​​zum Definieren eines ?:Operators erstellen :

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

Verwenden Sie es so:

$total = ($quantity * $price ) * (?:  {$quantity -le 10} {.9} {.75})
Edward Brey
quelle
Es scheint keinen Grund zu geben, warum 'decider' ein Skriptblock ist. Wenn ?:jemals ausgewertet wird, müssen die Argumente ausgewertet werden. Ohne einen Skriptblock hätte es eine ähnliche Verwendung, z. (?: ($quantity -le 10) {.9} {.75})
user2864740
Das ist eine coole Funktion, aber sie kann Ihr Skript als nicht standardisiert rendern, z. B. Ihr Skript oder Teile davon werden möglicherweise nicht portiert, es sei denn, die Alias-Definition wird ebenfalls damit portiert. Sie erstellen an dieser Stelle im Grunde genommen Ihre eigene Subsprache. All dies kann aufgrund der erhöhten Komplexität und der verringerten Lesbarkeit zu erhöhten Wartungskosten führen.
Vance McCorkle
1
@VanceMcCorkle Solid Point. Benutzerdefinierte Knappheit ist ihre Kosten wert, wenn sie häufig nützlich ist. Aber wenn die Situation nur selten auftritt, würde ich mich an einen "Wenn" -Ausdruck halten.
Edward Brey
21

Auch ich suchte nach einer besseren Antwort und während die Lösung in Edwards Beitrag "ok" ist, habe ich in diesem Blog-Beitrag eine weitaus natürlichere Lösung gefunden

Kurz und bündig:

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

Das macht es einfach, Dinge zu tun wie (weitere Beispiele im Blog-Beitrag):

$message == ($age > 50) ? "Old Man" :"Young Dude" 
Garrett Serack
quelle
1
das ist ziemlich genial. Ich benutze es einfach nicht gerne =als Alias, weil ==es in C # und vielen anderen Sprachen zur Gleichheitsprüfung verwendet wird. Einige Erfahrungen in C # sind unter Powershellern ziemlich häufig und das macht den resultierenden Code etwas verwirrend. :wäre möglicherweise ein besserer Alias. Die Verwendung in Verbindung mit der Variablenzuweisung erinnert =:möglicherweise jemanden an die in Pascal verwendete Zuweisung, hat jedoch weder in VB.NET noch in C # eine unmittelbare Entsprechung (die mir bekannt ist).
nohwnd
Sie können dies auf einen beliebigen Alias ​​einstellen. : oder ~oder was auch immer Ihr Boot schwimmt. : 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
Ich weiß, und ich habe es getan, ich habe nur kommentiert, warum ich einen anderen Alias ​​verwenden würde.
nohwnd
15

Versuchen Sie alternativ die switch- Anweisung von Powershell , insbesondere für die Variablenzuweisung - mehrere Zeilen, aber lesbar.

Beispiel,

$WinVer = switch ( Test-Path $Env:windir\SysWOW64 ) {
  $true    { "64-bit" }
  $false   { "32-bit" }
}
"This version of Windows is $WinVer"
nudl
quelle
1
Dies mag etwas ausführlicher sein, aber es gibt erhebliche Vorteile gegenüber vielen anderen Antworten. Insbesondere gibt es keine Abhängigkeiten von externem Code oder von Funktionen, die in ein Skript oder Modul kopiert / eingefügt werden.
bshacklett
9

Da beim Zuweisen von Werten normalerweise ein ternärer Operator verwendet wird, sollte er einen Wert zurückgeben. So kann es funktionieren:

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

Dumm, aber arbeitend. Diese Konstruktion kann auch verwendet werden, um ein int schnell in einen anderen Wert umzuwandeln. Fügen Sie einfach Array-Elemente hinzu und geben Sie einen Ausdruck an, der 0-basierte nicht negative Werte zurückgibt.

Vesper
quelle
6
Dies wird eine eifrige Bewertung der linken und rechten Optionen erzwingen: Dies unterscheidet sich stark von einer ordnungsgemäßen ternären Operation.
user2864740
6

Da ich das schon oft benutzt habe und es hier nicht aufgelistet habe, werde ich mein Stück hinzufügen:

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

hässlichste von allen!

irgendwie Quelle

Sodawillow
quelle
4
Dies wird eine eifrige Bewertung der linken und rechten Optionen erzwingen: Dies unterscheidet sich stark von einer ordnungsgemäßen ternären Operation.
user2864740
@ user2864740 Das liegt daran, dass es in PS keine ordnungsgemäße ternäre Operation gibt. Dies ist eine synthetische Variante.
Viggo Lundén
2
@ ViggoLundén Das Fehlen eines geeigneten ternären Operators verringert nicht die Richtigkeit des Kommentars. Diese "synthetische Variante" hat ein anderes Verhalten .
user2864740
Sie haben Recht, und nachdem ich Ihren Kommentar noch einmal gelesen habe, verstehe ich, wie es einen echten Unterschied machen kann. Vielen Dank.
Sodawillow
5

Ich habe kürzlich die ternären Bedingungs- und Null-Koaleszenz-Operatoren in der PoweShell-Bibliothek 'Pscx' verbessert (Open PullRequest). Bitte
suchen Sie nach meiner Lösung.


Mein Github-Themenzweig: UtilityModule_Invoke-Operators

Funktionen:

Invoke-Ternary
Invoke-TernaryAsPipe
Invoke-NullCoalescing
NullCoalescingAsPipe

Aliase

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"

Verwendung

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

<variable_expression> |?? <alternate_expression>

Als Ausdruck können Sie
Folgendes übergeben: $ null, ein Literal, eine Variable, einen 'externen' Ausdruck ($ b -eq 4) oder einen Skriptblock {$ b -eq 4}

Wenn eine Variable im Variablenausdruck $ null ist oder nicht vorhanden ist wird der alternative Ausdruck als Ausgabe ausgewertet.

und ich mache
quelle
4

PowerShell verfügt derzeit nicht über ein natives Inline-If (oder ein ternäres If ). Sie können jedoch auch das benutzerdefinierte Cmdlet verwenden:

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

Eisen
quelle
Der verlinkte Beitrag zeigt, wie man eine IIfFunktion erstellt. Es gibt jedoch keine Standard-PowerShell- IIfFunktion / Cmdlet.
user2864740
1
@ user2864740, na und? schließt dies die Antwort als mögliche verwendbare Antwort auf die Frage aus: " Was wäre der beste Weg (dh leicht zu lesen und zu warten), um das gleiche Ergebnis zu erzielen?" Abgesehen davon verweist der Link auf die IIf- Frage (veröffentlicht am 5. September 14), die dieser sehr ähnlich ist (Duplikat?). Der Rest der Antworten in der verknüpften Frage könnte auch als Mehrwert angesehen werden (und wo dies nicht der Fall ist, liegt dies hauptsächlich daran, dass es sich um Duplikate handelt).
iRon
Ein wenig Erklärung reicht weit, und aus diesem Grund wird von bloßen Links abgeraten, da nicht für Duplikate geschlossen wird, wenn keine zusätzlichen Informationen hinzugefügt werden. Bedenken Sie, dass eine sehr kleine Erklärung die Qualität einer solchen Bare-Link- Antwort erheblich verbessern würde : "Powershell unterstützt kein direktes 'ternäres' (^ aber siehe andere Antworten). Es ist jedoch möglich, eine Funktion wie (^ using zu schreiben diese Methode, oder sehen Sie andere Antworten) .. ", aber das ist nicht meine Aufgabe hinzuzufügen. Die Antworten können gegebenenfalls geändert / aktualisiert / usw. werden.
user2864740
@ user2864740, danke für dein Feedback, ich habe die Antwort entsprechend angepasst.
iRon
1

Hier ist ein alternativer Ansatz für benutzerdefinierte Funktionen:

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'

Beispiel

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

Unterschiede erklärt

  • Warum sind es 3 Fragezeichen statt 1?
    • Der ?Charakter ist bereits ein Alias ​​für Where-Object.
    • ?? wird in anderen Sprachen als Null-Koaleszenz-Operator verwendet, und ich wollte Verwirrung vermeiden.
  • Warum brauchen wir die Pipe vor dem Befehl?
    • Da ich die Pipeline verwende, um dies zu bewerten, benötigen wir dieses Zeichen immer noch, um die Bedingung in unsere Funktion zu leiten
  • Was passiert, wenn ich ein Array übergebe?
    • Wir erhalten für jeden Wert ein Ergebnis; dh -2..2 |??? 'match' : 'nomatch'gibt: match, match, nomatch, match, match(dh da jedes Nicht-Null-Int zu ergibt true; während Null zu ausgewertet wird false).
    • Wenn Sie das nicht möchten, konvertieren Sie das Array in einen Bool. ([bool](-2..2)) |??? 'match' : 'nomatch'(oder einfach: [bool](-2..2) |??? 'match' : 'nomatch')
JohnLBevan
quelle
1

Wenn Sie nur nach einer syntaktisch einfachen Möglichkeit suchen, eine Zeichenfolge oder eine Zahl basierend auf einer booleschen Bedingung zuzuweisen / zurückzugeben, können Sie den Multiplikationsoperator folgendermaßen verwenden:

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

Wenn Sie nur dann an dem Ergebnis interessiert sind, wenn etwas wahr ist, können Sie den falschen Teil einfach ganz weglassen (oder umgekehrt), z. B. ein einfaches Bewertungssystem:

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

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

Beachten Sie, dass der boolesche Wert nicht der führende Begriff in der Multiplikation sein sollte, dh $ condition * "true" usw. funktioniert nicht.

Nimbell
quelle
1

Ab PowerShell Version 7 ist der ternäre Operator in PowerShell integriert.

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