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 w przypadku nieoczekiwanego uruchomienia wartości $null .

Uwaga

Oryginalna wersja tego artykułu pojawiła się na blogu napisanym przez użytkownika @KevinMarquette. Zespół programu PowerShell dziękuje Zespołowi Za udostępnienie tej zawartości. Zapoznaj się z jego blogiem na PowerShellExplained.com.

Co to jest null?

Wartość NULL można określić jako wartość nieznaną lub pustą. Zmienna ma wartość NULL, dopóki nie przypiszesz do niego 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.

Program PowerShell $null

$null jest automatyczną zmienną w programie PowerShell używaną do reprezentowania wartości NULL. Można przypisać go do zmiennych, używać go w porównaniach i używać jako właściciela miejsca dla wartości NULL w kolekcji.

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

Przykłady $null

Za każdym razem, gdy próbujesz użyć zmiennej, która nie jest zainicjowana, wartość to $null. Jest to jeden z najpopularniejszych sposobów, w jaki wartości $null wywrzeć na kodzie.

PS> $null -eq $undefinedVariable
True

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

Innym sposobem znalezienia wartości $null jest to, że pochodzą 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 wartości $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 identyfikowanie 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 wartości $null są łatwe do zauważenia.

W równaniu liczbowym

Gdy wartość $null jest używana w równaniu liczbowym, wyniki są nieprawidłowe, jeśli nie dają błędu. Czasami wynikiem $null jest wartość , 0 a w innym przypadku cały wynik $null. Oto przykład z mnożeniem, które daje 0 $null lub 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

W miejsce kolekcji

Kolekcja umożliwia używanie indeksu do uzyskiwania dostępu do wartości. Jeśli spróbujesz zaindeksować do kolekcji, która jest w rzeczywistości null, wystąpi ten 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 pró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

W miejsce 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, $null otrzymasz wartość, jak w przypadku niezdefiniowanych zmiennych. W tym przypadku nie ma znaczenia, czy zmienna $null jest, czy jest 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 na obiekcie $null zgłasza wyjątek 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łuję metodę dla zmiennej bez wcześniejszego sprawdzenia jej pod uwagę $null.

Sprawdzanie pod $null

Być może zauważasz, że zawsze umieszczam $null to po lewej stronie podczas sprawdzania w $null moich przykładach. Jest to zamierzone i zaakceptowane jako najlepsze rozwiązanie programu PowerShell. Istnieją pewne scenariusze, w których umieszczenie go po prawej stronie nie daje oczekiwanego wyniku.

Spójrz na następny przykład 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 zdefinię $valuewartości , pierwsza z nich będzie miała wartość , $true a komunikat będzie mieć wartość The array is $null. W tej pułapce można utworzyć $value , co umożliwi utworzenie obu tych $false

$value = @( $null )

W tym przypadku jest to $value tablica zawierająca element $null. Funkcja -eq sprawdza każdą wartość w tablicy i zwraca $null wartość , która jest do siebie dopasowana. W tym przypadku wartość to $false. Funkcja -ne zwraca wszystkie wartości, które nie $null są zgodne i w tym przypadku nie ma żadnych wyników (jest to również wynikiem wartości $false). Żadna z nich nie $true jest, mimo że wygląda na to, że jedna z nich powinna być.

Nie tylko możemy utworzyć wartość $false, która sprawia, że oba z nich są oceniane na wartość , ale można utworzyć wartość, w której obaj oceniają wartość $true. Mathias Jessen (@IISResetMe) ma dobry wpis , który zagłębia się w ten scenariusz.

PSScriptAnalyzer i VSCode

Moduł PSScriptAnalyzer zawiera regułę, która sprawdza ten problem o nazwie PSPossibleIncorrectComparisonWithNull.

PS> Invoke-ScriptAnalyzer ./myscript.ps1

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

Ponieważ VS Code korzysta również z reguł PSScriptAnalyser, wyróżnia lub identyfikuje to jako problem w skrypcie.

Proste w przypadku sprawdzenia

Częstym sposobem, w jaki ludzie sprawdzają wartość $null jest if() użycie prostej instrukcji bez porównania.

if ( $value )
{
    Do-Something
}

