Udostępnij za pomocą


Wszystko, co chciałeś wiedzieć o $null

Program PowerShell $null często wydaje się być prosty, ale ma wiele niuansów. Przyjrzyjmy się bliżej $null , aby wiedzieć, co się stanie, gdy nieoczekiwanie napotkasz $null wartość.

Uwaga

Oryginalna wersja tego artykułu pojawiła się na blogu prowadzonym przez @KevinMarquette. Zespół programu PowerShell dziękuje Kevinowi za udostępnienie tej zawartości nam. Zapoznaj się ze swoim blogiem na PowerShellExplained.com.

Co to jest wartość NULL?

Wartość NULL można traktować jako nieznaną lub pustą wartość. Zmienna ma wartość NULL do momentu przypisania do niej wartości lub obiektu. Może to być ważne, ponieważ istnieją pewne polecenia, które wymagają wartości i generują błędy, jeśli wartość ma wartość NULL.

$null programu PowerShell

$null to zmienna automatyczna w programie PowerShell używana do reprezentowania wartości NULL. Można przypisać ją do zmiennych, używać jej w porównaniach i używać jej jako posiadacza zastępczego wartości NULL w kolekcji.

Program PowerShell traktuje $null jako obiekt o wartości NULL. Różni się to od tego, czego można się spodziewać, jeśli pochodzisz z innego języka.

Przykłady $null

Za każdym razem, gdy próbujesz użyć zmiennej, która nie została zainicjowana, wartość to $null. Jest to jeden z najbardziej typowych sposobów, za pomocą których $null wartości wkradają się do twojego kodu.

PS> $null -eq $undefinedVariable
True

Jeśli zdarzy ci się błędnie wpisać nazwę zmiennej, program PowerShell widzi ją jako inną zmienną, a wartość to $null.

Innym sposobem znajdowania $null wartości jest to, że pochodzą one z innych poleceń, które nie dają żadnych wyników.

PS> function Get-Nothing {}
PS> $value = Get-Nothing
PS> $null -eq $value
True

Wpływ $null

$null wartości wpływają na kod inaczej w zależności od tego, gdzie się pojawiają.

W ciągach

Jeśli używasz $null w ciągu, jest to pusta wartość (lub pusty ciąg).

PS> $value = $null
PS> Write-Output "'The value is $value'"
'The value is '

Jest to jeden z powodów, dla których lubię umieszczać nawiasy wokół zmiennych podczas ich używania w komunikatach dziennika. Jeszcze ważniejsze jest zidentyfikowanie krawędzi wartości zmiennych, gdy wartość znajduje się na końcu ciągu.

PS> $value = $null
PS> Write-Output "The value is [$value]"
The value is []

Dzięki temu puste ciągi i $null wartości są łatwe do wykrycia.

W równaniu liczbowym

Kiedy wartość $null jest używana w równaniu liczbowym, wtedy wyniki są nieprawidłowe, jeśli nie powodują błędu. Czasami $null daje wynik 0, a innym razem powoduje, że cały rezultat to $null. Oto przykład z mnożeniem, który daje wartość 0 lub $null w zależności od kolejności wartości.

PS> $null * 5
PS> $null -eq ( $null * 5 )
True

PS> 5 * $null
0
PS> $null -eq ( 5 * $null )
False

Zamiast kolekcji

Kolekcja umożliwia uzyskiwanie dostępu do wartości przy użyciu indeksu. Jeśli spróbujesz zaindeksować kolekcję, która jest rzeczywiście null, zostanie wyświetlony następujący błąd: Cannot index into a null array.

PS> $value = $null
PS> $value[10]
Cannot index into a null array.
At line:1 char:1
+ $value[10]
+ ~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

Jeśli masz kolekcję, ale spróbujesz uzyskać dostęp do elementu, który nie znajduje się w kolekcji, otrzymasz $null wynik.

$array = @( 'one','two','three' )
$null -eq $array[100]
True

Zamiast obiektu

Jeśli spróbujesz uzyskać dostęp do właściwości lub właściwości podrzędnej obiektu, który nie ma określonej właściwości, uzyskasz wartość podobną $null do niezdefiniowanej zmiennej. Nie ma znaczenia, czy w tym przypadku jest to zmienna $null, czy faktyczny obiekt.

PS> $null -eq $undefined.Some.Fake.Property
True

