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 $null
jest 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 $null
element . 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 $false
wartość , można utworzyć wartość, w której obie wartości są obliczane na $true
wartość . 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 lub0
$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ż $null
wartość . 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"
}
Wyliczalna wartość null
Istnieje jeden specjalny rodzaj $null
, który działa inaczej niż inne. Mam zamiar nazwać to wyliczenie null, ale to naprawdę System.Management.Automation.Internal.AutomationNull.
Ta wyliczalna wartość null jest wartością uzyskaną 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 $null
wartością $null
, uzyskasz wartość. W przypadku użycia w ocenie, gdy wartość jest wymagana, wartość jest zawsze $null
wartoś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 pustą tablicę wewnątrz tablicy, nie jest ona liczone jako element. Liczba to 0
.
Jeśli traktujesz wyliczenie o wartości null, takie jak kolekcja, jest ona pusta.
W przypadku przekazania wyliczonej wartości null do parametru funkcji, który nie jest silnie wpisany, program PowerShell domyślnie przekształca wyliczaną wartość null do $null
wartości. Oznacza to, że wewnątrz funkcji wartość jest 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. Wartość można przekazać potokiem $null
, ale nie wyliczaną wartością 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ść null z możliwością wyliczania 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 wyliczenie wartości null. 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 $result
elementu . 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ć.