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ęte są odsył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.