PS> $date = Get-Date
PS> $null -eq $date.Some.Fake.Property
True

Metoda na wyrażeniu o wartości nullowej

Wywołanie metody w $null obiekcie zgłasza błąd RuntimeException.

PS> $value = $null
PS> $value.ToString()
You cannot call a method on a null-valued expression.
At line:1 char:1
+ $value.ToString()
+ ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

Za każdym razem, gdy widzę frazę You cannot call a method on a null-valued expression , pierwszą rzeczą, której szukam, są miejsca, w których wywołujem metodę w zmiennej bez uprzedniego sprawdzenia jej pod kątem $null.

Sprawdzanie, czy jest $null

Być może zauważyłeś, że zawsze umieszczam $null z lewej strony, kiedy sprawdzam $null w moich przykładach. Jest to celowe i akceptowane jako najlepsze rozwiązanie programu PowerShell. Istnieją pewne scenariusze, w których umieszczenie go po prawej stronie nie daje oczekiwanego wyniku.

Przyjrzyj się temu następnemu przykładowi i spróbuj przewidzieć wyniki:

if ( $value -eq $null )
{
    'The array is $null'
}
if ( $value -ne $null )
{
    'The array is not $null'
}

Jeśli nie zdefiniuję $value, pierwsza z nich zwróci wartość $true , a nasza wiadomość to The array is $null. Pułapka polega na tym, że można utworzyć $value, który pozwala, aby obydwoje byli $false

$value = @( $null )

W tym przypadku $value jest tablicą zawierającą $null element. Funkcja -eq sprawdza każdą wartość w tablicy i zwraca $null dopasowaną wartość. Ocena wynosi $false. Funkcja -ne zwraca wszystko, co nie jest zgodne z $null, a w tym przypadku nie ma żadnych wyników (to również jest oceniane jako $false). Żaden z nich nie jest $true, chociaż wygląda na to, że jeden z nich powinien być.

Nie tylko możemy utworzyć wartość, która sprawia, że obie są obliczane do $false, ale również wartość, przy której oba są obliczane do $true. Mathias Jessen (@IISResetMe) ma dobry wpis , który zagłębia się w ten scenariusz.

PSScriptAnalyzer i VS Code

Moduł PSScriptAnalyzer ma regułę sprawdzającą ten problem o nazwie PSPossibleIncorrectComparisonWithNull.

PS> Invoke-ScriptAnalyzer ./myscript.ps1

RuleName                              Message
--------                              -------
PSPossibleIncorrectComparisonWithNull $null should be on the left side of equality comparisons.

Ponieważ program VS Code używa również reguł PSScriptAnalyser, wyróżnia lub identyfikuje go jako problem w skrypcie.

Proste sprawdzenie warunku if

Typowym sposobem sprawdzania wartości innej niż $null jest użycie prostej if() instrukcji bez porównania.

if ( $value )
{
    Do-Something
}

Jeśli wartość to $null, to ocenia się na $false. Jest to łatwe do odczytania, ale uważaj, że szuka dokładnie tego, czego oczekujesz. Czytałem ten wiersz kodu jako:

Jeśli $value ma wartość.

Ale to nie jest cała historia. Ta linia rzeczywiście mówi:

Jeśli $value nie jest $null ani 0 ani $false, ani pustym ciągiem, ani pustą tablicą.

Oto bardziej kompletna próbka tego oświadczenia.

if ( $null -ne $value -and
        $value -ne 0 -and
        $value -ne '' -and
        ($value -isnot [array] -or $value.Length -ne 0) -and
        $value -ne $false )
{
    Do-Something
}

W porządku jest użyć podstawowej if kontroli, o ile pamiętasz, że inne wartości są liczone jako $false, a nie tylko, że zmienna ma wartość.

Napotkałem ten problem podczas refaktoryzacji kodu kilka dni temu. Miał podstawowy test właściwości w ten sposób.

if ( $object.Property )
{
    $object.Property = $value
}

Chciałem przypisać wartość do właściwości obiektu tylko wtedy, gdy istniała. W większości przypadków oryginalny obiekt miał wartość, która ocenia się jako $true w instrukcji if. Ale wpadłem w problem, w którym wartość czasami nie była ustawiana. Debugowałem kod i okazało się, że obiekt miał właściwość, ale był to pusty ciąg znaków. Uniemożliwiło to aktualizację przy użyciu poprzedniej logiki. Zatem dodałem właściwą $null kontrolę i wszystko zadziałało.

