Łączenie wartości zerowej w programie PowerShell

115

Czy w programie PowerShell istnieje operator koalescencji zerowej?

Chciałbym móc wykonać te polecenia C # w PowerShell:

var s = myval ?? "new value";
var x = myval == null ? "" : otherval;
Micheasza
źródło

Odpowiedzi:

133

Powershell 7+

Powershell 7 wprowadza natywne łączenie wartości null, przypisanie warunkowe wartości null i operatory trójskładnikowe w programie PowerShell.

Null Coalescing

$null ?? 100    # Result is 100

"Evaluated" ?? (Expensive-Operation "Not Evaluated")    # Right side here is not evaluated

Przypisanie warunkowe o wartości Null

$x = $null
$x ??= 100    # $x is now 100
$x ??= 200    # $x remains 100

Operator trójskładnikowy

$true  ? "this value returned" : "this expression not evaluated"
$false ? "this expression not evaluated" : "this value returned"

Poprzednie wersje:

Nie ma potrzeby korzystania z rozszerzeń społeczności programu Powershell, można użyć standardowych instrukcji Powershell if jako wyrażenia:

variable = if (condition) { expr1 } else { expr2 }

A więc do zamienników dla twojego pierwszego wyrażenia w C #:

var s = myval ?? "new value";

staje się jedną z następujących (w zależności od preferencji):

$s = if ($myval -eq $null) { "new value" } else { $myval }
$s = if ($myval -ne $null) { $myval } else { "new value" }

lub w zależności od tego, co może zawierać $ myval, możesz użyć:

$s = if ($myval) { $myval } else { "new value" }

a drugie wyrażenie C # odwzorowuje w podobny sposób:

var x = myval == null ? "" : otherval;

staje się

$x = if ($myval -eq $null) { "" } else { $otherval }

Aby być uczciwym, nie są one zbyt zgryźliwe i nie są tak wygodne w użyciu, jak formularze C #.

Możesz również rozważyć zawinięcie go w bardzo prostą funkcję, aby uczynić rzeczy bardziej czytelnymi:

function Coalesce($a, $b) { if ($a -ne $null) { $a } else { $b } }

$s = Coalesce $myval "new value"

lub prawdopodobnie jako IfNull:

function IfNull($a, $b, $c) { if ($a -eq $null) { $b } else { $c } }

$s = IfNull $myval "new value" $myval
$x = IfNull $myval "" $otherval

Jak widać, bardzo prosta funkcja zapewnia dużą swobodę składni.

AKTUALIZACJA: Jedną dodatkową opcją do rozważenia w miksie jest bardziej ogólna funkcja IsTrue:

function IfTrue($a, $b, $c) { if ($a) { $b } else { $c } }

$x = IfTrue ($myval -eq $null) "" $otherval

Następnie połącz to ze zdolnością Powershell do deklarowania aliasów, które wyglądają trochę jak operatory, a otrzymasz:

New-Alias "??" Coalesce

$s = ?? $myval "new value"

New-Alias "?:" IfTrue

$ans = ?: ($q -eq "meaning of life") 42 $otherval

Oczywiście nie wszystkim przypadnie do gustu, ale może być tym, czego szukasz.

Jak zauważa Thomas, jeszcze jedna subtelna różnica między wersją C # a wersją powyższą polega na tym, że C # dokonuje zwarcia argumentów, ale wersje programu Powershell obejmujące funkcje / aliasy zawsze będą oceniać wszystkie argumenty. Jeśli jest to problem, użyj ifformularza wyrażenia.

StephenD
źródło
6
Jedynym prawdziwym odpowiednikiem operatora koalescencji jest użycie instrukcji if; problem polega na tym, że każde inne podejście ocenia wszystkie operandy zamiast tworzenia zwarcia. „?? $ myval SomeReallyExpenisveFunction ()” wywoła funkcję, nawet jeśli $ myval nie ma wartości null. Przypuszczam, że można by opóźnić ocenę za pomocą bloków skryptów, ale należy pamiętać, że bloki skryptów NIE są zamknięciami i rzeczy zaczynają się robić niezgrabne.
Thomas S. Trias
Nie działa w trybie ścisłym - wyrzuca The variable '$myval' cannot be retrieved because it has not been set..
BrainSlugs83
1
@ BrainSlugs83 Błąd, który widzisz w trybie ścisłym, nie jest związany z przedstawionymi opcjami łączenia zerowego. To tylko standard, Powershell sprawdza, czy zmienna jest zdefiniowana jako pierwsza. Jeśli ustawiłeś $myval = $nullprzed wykonaniem testu, błąd powinien zniknąć.
StephenD
1
Ostrzeżenie, dziwactwo programu PowerShell, wartość null należy zawsze porównywać (niezręcznie), umieszczając wartość null jako pierwszą, tj. $null -ne $a Nie można teraz znaleźć przyzwoitego odniesienia, ale jest to długoletnia praktyka.
dudeNumber4
90

