Operator trójskładnikowy w PowerShell


214

Z tego co wiem, PowerShell nie wydaje się mieć wbudowanego wyrażenia dla tak zwanego operatora trójskładnikowego .

Na przykład w języku C, który obsługuje operator potrójny, mógłbym napisać coś takiego:

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

Jeśli tak naprawdę nie istnieje w PowerShell, jaki byłby najlepszy sposób (tj. Łatwy do odczytania i utrzymania), aby osiągnąć ten sam wynik?


5
Spójrz na github.com/nightroman/PowerShellTraps/tree/master/Basic/… . Jeśli tego właśnie szukasz, mogę udzielić odpowiedzi.
Roman Kuzmin,

2
Jest to operator warunkowy lub trójskładnikowy, jeśli . To nie jest „trójskładnikowy operator”, ponieważ wszystko to oznacza operator ( dowolny operator), który przyjmuje trzy argumenty.
Damien_The_Unbeliever,

7
@Damien_The_Unbeliever To technicznie prawda, ale często nazywane jest operatorem trójskładnikowym. „Ponieważ operator ten jest często jedynym operatorem potrójnym w danym języku, czasami jest on po prostu nazywany„ operatorem potrójnym ”. W niektórych językach ten operator jest nazywany„ operatorem warunkowym ”. Operacja
potrójna

Visual Basic nie ma prawdziwego operatora trójskładnikowego, ale uważa, że ​​IF i IFF są funkcjonalnie równoważne.
Matt

@Matt To nieprawda. IIF Funkcja zawsze będzie oceniać zarówno argumenty. IfOświadczenie nie będzie - patrz stackoverflow.com/questions/1220411/... (nowsze wersje VB.NET dodał potrójny wyrażenie: If (x, y, z))
user2864740

Odpowiedzi:


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

Cała reszta to przypadkowa złożoność, której należy unikać.

Do użycia w lub jako wyrażenie, a nie tylko zadanie, zawiń je $(), a zatem:

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

3
Działa to po prawej stronie znaku równości, ale nie do końca tak, jak można się spodziewać po operatorze trójskładnikowym - te zawodzą: „a” + If ($ condition) {„true”} Else {„false”} i „a” + (If ($ condition) {„true”} Else {„false”}) To działa (nie jestem pewien, dlaczego): „a” + $ (If ($ condition) {„true”} Else {„false "})
Lamarth

12
@ Lamarth - Działa, ponieważ $()opakowanie wymusza ocenę instrukcji jako wyrażenia, zwracając w ten sposób albo prawdziwą wartość fałszywej wartości, podobnie jak oczekiwałby tego operator potrójny. To jest najbliższe AFAIK PowerShell.
KeithS,

4
W przeciwieństwie do niektórych innych odpowiedzi „niefunkcjonalnych”, zapewni to również wykonanie tylko jednej gałęzi.
user2864740,

63

Najbliższa konstrukcja programu PowerShell, jaką udało mi się wymyślić, naśladująca:

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

15
({true}, {false})[!$condition]jest nieco lepszy (być może): a) tradycyjny porządek części prawdziwych i fałszywych; b) $conditionnie musi być tylko 0 lub 1 lub $ false, $ true. Operator !konwertuje go w razie potrzeby. Czyli $conditionmoże być, powiedzmy, 42: 42 ~ $ false ~ 0 ~ pierwszy wyraz.
Roman Kuzmin,