if ( $null -ne $object.Property )
{
    $object.Property = $value
}

To małe błędy, jak te, które są trudne do wykrycia i sprawiają, że intensywnie sprawdzam wartości dla $null.

$null.Liczba

Jeśli spróbujesz uzyskać dostęp do właściwości w wartości $null, to właściwość również ma $null wartość. Właściwość Count jest wyjątkiem od tej reguły.

PS> $value = $null
PS> $value.Count
0

Kiedy masz wartość $null, wtedy Count to 0. Ta specjalna właściwość jest dodawana przez program PowerShell.

[PSCustomObject] Liczba

Prawie wszystkie obiekty w programie PowerShell mają te Count właściwości. Jednym ważnym wyjątkiem jest [pscustomobject] w Windows PowerShell 5.1 (ten wyjątek został naprawiony w PowerShell 6.0). Nie ma Count właściwości, więc otrzymasz $null wartość, jeśli spróbujesz jej użyć. Zaznaczam to tutaj, aby nie próbować użyć Count zamiast kontroli $null.

Uruchomienie tego przykładu w programach Windows PowerShell 5.1 i PowerShell 6.0 daje różne wyniki.

$value = [pscustomobject]@{Name='MyObject'}
if ( $value.Count -eq 1 )
{
    "We have a value"
}

Wyliczalna wartość null

Istnieje jeden specjalny rodzaj $null , który działa inaczej niż inne. Mam zamiar nazwać to "wyliczalny null", ale to naprawdę System.Management.Automation.Internal.AutomationNull. Ten enumerowalny null jest wynikiem funkcji lub bloku skryptu, który nie zwraca niczego (pusty wynik).

PS> function Get-Nothing {}
PS> $nothing = Get-Nothing
PS> $null -eq $nothing
True

Jeśli porównasz to z $null, uzyskasz wartość $null. W przypadku użycia w ocenie, gdy wartość jest wymagana, zawsze jest $null. Jeśli jednak umieścisz ją wewnątrz tablicy, będzie ona traktowana tak samo jak pusta tablica.

PS> $containEmpty = @( @() )
PS> $containNothing = @($nothing)
PS> $containNull = @($null)

PS> $containEmpty.Count
0
PS> $containNothing.Count
0
PS> $containNull.Count
1

Możesz mieć tablicę zawierającą jedną $null wartość, a jej Count wartość to 1. Jeśli jednak umieścisz pustą tablicę wewnątrz tablicy, nie jest ona liczone jako element. Liczba to 0.

Jeśli traktujesz wyliczalny null jak kolekcję, to jest ona pusta.

W przypadku przekazania wartości null, która jest enumerowalna, do parametru funkcji, który nie jest silnie typowany, PowerShell domyślnie przekształca tę wartość null na $null. Oznacza to, że wewnątrz funkcji wartość jest traktowana jako $null zamiast typu System.Management.Automation.Internal.AutomationNull .

Rurociąg

Podstawowe miejsce, w którym widać różnicę, to w przypadku korzystania z potoku. Wartość $null można przekazać potokiem, ale nie wartość null enumerowalną.

PS> $null | ForEach-Object{ Write-Output 'NULL Value' }
'NULL Value'
PS> $nothing | ForEach-Object{ Write-Output 'No Value' }

W zależności od kodu należy uwzględnić element $null w logice.

Sprawdź najpierw $null

  • Usuń wartości null z potoku (... | where {$null -ne $_} | ...)
  • Obsłuż to w funkcji potoku

foreach

Jedną z moich ulubionych funkcji foreach jest to, że nie wylicza kolekcji $null.

foreach ( $node in $null )
{
    #skipped
}

Dzięki temu nie trzeba $null sprawdzać kolekcji, zanim ją wyliczę. Jeśli masz kolekcję $null wartości, $node nadal może być $null.

Zaczęto używać foreach w ten sposób w programie PowerShell 3.0. Jeśli masz starszą wersję, tak nie jest. Jest to jedna z ważnych zmian, o których należy pamiętać, przy dostosowywaniu kodu wstecz do kompatybilności z wersją 2.0.

Typy wartości