PowerShell 7 i nowsze

PowerShell 7 wprowadza wiele nowych funkcji i przeprowadza migrację z .NET Framework do .NET Core. Od połowy 2020 r. Nie zastąpił on całkowicie starszych wersji programu PowerShell ze względu na zależność od platformy .NET Core, ale firma Microsoft wskazała, że ​​zamierza ostatecznie zastąpić starszą rodzinę Framework rodziny Core. Zanim to przeczytasz, kompatybilna wersja PowerShell może być już wstępnie zainstalowana w twoim systemie; jeśli nie, zobacz https://github.com/powershell/powershell .

Zgodnie z dokumentacją następujące operatory są obsługiwane natychmiast po wyjęciu z pudełka w programie PowerShell 7.0:

  1. Koalescencja zerowa :??
  2. Przypisanie z łączeniem zerowym :??=
  3. Trójskładnikowy :... ? ... : ...

Działają tak, jak można by oczekiwać w przypadku koalescencji zerowej:

$x = $a ?? $b ?? $c ?? 'default value'
$y ??= 'default value'

Ponieważ wprowadzono operator trójskładnikowy, możliwe są teraz następujące czynności, chociaż jest to niepotrzebne, biorąc pod uwagę dodanie zerowego operatora koalescencji:

$x = $a -eq $null ? $b : $a

Od wersji 7.0 dostępne są również następujące elementy, jeśli PSNullConditionalOperatorsfunkcja opcjonalna jest włączona , jak wyjaśniono w dokumentach ( 1 , 2 ):

  1. Zerowy warunkowy dostęp członków dla członków: ?.
  2. Dostęp do elementów składowych o wartości zerowej dla tablic i wsp .: ?[]

Mają kilka zastrzeżeń:

  1. Ponieważ są one eksperymentalne, mogą ulec zmianie. W chwili, gdy to przeczytasz, mogą nie być już uważane za eksperymentalne, a lista zastrzeżeń mogła ulec zmianie.
  2. Zmienne należy ująć w, ${}jeśli następuje po nich jeden z operatorów eksperymentalnych, ponieważ znaki zapytania są dozwolone w nazwach zmiennych. Nie jest jasne, czy tak się stanie, jeśli / kiedy funkcje przejdą ze statusu eksperymentalnego (patrz numer 11379 ). Na przykład ${x}?.Test()używa operatora new, ale $x?.Test()działa Test()na zmiennej o nazwie $x?.
  3. Nie ma ?(operatora, jak można by się spodziewać, jeśli pochodzisz z języka TypeScript. Poniższe nie będą działać:$x.Test?()

PowerShell 6 i starsze

Wersje programu PowerShell wcześniejsze niż 7 mają rzeczywisty operator koalescencji wartości null lub przynajmniej operator, który jest zdolny do takiego zachowania. Tym operatorem jest -ne:

# Format:
# ($a, $b, $c -ne $null)[0]
($null, 'alpha', 1 -ne $null)[0]

# Output:
alpha

Jest nieco bardziej wszechstronny niż operator łączący wartość null, ponieważ tworzy tablicę wszystkich elementów niezerowych:

$items = $null, 'alpha', 5, 0, '', @(), $null, $true, $false
$instances = $items -ne $null
[string]::Join(', ', ($instances | ForEach-Object -Process { $_.GetType() }))

# Result:
System.String, System.Int32, System.Int32, System.String, System.Object[],
System.Boolean, System.Boolean

-eq działa podobnie, co jest przydatne do liczenia pustych wpisów:

($null, 'a', $null -eq $null).Length

# Result:
2

Ale tak czy inaczej, oto typowy przypadek odzwierciedlenia ??operatora C # :

'Filename: {0}' -f ($filename, 'Unknown' -ne $null)[0] | Write-Output

Wyjaśnienie

To wyjaśnienie jest oparte na sugestii edycji od anonimowego użytkownika. Dzięki, kimkolwiek jesteś!

Na podstawie kolejności operacji działa to w następującej kolejności:

  1. ,Operatora tworzy tablicą wartości, które mają być testowane.
  2. Te -nefiltry operator żadnych elementów z tablicy, które pasują do podanych wartości - w tym przypadku, null. Wynikiem jest tablica wartości niezerowych w tej samej kolejności, co tablica utworzona w kroku 1.
  3. [0] służy do zaznaczenia pierwszego elementu przefiltrowanej tablicy.

Upraszczając, że:

  1. Utwórz tablicę możliwych wartości w preferowanej kolejności
  2. Wyklucz wszystkie wartości null z tablicy
  3. Weź pierwszy element z wynikowej tablicy

Ostrzeżenia

W przeciwieństwie do operatora łączenia wartości null w języku C #, każde możliwe wyrażenie zostanie ocenione, ponieważ pierwszym krokiem jest utworzenie tablicy.

Zenexer
źródło
Skończyło się na tym , że użyłem zmodyfikowanej wersji twojej odpowiedzi w mojej , ponieważ musiałem pozwolić, aby wszystkie wystąpienia w połączeniu były zerowe, bez rzucania wyjątku. Jednak bardzo fajny sposób na zrobienie tego, wiele mnie nauczył. :)
Johny Skovdal
Ta metoda zapobiega zwarciom. Zdefiniuj function DidItRun($a) { Write-Host "It ran with $a"; return $a }i biegnij, ((DidItRun $null), (DidItRun 'alpha'), 1 -ne $null)[0]aby to zobaczyć.
jpmc26
@ jpmc26 Tak, to jest zamierzone.
Zenexer
Jakakolwiek próba zdefiniowania funkcji koalescencji również prawdopodobnie wyeliminuje zwarcia, prawda?
Chris F Carroll
8
Ahhhh Pierwszeństwo operatorów PowerShell mnie zabija! Zawsze zapominam, że operator przecinka ,ma tak wysoki priorytet. Dla każdego, kto nie wie, jak to działa, ($null, 'alpha', 1 -ne $null)[0]jest tak samo jak (($null, 'alpha', 1) -ne $null)[0]. W rzeczywistości jedynymi dwoma operatorami „myślnikowymi” z wyższą precyzją są -spliti -join(i jednoargumentowy myślnik? Tj. -4Lub -'56').
Pluton
15

