Łą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;

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.


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.


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.


([string]$null)-> ""wygląda na "użyteczny, ale niefortunny efekt uboczny" :(
user2864740


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}

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



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]
 }

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)

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"

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.