1
Niezupełnie identyczny @RomanKuzmin. Przykład mjolinor zwraca ciąg znaków. Ale te same wartości ciągu z jego przykładu podłączonego do wyrażenia zwracają blok skryptu. :-(
Michael Sorens,

5
Przez {true}i {false}mam na myśli, <true expression>a <false expression>nie bloki skryptów. Przepraszamy za niedokładność.
Roman Kuzmin

1
Dzięki za wyjaśnienie, @ RomanKuzmin - teraz widzę wartość w twojej sugestii.
Michael Sorens,

12
Wymusi to chętną ocenę lewej i prawej opcji: znacznie różni się to od prawidłowej operacji trójskładnikowej.
user2864740


24

W tym poście na blogu PowerShell możesz utworzyć alias, aby zdefiniować ?:operatora:

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

Użyj tego w ten sposób:

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

Wydaje się, że nie ma powodu, aby „decydujący” był blokiem skryptowym. Jeśli ?:jest kiedykolwiek oceniane, będzie wymagało oceny argumentów. Bez bloku skryptu miałby podobne zastosowanie, np. (?: ($quantity -le 10) {.9} {.75})
user2864740,

To fajna funkcja, ale może sprawić, że twój skrypt będzie niestandardowy, np. Twój skrypt lub jego części mogą nie zostać przeniesione, chyba że definicja aliasu jest z nim również przeniesiona. W tym momencie zasadniczo tworzysz swój własny język podrzędny. Wszystko to może prowadzić do wzrostu kosztów konserwacji ze względu na większą złożoność i zmniejszoną czytelność.
Vance McCorkle

1
@VanceMcCorkle Punkt stały. Zwyczajna zwięzłość jest warta swojego kosztu, jeśli jest często przydatna. Ale jeśli sytuacja zdarza się rzadko, trzymałbym się wyrażenia „jeśli”.
Edward Brey

21

Ja również szukałem lepszej odpowiedzi i chociaż rozwiązanie w poście Edwarda jest „ok”, w tym wpisie na blogu znalazłem znacznie bardziej naturalne rozwiązanie

Krótkie i słodkie:

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

Co ułatwia robienie takich rzeczy (więcej przykładów w poście na blogu):

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

1
to jest całkiem niesamowite. Po prostu nie lubię używać =jako aliasu, ponieważ ==jest używany w C # i wielu innych językach do sprawdzania równości. Posiadanie doświadczenia w C # jest dość powszechne wśród PowerShellerów, co sprawia, że ​​powstały kod jest nieco mylący. :byłby prawdopodobnie lepszy alias. Używanie go w połączeniu z przypisywaniem zmiennych =:może przypominać komuś przypisanie używane w Pascalu, ale nie ma żadnego bezpośredniego odpowiednika (o którym wiem) w VB.NET ani w C #.
nohwnd

Możesz ustawić dowolny alias. : lub ~cokolwiek płynie łodzią. : 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

Wiem i wiedziałem, właśnie komentowałem, dlaczego mam użyć innego aliasu.
nohwnd

15

Wypróbuj instrukcję switch programu PowerShell jako alternatywę, szczególnie w przypadku przypisywania zmiennych - wiele wierszy, ale czytelnych.

Przykład,

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

1
Może to być nieco bardziej szczegółowe, ale istnieją znaczące korzyści w stosunku do wielu innych odpowiedzi. W szczególności nie ma zależności od zewnętrznego kodu, ani od kopiowania / wklejania funkcji do skryptu lub modułu.
bshacklett,

9

Ponieważ operator trójskładnikowy jest zwykle używany podczas przypisywania wartości, powinien zwrócić wartość. Oto sposób, w jaki może działać:

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

Głupie, ale działające. Tej konstrukcji można także użyć do szybkiego przekształcenia liczby int w inną wartość, wystarczy dodać elementy tablicy i określić wyrażenie zwracające wartości nieujemne oparte na 0.


6
Wymusi to chętną ocenę lewej i prawej opcji: znacznie różni się to od prawidłowej operacji trójskładnikowej.
user2864740

6

Ponieważ używałem tego już wiele razy i nie widziałem go tutaj, dodam swój kawałek:

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

najbrzydszy ze wszystkich!

rodzaj źródła


4
Wymusi to chętną ocenę lewej i prawej opcji: znacznie różni się to od prawidłowej operacji trójskładnikowej.
user2864740

@ user2864740 to dlatego, że w PS nie ma właściwej operacji trójskładnikowej. To jest wariant syntetyczny.
Viggo Lundén,

2
@ ViggoLundén Brak właściwego operatora trójskładnikowego nie zmniejsza poprawności komentarza. Ten „wariant syntetyczny” ma inne zachowanie .
user2864740 11.04.19

Masz rację i po ponownym przeczytaniu twojego komentarza rozumiem, jak może to naprawdę zmienić. Dzięki.
sodawillow

5

Niedawno ulepszyłem (otwórz PullRequest) trójskładnikowe operatory warunkowe i zerowo-koalescencyjne w PoweShell lib 'Pscx'
Pls szukają mojego rozwiązania.


Moja gałąź tematów github: UtilityModule_Invoke-Operators

Funkcje:

Invoke-Ternary
Invoke-TernaryAsPipe
Invoke-NullCoalescing
NullCoalescingAsPipe

Skróty

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"

Stosowanie

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

<variable_expression> |?? <alternate_expression>

Jako wyrażenie możesz podać:
$ null, literał, zmienna, wyrażenie „zewnętrzne” ($ b -eq 4) lub blok skryptu {$ b -eq 4}

Jeśli zmienna w wyrażeniu zmiennej jest $ null lub nie istnieje , alternatywne wyrażenie jest oceniane jako wynik.


4

Program PowerShell nie ma obecnie natywnego wbudowanego If (lub trójskładnikowego If ), ale można rozważyć użycie niestandardowego polecenia cmdlet:

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


Połączone pokazy postu tworzyć takie IIffunkcje. Jednak nie ma standardowej IIffunkcji / polecenia cmdlet programu PowerShell .
user2864740

1
@ user2864740, więc co? czy to wyklucza odpowiedź jako możliwą do wykorzystania odpowiedź na pytanie: „ jaki byłby najlepszy sposób (tj. łatwy do odczytania i utrzymania), aby osiągnąć ten sam wynik?” Ale poza tym link odnosi się do pytania IIf (opublikowane 5 września 14), które jest bardzo podobne do tego (duplikat?). Pozostałe odpowiedzi w połączonym pytaniu można również postrzegać jako wartość dodaną (a jeśli nie, to głównie dlatego, że są duplikatami).
iRon

Trochę wyjaśnienia ma długą drogę i dlatego odsłonięteodsyłacze odsłonięte , ponieważ nie zamyka się duplikatów, jeśli nie ma dodatkowych informacji. Weź pod uwagę, że bardzo małe wyjaśnienie znacznie poprawiłoby jakość takiej odsyłacza bez odpowiedzi: „Powershell nie obsługuje bezpośredniego„ trójki ”(^, ale zobacz inne odpowiedzi). Można jednak napisać funkcję taką jak (^ używając ta metoda lub zobacz inne odpowiedzi) .. ”, ale to nie jest moje zadanie do dodania. Odpowiedzi mogą być poprawione / zaktualizowane / itp., Stosownie do przypadku.
user2864740

@ user2864740, dzięki za opinie, odpowiednio dostosowałem odpowiedź.
iRon

1

Oto alternatywne podejście do funkcji niestandardowych:

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'

Przykład

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

Różnice wyjaśnione

  • Dlaczego to 3 znaki zapytania zamiast 1?
    • Ta ?postać jest już pseudonimemWhere-Object .
    • ?? jest używany w innych językach jako zerowy operator koalescencyjny i chciałem uniknąć zamieszania.
  • Dlaczego potrzebujemy rury przed poleceniem?
    • Ponieważ używam potoku do oceny tego, nadal potrzebujemy tego znaku, aby przesłać warunek do naszej funkcji
  • Co się stanie, jeśli przekażę w tablicy?
    • Otrzymujemy wynik dla każdej wartości; tzn. -2..2 |??? 'match' : 'nomatch'daje: match, match, nomatch, match, match(tj. ponieważ dowolna niezerowa liczba int ocenia na true; podczas gdy zero ocenia nafalse ).
    • Jeśli tego nie chcesz, przekonwertuj tablicę na bool; ([bool](-2..2)) |??? 'match' : 'nomatch'(lub po prostu: [bool](-2..2) |??? 'match' : 'nomatch')

1

Jeśli szukasz tylko prostego pod względem składniowym sposobu przypisywania / zwracania łańcucha lub wartości liczbowej na podstawie warunku logicznego, możesz użyć operatora mnożenia w następujący sposób:

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

Jeśli kiedykolwiek interesuje Cię wynik, gdy coś jest prawdą, możesz po prostu całkowicie pominąć fałszywą część (lub odwrotnie), np. Prosty system punktacji:

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

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

Zauważ, że wartość logiczna nie powinna być wiodącym terminem w mnożeniu, tzn. $ Warunek * „prawda” itp. Nie zadziała.


1

Począwszy od wersji 7 programu PowerShell, trójskładnikowy operator jest wbudowany w PowerShell.

1 -gt 2 ? "Yes" : "No"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.