To tylko połowa odpowiedzi na pierwszą połowę pytania, więc jedna czwarta odpowiedzi, jeśli wolisz, ale istnieje znacznie prostsza alternatywa dla operatora koalescencji zerowej, pod warunkiem, że domyślna wartość, której chcesz użyć, jest w rzeczywistości wartością domyślną dla typu :

string s = myval ?? "";

Można zapisać w Powershell jako:

([string]myval)

Lub

int d = myval ?? 0;

przekłada się na Powershell:

([int]myval)

Pierwszy z nich okazał się przydatny podczas przetwarzania elementu xml, który może nie istnieć i który, gdyby istniał, mógłby zawierać niepożądane białe znaki wokół niego:

$name = ([string]$row.td[0]).Trim()

Przelewanie na strunę zabezpiecza element przed zerowaniem i zapobiega ryzyku Trim()awarii.

Duncan
źródło
([string]$null)-> ""wygląda na "użyteczny, ale niefortunny efekt uboczny" :(
user2864740
11

$null, $null, 3 | Select -First 1

zwroty

3

Chris F Carroll
źródło
1
Ale komentarz @ thomas-s-trias o zwarciu jest nadal aktualny.
Chris F Carroll
9

Jeśli zainstalujesz moduł rozszerzeń społeczności Powershell , możesz użyć:

?? jest aliasem dla Invoke-NullCoalescing.

$s = ?? {$myval}  {"New Value"}

?: to alias dla Invoke-Ternary.

$x = ?: {$myval -eq $null} {""} {$otherval}
EBGreen
źródło
Właściwie to nie są polecenia programu PowerShell. Dostałeś je razem z pscx:?: -> Invoke-
Ternary
... brakowało aktualnego kodu i wyniku ..;) Get-Command -Module pscx -CommandType alias | gdzie {$ _. Name -match '\ ?.' } | foreach {"{0}: {1}" -f $ _. Nazwa, $ _. Definicja}?:: Invoke-Ternary ?? : Invoke-NullCoalescing
BartekB
Ups ... masz całkowitą rację. Często zapominam, że nawet ładuję ten moduł.
EBGreen
5

Wreszcie PowerShell 7 ma operatory przypisania Null Coalescing !