Jeśli wartość to $null, jest to wartość $false. Jest to łatwe do odczytania, ale należy uważać, aby szukać dokładnie tego, czego oczekujesz. Czytam ten wiersz kodu jako:

Jeśli $value ma wartość .

Ale to nie jest cała historia. Ten wiersz w rzeczywistości mówi:

Jeśli $value nie jest $null lub 0 lub $false``empty string

Oto bardziej kompletny przykład tej instrukcji.

if ( $null -ne $value -and
        $value -ne 0 -and
        $value -ne '' -and
        $value -ne $false )
{
    Do-Something
}

Jeśli pamiętasz if $false , że te inne wartości są liczone jako , a nie tylko zmienna ma wartość, można użyć podstawowego sprawdzenia.

Kilka dni temu podczas refaktoryzacji kodu występuje ten problem. Miało ono podstawowe sprawdzenie właściwości podobne do tego.

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

Chcę 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 oceniana jako w $true instrukcji if . Jednak popadłem w problem, w którym wartość czasami nie była ustawiana. Po debugowaniu kodu okazało się, że obiekt ma właściwość , ale jest to pusta wartość ciągu. Uniemożliwiło to jego zaktualizowanie przy użyciu poprzedniej logiki. Dlatego dodano właściwe sprawdzenie $null i wszystko działało.

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

Jest to mało usterek, takich jak te, które trudno zauważyć i sprawić, że agresywnie sprawdzam wartości dla .$null

$null. Liczba

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

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

Jeśli masz wartość $null , wartość to count 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] Windows PowerShell 5.1 (ten problem został rozwiązany w programie PowerShell 6.0). Nie ma właściwości count, więc jeśli $null spróbujesz jej użyć, otrzymasz wartość. Nazywam to tutaj, aby .Count nie próbować używać zamiast sprawdzania $null .

Uruchomienie tego przykładu w 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 typ, który $null działa inaczej niż inne. Nazwać ją pustą, $null ale tak naprawdę jest to System.Management.Automation.Internal.AutomationNull. Ten pusty $null to ten, który jest zwracany w wyniku działania 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 go z $nullwartością , otrzymasz $null wartość. W przypadku korzystania z wartości w ocenie, gdy wartość jest wymagana, wartość jest zawsze wartością $null. Jeśli jednak umieszczasz ją wewnątrz tablicy, jest 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ą wartość, a $null jej wartość to count 1. Jeśli jednak umieszczasz pusty wynik wewnątrz tablicy, nie jest on liczony jako element. Liczba to 0.

Jeśli traktujesz puste jak $null kolekcję, jest ono puste.

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

Potok

Główną różnicą jest użycie potoku. Można w potoku utworzyć $null wartość, ale nie 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ć w logice $null .

Sprawdź, czy najpierw $null

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

foreach

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

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

Dzięki temu nie trzeba sprawdzać $null kolekcji przed jej wyliczeniem. Jeśli masz kolekcję wartości $null , nadal może $node to być $null.

Program 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ć podczas przenoszenia kodu wstecznego w celu zapewnienia zgodności z 2.0.

Typy wartości

Technicznie rzecz ujmuje się, że tylko typy referencyjne mogą być typu $null. Jednak program PowerShell jest bardzo obszerny i umożliwia korzystanie ze zmiennych dowolnego typu. Jeśli zdecydujesz się na silnie typ wartości, nie może to być typ $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ą błąd Cannot convert null to type .

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 typiznych wartości w parametrach funkcji jest bardzo częste. Zazwyczaj uczymy się definiować typy naszych parametrów, nawet jeśli zwykle nie definiujemy typów innych zmiennych w naszych skryptach. Być może masz już pewne silnie typizowane zmienne w funkcjach i nawet nie zdasz sobie z tego sprawy.

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

Gdy tylko ustawisz typ parametru jako string, wartość nigdy nie może być wartością $null. Często sprawdza się, czy wartość ma być $null sprawdzana, czy użytkownik podał wartość, czy nie.

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

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

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

$PSBoundParameters Zawiera tylko parametry określone podczas wywoływania funkcji. Możesz również użyć metody ContainsKey , aby sprawdzić właściwość .

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

IsNotNullOrEmpty

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

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

Często korzystam z tej funkcji, gdy wiem, że typ wartości powinien być ciągiem.

Po $null sprawdzić

