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 napisanym 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, w jaki $null wartości wkradają się do 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

$null Jeśli wartość jest używana w równaniu liczbowym, wyniki są nieprawidłowe, jeśli nie dają błędu. $null Czasami daje w wyniku 0 wartość , a w innym czasie cały wynik $nulljest wynikiem . 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 zmienna jest $null lub rzeczywisty obiekt w tym przypadku.

PS> $null -eq $undefined.some.fake.property
True

PS> $date = Get-Date
PS> $null -eq $date.some.fake.property
True

Metoda w wyrażeniu o wartości null

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 $null

Być może zauważyłeś, że zawsze umieszczam po $null lewej stronie podczas sprawdzania $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ć element $value , który pozwala obu z nich być $false

$value = @( $null )

W tym przypadku $value jest to tablica zawierająca $nullelement . Funkcja -eq sprawdza każdą wartość w tablicy i zwraca $null dopasowaną wartość. Daje to wartość .$false Funkcja -ne zwraca wszystko, co nie jest zgodne$null, a w tym przypadku nie ma żadnych wyników (daje to również wynik ).$false Żaden z nich nie jest $true taki, mimo że wygląda na to, że jeden z nich powinien być.

Nie tylko możemy utworzyć wartość, która sprawia, że obie z nich są obliczane na $falsewartość , można utworzyć wartość, w której obie wartości są obliczane na $truewartość . Mathias Jessen (@IISResetMe) ma dobry wpis , który zagłębia się w ten scenariusz.

PSScriptAnalyzer i VSCode

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 sprawdzanie, czy

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, zwraca wartość .$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 $null jest lub 0$false pusty ciąg lub pusta tablica.

Oto bardziej kompletna próbka tej instrukcji.

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
}

Doskonale jest ok, aby użyć podstawowego if sprawdzenia, o ile pamiętasz, że inne wartości są liczone jako $false i nie tylko, że zmienna ma wartość.

Napotkałem ten problem podczas refaktoryzacji kodu kilka dni temu. Miał podstawową kontrolę 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 zwróci wartość $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 pusta wartość ciągu. Uniemożliwiło to aktualizację przy użyciu poprzedniej logiki. Więc 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, takie jak te, które są trudne do wykrycia i sprawiają, że agresywnie sprawdzam wartości dla $null.

$null. Liczba

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

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

Jeśli masz $null wartość, wartość count to 0. Ta specjalna właściwość jest dodawana przez program PowerShell.

[PSCustomObject] Liczba

Prawie wszystkie obiekty w programie PowerShell mają te właściwości count. Jednym ważnym wyjątkiem jest [PSCustomObject] program Windows PowerShell 5.1 (jest to naprawione w programie PowerShell 6.0). Nie ma właściwości count, więc otrzymasz $null wartość, jeśli spróbujesz jej użyć. Wzywam to tutaj, aby nie próbować użyć .Count zamiast sprawdzania $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"
}

Pusta wartość null

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

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

Jeśli porównasz ją z $nullwartością $null , uzyskasz wartość. W przypadku użycia w ocenie, gdy wartość jest wymagana, wartość jest zawsze $nullwartością . 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 pusty wynik wewnątrz tablicy, nie jest on liczone jako element. Liczba to 0.

Jeśli traktujesz pustą pustą $null kolekcję, jest ona pusta.

Jeśli przekażesz pustą wartość do parametru funkcji, który nie jest silnie typizowany, program PowerShell domyślnie przekazuje wartość nic do $null wartości. Oznacza to, że wewnątrz funkcji wartość będzie traktowana jako $null zamiast typu System.Management.Automation.Internal.AutomationNull .

Potok

Podstawowe miejsce, w którym widać różnicę, to w przypadku korzystania z potoku. Możesz przekazać wartość, $null ale nie wartość pustą $null .

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.

Sprawdzanie pierwszego $null

  • Odfiltruj wartość null w potoku (... | Where {$null -ne $_} | ...)
  • Obsługa jej w funkcji potoku

foreach

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

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 to być $null.