PS > $null ?? "a"
a
PS > "x" ?? "y"
x
PS > $x = $null
PS > $x ??= "abc"
PS > $x
abc
PS > $x ??= "xyz"
PS > $x
abc
hiroki
źródło
3
@($null,$null,"val1","val2",5) | select -First 1
staticrysis
źródło
2
function coalesce {
   Param ([string[]]$list)
   #$default = $list[-1]
   $coalesced = ($list -ne $null)
   $coalesced[0]
 }
 function coalesce_empty { #COALESCE for empty_strings

   Param ([string[]]$list)
   #$default = $list[-1]
   $coalesced = (($list -ne $null) -ne '')[0]
   $coalesced[0]
 }
Shawn
źródło
2

Często okazuje się, że podczas korzystania z koalescencji muszę również traktować pusty ciąg jako zerowy. Skończyło się na tym, że napisałem funkcję do tego, która używa rozwiązania Zenexera do łączenia w prostą koalescencję zerową, a następnie użyłem funkcji Keitha Hilla do sprawdzania wartości null lub pustej i dodałem ją jako flagę, aby moja funkcja mogła wykonywać oba te zadania.

Jedną z zalet tej funkcji jest to, że obsługuje ona również posiadanie wszystkich elementów null (lub pustych), bez zgłaszania wyjątku. Może być również używany do wielu dowolnych zmiennych wejściowych, dzięki temu, jak PowerShell obsługuje dane wejściowe tablic.

function Coalesce([string[]] $StringsToLookThrough, [switch]$EmptyStringAsNull) {
  if ($EmptyStringAsNull.IsPresent) {
    return ($StringsToLookThrough | Where-Object { $_ } | Select-Object -first 1)
  } else {
    return (($StringsToLookThrough -ne $null) | Select-Object -first 1)
  }  
}

Daje to następujące wyniki testów:

Null coallesce tests:
1 (w/o flag)  - empty/null/'end'                 : 
1 (with flag) - empty/null/'end'                 : end
2 (w/o flag)  - empty/null                       : 
2 (with flag) - empty/null                       : 
3 (w/o flag)  - empty/null/$false/'end'          : 
3 (with flag) - empty/null/$false/'end'          : False
4 (w/o flag)  - empty/null/"$false"/'end'        : 
4 (with flag) - empty/null/"$false"/'end'        : False
5 (w/o flag)  - empty/'false'/null/"$false"/'end': 
5 (with flag) - empty/'false'/null/"$false"/'end': false

Kod testowy:

Write-Host "Null coalesce tests:"
Write-Host "1 (w/o flag)  - empty/null/'end'                 :" (Coalesce '', $null, 'end')
Write-Host "1 (with flag) - empty/null/'end'                 :" (Coalesce '', $null, 'end' -EmptyStringAsNull)
Write-Host "2 (w/o flag)  - empty/null                       :" (Coalesce('', $null))
Write-Host "2 (with flag) - empty/null                       :" (Coalesce('', $null) -EmptyStringAsNull)
Write-Host "3 (w/o flag)  - empty/null/`$false/'end'          :" (Coalesce '', $null, $false, 'end')
Write-Host "3 (with flag) - empty/null/`$false/'end'          :" (Coalesce '', $null, $false, 'end' -EmptyStringAsNull)
Write-Host "4 (w/o flag)  - empty/null/`"`$false`"/'end'        :" (Coalesce '', $null, "$false", 'end')
Write-Host "4 (with flag) - empty/null/`"`$false`"/'end'        :" (Coalesce '', $null, "$false", 'end' -EmptyStringAsNull)
Write-Host "5 (w/o flag)  - empty/'false'/null/`"`$false`"/'end':" (Coalesce '', 'false', $null, "$false", 'end')
Write-Host "5 (with flag) - empty/'false'/null/`"`$false`"/'end':" (Coalesce '', 'false', $null, "$false", 'end' -EmptyStringAsNull)
Johny Skovdal
źródło
1

Najbliższy, jaki mogę uzyskać, to: $Val = $MyVal |?? "Default Value"

Zaimplementowałem zerowy operator koalescencyjny dla powyższego w następujący sposób:

function NullCoalesc {
    param (
        [Parameter(ValueFromPipeline=$true)]$Value,
        [Parameter(Position=0)]$Default
    )

    if ($Value) { $Value } else { $Default }
}

Set-Alias -Name "??" -Value NullCoalesc

Operator warunkowy trójskładnikowy można zaimplementować w podobny sposób.

function ConditionalTernary {
    param (
        [Parameter(ValueFromPipeline=$true)]$Value,
        [Parameter(Position=0)]$First,
        [Parameter(Position=1)]$Second
    )

    if ($Value) { $First } else { $Second }
}

Set-Alias -Name "?:" -Value ConditionalTernary

I używane jak: $Val = $MyVal |?: $MyVal "Default Value"

Elon Mallin
źródło