Technicznie tylko typy referencyjne mogą mieć wartość $null. Jednak program PowerShell jest bardzo hojny i umożliwia używanie zmiennych do dowolnego typu. Jeśli zdecydujesz się na silne typowanie typu wartości, nie może to być $null. Program PowerShell konwertuje $null na wartość domyślną dla wielu typów.

PS> [int]$number = $null
PS> $number
0

PS> [bool]$boolean = $null
PS> $boolean
False

PS> [string]$string = $null
PS> $string -eq ''
True

Istnieją pewne typy, które nie mają prawidłowej konwersji z $null. Te typy generują Cannot convert null to type błąd.

PS> [datetime]$date = $null
Cannot convert null to type "System.DateTime".
At line:1 char:1
+ [datetime]$date = $null
+ ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException

Parametry funkcji

Używanie silnie typowanych wartości w parametrach funkcji jest bardzo powszechne. Zazwyczaj uczymy się definiować typy naszych parametrów, nawet jeśli nie definiujemy typów innych zmiennych w naszych skryptach. Być może masz już pewne silnie typizowane zmienne w funkcjach, a nawet nie zdajesz sobie z tego sprawy.

function Do-Something
{
    param(
        [string] $Value
    )
}

Jak tylko ustawisz typ parametru jako string, wartość nigdy nie może być $null. Często sprawdza się, czy wartość to $null, aby zobaczyć, czy użytkownik podał wartość, czy nie.

if ( $null -ne $Value ){...}

$Value jest pustym ciągiem '' , gdy nie podano żadnej wartości. Zamiast tego użyj zmiennej automatycznej $PSBoundParameters.Value .

if ( $null -ne $PSBoundParameters.Value ){...}

$PSBoundParameters Zawiera tylko parametry, które zostały określone podczas wywoływanej funkcji. Możesz także użyć metody ContainsKey, aby sprawdzić właściwość.

if ( $PSBoundParameters.ContainsKey('Value') ){...}

NieJestPustyLubNieMaNulla

Jeśli wartość jest ciągiem, możesz użyć funkcji statycznej do sprawdzenia, czy wartość jest $null lub pustym ciągiem jednocześnie.

if ( -not [string]::IsNullOrEmpty( $value ) ){...}

Używam tego często, gdy wiem, że typ wartości powinien być ciągiem.

Kiedy sprawdzam wartość $null

Jestem defensywnym skrypterem. Za każdym razem, gdy wywołujem funkcję i przypisuję ją do zmiennej, sprawdzam ją pod kątem $null.

$userList = Get-ADUser kevmar
if ($null -ne $userList){...}

Wolę zdecydowanie używać if lub foreach zamiast używać try/catch. Nie zrozum mnie źle, wciąż często używam try/catch. Ale jeśli mogę przetestować warunek błędu lub pusty zestaw wyników, mogę skoncentrować się na obsłudze rzeczywistych wyjątków.

Mam również tendencję do sprawdzania $null zanim zaindeksuję wartość lub wywołam metody na obiekcie. Obie te akcje kończą się niepowodzeniem w przypadku obiektu $null, więc uważam, że ważne jest, aby najpierw je zweryfikować. Te scenariusze zostały już omówione wcześniej w tym wpisie.

Brak scenariusza wyników

Ważne jest, aby wiedzieć, że różne funkcje i polecenia obsługują scenariusz bez wyników inaczej. Wiele poleceń programu PowerShell zwraca null będący typem wyliczeniowym oraz błąd w strumieniu błędów. Ale inni zgłaszają wyjątki lub zwracają obiekt statusu. Nadal musisz wiedzieć, jak polecenia, których używasz, radzą sobie z brakiem wyników i scenariuszami błędów.

Inicjowanie do $null

Jednym z nawyków, które nabyłem, jest inicjowanie wszystkich moich zmiennych przed ich użyciem. Musisz to zrobić w innych językach. Na początku mojej funkcji lub gdy wchodzę do foreach pętli, definiuję wszystkie wartości, których używam.

Chcę, żebyś przyjrzał się bliżej temu scenariuszowi. To przykład błędu, który musiałem wcześniej ścigać.

function Do-Something
{
    foreach ( $node in 1..6 )
    {
        try
        {
            $result = Get-Something -Id $node
        }
        catch
        {
            Write-Verbose "[$result] not valid"
        }

        if ( $null -ne $result )
        {
            Update-Something $result
        }
    }
}