Foreach rozpoczął pracę w ten sposób z programem 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ć, gdy kod przenoszenia wstecznego dla wersji 2.0 jest zgodny.

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 silnie typ wartości, nie może to być $null. Program PowerShell konwertuje $null 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 typiowanych 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ścią jest $null sprawdzenie, 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 również użyć ContainsKey metody , aby sprawdzić właściwość .

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

IsNotNullOrEmpty

Jeśli wartość jest ciągiem, możesz użyć funkcji ciągów statycznych, aby sprawdzić, czy wartość jest lub czy ciąg jest $null pusty w tym samym czasie.

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

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

Kiedy $null sprawdzić

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){...}

Bardzo wolę używać lub foreach za pomocą if polecenia try/catch. Nie pomylij się, nadal dużo używam try/catch . Ale jeśli mogę przetestować warunek błędu lub pusty zestaw wyników, mogę zezwolić na obsługę wyjątków dla prawdziwych wyjątków.

Mam również tendencję do sprawdzania $null , zanim zaindeksuję do metody wartości lub wywołania obiektu. Te dwie akcje kończą się niepowodzeniem $null dla obiektu, 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 wartość pustą $null i błąd w strumieniu błędów. Ale inni zgłaszają wyjątki lub dają obiekt stanu. Nadal musisz wiedzieć, jak polecenia, których używasz, mają do czynienia z brakiem wyników i scenariuszy błędów.

Inicjowanie do $null

Jednym z nawyków, które podniósłem, jest inicjowanie wszystkich moich zmiennych przed ich użyciem. Musisz to zrobić w innych językach. W górnej części funkcji lub podczas wprowadzania pętli foreach zdefiniuję wszystkie wartości, których używam.

Oto scenariusz, który chcę, aby przyjrzeć się bliżej. To przykład błędu musiałem gonić wcześniej.

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 pusty $nullelement . Jeśli wystąpi błąd, rejestrujemy go. Następnie sprawdzamy, czy otrzymaliśmy prawidłowy wynik przed jego przetworzeniem.

Usterka ukrywana w tym kodzie jest wtedy, gdy Get-Something zgłasza wyjątek i nie przypisuje wartości do $resultelementu . Kończy się niepowodzeniem przed przypisaniem, więc nie przypisujemy $null jej nawet do zmiennej $result . $result nadal zawiera poprzednią prawidłową wartość $result z innych iteracji. Update-Something aby wykonać wiele razy na tym samym obiekcie w tym przykładzie.

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

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

Problemy z zakresem

Pomaga to również wyeliminować problemy określające zakres. W tym przykładzie przypisujemy wartości do $result ponad i w pętli. Jednak ponieważ program PowerShell zezwala na wartości zmiennych spoza funkcji, aby krwawić do zakresu bieżącej funkcji, inicjowanie ich wewnątrz funkcji zmniejsza liczbę usterek, które można wprowadzić w ten sposób.

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

Jeśli biorę ten sam Do-something przykład i usuń pętlę, w końcu znajdziesz coś, 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 w celu Get-Something zgłoszenia wyjątku, sprawdź $null , czy znajduje element $result z Invoke-Something. Inicjowanie wartości wewnątrz funkcji ogranicza ten problem.

Zmienne nazewnictwa są trudne i często autor używa tych samych nazw zmiennych w wielu funkcjach. Wiem,$data że używam $node,$result przez 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ę o $null wartościach dla tego całego artykułu, ale temat nie został ukończony, jeśli nie wspomniałem 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 tego celu.

Out-Null

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

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

Przypisywanie do $null

Możesz przypisać wyniki polecenia do dla $null tego samego efektu, co przy użyciu polecenia 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 w następujący $null sposób:

git status *> $null

Podsumowanie

Obejmowałem wiele ziemi na tym jednym i wiem, że ten artykuł jest bardziej rozdrobniony niż większość moich głębokich nurkowań. Wynika to z faktu, że $null wartości mogą pojawiać się w wielu różnych miejscach w programie PowerShell, a wszystkie niuanse są specyficzne dla tego, gdzie go znajdziesz. Mam nadzieję, że odejdziesz od tego z lepszym zrozumieniem $null i świadomością bardziej niejasnych scenariuszy, które możesz napotkać.