Wszystko, co chcesz 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. Sprawdź swój blog 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 jest zmienną automatyczną w programie PowerShell używaną 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 spróbujesz użyć zmiennej, która nie została zainicjowana, wartość to $null. Jest to jeden z najpopularniejszych 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 zobaczy 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 są wyświetlane.

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 używania ich 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 i w innych przypadkach cały wynik $null. Oto przykład z mnożeniem, które 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ć w kolekcji, która jest w rzeczywistości 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 $null wartość, jak w przypadku niezdefiniowanej zmiennej. Nie ma znaczenia, czy zmienna jest $null czy w tym przypadku rzeczywistym obiektem.

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

Zawsze, 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ę dla zmiennej bez uprzedniego sprawdzenia jej pod kątem $null.

Sprawdzanie $null

Być może zauważyłeś, że zawsze umieszczam element 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ę $valueelementu , pierwsza z nich zwróci wartość $true , a naszym komunikatem jest The array is $null. Pułapka polega na tym, że można utworzyć element $value , który pozwala im 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 w ocenie wartość $false. Funkcja -ne zwraca wszystko, co nie jest zgodne$null, a w tym przypadku nie ma żadnych wyników (jest to również wynikiem ).$false Żaden z nich nie jest $true , 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 je jako problem w skrypcie.

Proste sprawdzanie w przypadku

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, daje $falsewartość . 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 ani 0$false ciągiem pustym, albo pustą tablicą.

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 nadaje się do użycia podstawowego if sprawdzania, o ile pamiętasz, że inne wartości są liczone jako $false , a nie tylko, że zmienna ma wartość.

Wystąpiłem w tym problemie podczas refaktoryzacji kodu kilka dni temu. Miał podstawową kontrolę właściwości w następujący 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 byłaby obliczana $true w instrukcji if . Ale wpadłem na problem, w którym wartość od czasu do czasu nie jest 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ą właściwość count. Jednym z ważnych wyjątków jest [PSCustomObject] w 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żywać .Count zamiast sprawdzania $null .

Uruchomienie tego przykładu w systemach 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"
}

Puste wartości 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. Jest to wartość pusta $null uzyskana w wyniku bloku funkcji lub 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ą kolekcję, jest ona pusta $null .

W przypadku przekazania pustej wartości do parametru funkcji, który nie jest silnie wpisany, program PowerShell domyślnie przekształca wartość nic w $null wartość. Oznacza to, że wewnątrz funkcji wartość będzie traktowana $null zamiast typu System.Management.Automation.Internal.AutomationNull .

Potok

Podstawowe miejsce, w którym widać różnicę, jest używana podczas korzystania z potoku. Możesz potokować $null wartość, ale nie pustą $null wartość.

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ć $null element w logice.

Jedną z nich należy $null najpierw sprawdzić

  • Filtrowanie wartości 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ą wyliczyłem. 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 zdarzy ci się być w starszej wersji, to nie jest tak. Jest to jedna z ważnych zmian, które należy wziąć pod uwagę, gdy kod przenoszenia kopii zapasowej dla zgodności z wersją 2.0.

Typy wartości

Technicznie tylko typy referencyjne mogą być $null. Jednak program PowerShell jest bardzo hojny i umożliwia używanie zmiennych do dowolnego typu. Jeśli zdecydujesz się na silnie wpisz 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 $nullklasy . 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. Ogólnie 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
    )
}

Gdy tylko ustawisz typ parametru jako stringwartość , wartość nigdy nie może być $nullrówna . 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żna również użyć metody do ContainsKey sprawdzenia właściwości.

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 $null lub pusty ciąg w tym samym czasie.

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

Często używam tego typu wartości, gdy wiem, że typ wartości powinien być ciągiem.

Kiedy $null sprawdzić

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

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

Wolisz używać lub foreach ponad za pomocą iftry/catch. Nie mylę się, nadal używam try/catch dużo. 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 przed indeksem do wartości lub wywołania metod na obiekcie. Te dwie akcje kończą się niepowodzeniem $null dla obiektu, więc uważam, że ważne jest, aby najpierw je zweryfikować. Już omówiłem te scenariusze 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 trzeba wiedzieć, w jaki sposób polecenia, których używasz, nie dotyczą żadnych wyników i scenariuszy błędów.

Inicjowanie do $null

Jeden nawyk, który podniósłem, inicjuje wszystkie moje zmienne przed ich użyciem. Musisz to zrobić w innych językach. W górnej części mojej funkcji lub podczas wprowadzania pętli foreach zdefiniuj wszystkie używane wartości.

Oto scenariusz, na który chcesz przyjrzeć się bliżej. Jest to przykład błędu, który 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 sprawdzimy, 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 . Nie powiedzie się przed przypisaniem, więc nawet nie przypisujemy $null 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.

Ustawiłem wartość $result$null bezpośrednio 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 over i over w pętli. Ponieważ program PowerShell zezwala na wprowadzanie wartości zmiennych spoza funkcji do zakresu bieżącej funkcji, inicjowanie ich wewnątrz funkcji ogranicza błędy, 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 zajmę ten sam Do-something przykład i usuń pętlę, w końcu pojawi się 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 Get-Something metody zgłasza wyjątek, sprawdź, $null czy znajduje element $result z Invoke-Something. Inicjowanie wartości wewnątrz funkcji ogranicza ten problem.

Zmienne nazewnictwa są trudne i autor często używa tych samych nazw zmiennych w wielu funkcjach. Wiem, że używam $node,$result,$data cały czas. Byłoby więc bardzo łatwo, aby wartości z różnych zakresów były wyświetlane 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. W przypadku poleceń wyjściowych lub obiektów, które mają zostać pominięte, występują polecenia. Przekierowuje dane wyjściowe do $null tego celu.

Out-Null

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

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

Przypisywanie do $null

Wyniki polecenia można przypisać do $null dla 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 moim kodzie, ale często działa szybciej niż Out-Null.

Przekierowywanie 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 $null następujący sposób:

git status *> $null

Podsumowanie

Omówiłem wiele ziemi na tym 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 ją znajdziesz. Mam nadzieję, że odejdziesz od tego z lepszym zrozumieniem $null i świadomością bardziej niejasnych scenariuszy, które możesz napotkać.