W tym przypadku oczekuje się, że Get-Something zwraca wynik lub enumerowalny null. Jeśli wystąpi błąd, rejestrujemy go. Następnie sprawdzamy, czy otrzymaliśmy prawidłowy wynik przed jego przetworzeniem.

Błąd w tym kodzie występuje, gdy Get-Something zgłasza wyjątek i nie przypisuje wartości do elementu $result. Zawodzi przed przypisaniem, więc nawet nie przypisujemy $null do zmiennej $result. $result nadal zawiera poprzednią prawidłową wartość $result z innych iteracji. Aby wykonać tę operację wiele razy na tym samym obiekcie w tym przykładzie, użyj Update-Something.

Ustawiam $result na $null bezpośrednio wewnątrz pętli foreach, zanim go użyję, aby rozwiązać ten problem.

foreach ( $node in 1..6 )
{
    $result = $null
    try
    {
        ...

Problemy z zakresem

Pomaga to również łagodzić problemy związane z zakresem. W tym przykładzie przypisujemy wartości do $result w kółko w pętli. Jednak ponieważ program PowerShell zezwala na przenikanie wartości zmiennych spoza funkcji do zakresu bieżącej funkcji, inicjowanie ich wewnątrz funkcji zmniejsza ryzyko błędów, które mogą być w ten sposób wprowadzone.

Niezainicjowana zmienna w twojej funkcji nie jest $null, jeśli zostanie przypisana wartość w zakresie nadrzędnym. Zakres nadrzędny może być inną funkcją, która wywołuje twoją funkcję i używa tych samych nazw zmiennych.

Jeśli wezmę ten sam Do-something przykład i usunę pętlę, skończę z czymś, co wygląda następująco:

function Invoke-Something
{
    $result = 'ParentScope'
    Do-Something
}

function Do-Something
{
    try
    {
        $result = Get-Something -Id $node
    }
    catch
    {
        Write-Verbose "[$result] not valid"
    }

    if ( $null -ne $result )
    {
        Update-Something $result
    }
}

Jeśli wywołanie Get-Something zgłasza wyjątek, to moje sprawdzenie $null znajduje $result z Invoke-Something. Inicjalizacja wartości w funkcji zmniejsza ten problem.

Nazywanie zmiennych jest trudne i jest powszechne, że autor używa tych samych nazw zmiennych w wielu funkcjach. Wiem, że używam $node,$result,$data cały czas. Dlatego bardzo łatwo byłoby wyświetlić wartości z różnych zakresów w miejscach, w których nie powinny być.

Przekierowywanie danych wyjściowych do $null

Mówiłem o $null wartościach w całym tym artykule, ale temat nie byłby kompletny, gdybym nie wspomniał o przekierowywaniu danych wyjściowych do $null. Czasami istnieją polecenia, które wyświetlają informacje wyjściowe lub obiekty, które mają zostać pominięte. Przekierowywanie danych wyjściowych do $null to robi.

Out-Null

Polecenie Out-Null to wbudowany sposób przekierowywania danych z potoku do usługi $null.

New-Item -Type Directory -Path $path | Out-Null

Przypisz do $null

Możesz przypisać wyniki polecenia do $null, aby uzyskać ten sam efekt, co przy użyciu Out-Null.

$null = New-Item -Type Directory -Path $path

Ponieważ $null jest to stała wartość, nigdy nie można jej zastąpić. Nie lubię sposobu, w jaki wygląda w kodzie, ale często działa szybciej niż Out-Null.

Przekieruj do $null

Możesz również użyć operatora przekierowania, aby wysłać dane wyjściowe do .$null

New-Item -Type Directory -Path $path > $null

Jeśli masz do czynienia z plikami wykonywalnymi wiersza polecenia, które generują dane wyjściowe w różnych strumieniach. Możesz przekierować wszystkie strumienie wyjściowe na $null w następujący sposób:

git status *> $null

Podsumowanie

Pokryłem wiele tematów w tym artykule i wiem, że jest on bardziej fragmentaryczny niż większość moich dogłębnych analiz. Wynika to z faktu, że wartości $null mogą pojawiać się w wielu różnych miejscach w programie PowerShell, a wszystkie niuanse są specyficzne dla miejsca, w którym je znajdziesz. Mam nadzieję, że odejdziesz od tego z lepszym zrozumieniem $null i świadomością bardziej niejasnych scenariuszy, które możesz napotkać.