Jestem skryptem obronnym. Za każdym razem, gdy wywołam funkcję i przypisuję ją do zmiennej, sprawdzam jej wartość $null.

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

Zdecydowanie preferuję używanie lub if foreach zamiast .try/catch Nie wywołuj tego błędu, nadal używam try/catch wielu. 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.

Zwykle sprawdzam również, czy indeksuję $null do wartości lub metody wywołania obiektu. Te dwie akcje nie powiodą się dla $null obiektu, dlatego ważne jest ich zweryfikowanie w pierwszej kolejności. Te scenariusze zostały już uwzględnione wcześniej w tym wpisie.

Scenariusz bez wyników

Ważne jest, aby wiedzieć, że różne funkcje i polecenia inaczej obsługują scenariusz bez wyników. Wiele poleceń programu PowerShell zwraca puste wartości $null i błąd w strumieniu błędów. Jednak inne osoby zgłaszają wyjątki lub zapewniają obiekt stanu. Nadal musisz wiedzieć, jak polecenia, których używasz, radzić sobie ze scenariuszami bez wyników i błędów.

Inicjowanie do $null

Jednym z nawyków, które wybraliśmy, jest inicjowanie wszystkich zmiennych przed ich użyciem. Musisz to zrobić w innych językach. W górnej części funkcji lub po wprowadzeniu pętli foreach zdefiniuję wszystkie wartości, których używasz.

Oto scenariusz, który chcę dokładniej przyjrzeć się temu scenariuszowi. Jest to przykład błędu, który musiałem wcześniej schować.

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

Oczekiwanie jest takie, że zwraca Get-Something wynik lub wartość pustą $null. Jeśli wystąpi błąd, zostanie on rejestrny. Następnie sprawdzamy, czy mamy prawidłowy wynik przed jego przetworzeniem.

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

Ustawię $result wartość $null bezpośrednio wewnątrz pętli foreach, zanim użyję jej do ograniczenia tego problemu.

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

Problemy z zakresem

Pomaga to również rozwiązać problemy z zakresu. W tym przykładzie przypisujemy wartości w $result pętli. Jednak ponieważ program PowerShell umożliwia wywłaszczaniu wartości zmiennych spoza funkcji do zakresu bieżącej funkcji, inicjowanie ich wewnątrz funkcji ogranicza błędy, które mogą zostać wprowadzone w ten sposób.

Niezainicjowana zmienna w funkcji $null nie 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 przejdę do tego samego Do-something przykładu i usuną pętlę, w końcu zobaczę coś, co wygląda podobnie do tego przykładu:

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 do zgłasza Get-Something wyjątek, moje sprawdzenie $null znajdzie element z $result .Invoke-Something Inicjowanie wartości wewnątrz funkcji pozwala rozwiązać ten problem.

Nazewnictwo zmiennych jest trudne i autor często używa tych samych nazw zmiennych w wielu funkcjach. Wiem, że używam $node,$result,$data przez cały czas. Dlatego bardzo łatwo wartości z różnych zakresów mogą być wyświetlane w miejscach, w których nie powinny.

Przekierowywanie danych wyjściowych do $null

Mówię o wartościach $null dla całego artykułu, ale temat nie jest kompletny, jeśli nie wspomnę o przekierowaniu danych wyjściowych do .$null Czasami istnieją polecenia, które wyprowadzają informacje lub obiekty, które mają zostać pominięte. Przekierowywanie danych wyjściowych do tego $null 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

Wyniki polecenia można przypisać do , aby $null uzyskać taki sam efekt jak przy użyciu polecenia Out-Null.

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

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

Przekierowanie 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 są wyjściowe w różnych strumieniach. Wszystkie strumienie wyjściowe można przekierować w taki $null sposób:

git status *> $null

Podsumowanie

Na tym tematu omówiłem wiele podstaw i wiem, że ten artykuł jest bardziej fragmentaryzowany niż większość moich dogłębnych informacji. Wynika to z $null tego, że wartości mogą pojawiać się w wielu różnych miejscach w programie PowerShell, a wszystkie jego niuanse są specyficzne dla tego, gdzie je znajdziesz. Mam nadzieję, że odchodzisz od tego z $null lepszym zrozumieniem i świadomością bardziej niejasnych scenariuszy, w których możesz się liczyć.