Allt du ville veta om $null

PowerShell $null verkar ofta vara enkelt, men det har många nyanser. Låt oss ta en närmare titt så $null att du vet vad som händer när du oväntat stöter på ett $null värde.

Anteckning

Den ursprungliga versionen av den här artikeln visades på bloggen som skrivits av @KevinMarquette. PowerShell-teamet tackar Kevin för att han delade det här innehållet med oss. Kolla in hans blogg på PowerShellExplained.com.

Vad är NULL?

Du kan betrakta NULL som ett okänt eller tomt värde. En variabel är NULL tills du tilldelar ett värde eller ett objekt till den. Detta kan vara viktigt eftersom det finns vissa kommandon som kräver ett värde och genererar fel om värdet är NULL.

PowerShell-$null

$null är en automatisk variabel i PowerShell som används för att representera NULL. Du kan tilldela den till variabler, använda den i jämförelser och använda den som platshållare för NULL i en samling.

PowerShell behandlar $null som ett objekt med värdet NULL. Detta skiljer sig från vad du kan förvänta dig om du kommer från ett annat språk.

Exempel på $null

Varje gång du försöker använda en variabel som du inte har initierat är $nullvärdet . Det här är ett av de vanligaste sätten som $null värden smyger in i din kod.

PS> $null -eq $undefinedVariable
True

Om du råkar feltypa ett variabelnamn ser PowerShell det som en annan variabel och värdet är $null.

Det andra sättet du hittar $null värden på är när de kommer från andra kommandon som inte ger dig några resultat.

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

Effekten av $null

$null -värden påverkar koden på olika sätt beroende på var de visas.

I strängar

Om du använder $null i en sträng är det ett tomt värde (eller en tom sträng).

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

Detta är en av anledningarna till att jag gillar att placera hakparenteser runt variabler när du använder dem i loggmeddelanden. Det är ännu viktigare att identifiera kanterna på dina variabelvärden när värdet är i slutet av strängen.

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

Det gör det enkelt att hitta tomma strängar och $null värden.

I numerisk ekvation

När ett $null värde används i en numerisk ekvation är resultatet ogiltigt om de inte ger något fel. $null Ibland utvärderas till 0 och andra gånger gör det hela resultatet $null. Här är ett exempel med multiplikation som ger 0 eller $null beroende på värdenas ordning.

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

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

I stället för en samling

Med en samling kan du använda ett index för att komma åt värden. Om du försöker indexera till en samling som faktiskt nullär får du det här felet: 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

Om du har en samling men försöker komma åt ett element som inte finns i samlingen får du ett $null resultat.

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

I stället för ett objekt

Om du försöker komma åt en egenskap eller underegenskap för ett objekt som inte har den angivna egenskapen får du ett $null värde som du skulle ha för en odefinierad variabel. Det spelar ingen roll om variabeln är $null eller ett faktiskt objekt i det här fallet.

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

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

Metod för ett nullvärdesuttryck

Om du anropar en -metod för ett $null -objekt utlöser en 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

När jag ser frasen You cannot call a method on a null-valued expression är det första jag letar efter platser där jag anropar en metod på en variabel utan att först kontrollera den för $null.

Söker efter $null

Du kanske har märkt att jag alltid placera till $null vänster när du söker efter $null i mina exempel. Detta är avsiktligt och accepteras som bästa praxis för PowerShell. Det finns vissa scenarier där du inte får det förväntade resultatet om du placerar det till höger.

Titta på nästa exempel och försök förutsäga resultatet:

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

Om jag inte definierar $valueutvärderas det första till $true och vårt meddelande är The array is $null. Fällan här är att det är möjligt att skapa en $value som gör att båda kan $false

$value = @( $null )

I det här fallet $value är en matris som innehåller en $null. -eq Kontrollerar varje värde i matrisen och returnerar $null det som matchas. Detta utvärderas till $false. -ne returnerar allt som inte matchar $null och i det här fallet finns det inga resultat (detta utvärderas också till $false). Ingen av dem är $true det trots att det ser ut som om en av dem borde vara det.

Vi kan inte bara skapa ett värde som gör att båda utvärderas till $false, det är möjligt att skapa ett värde där båda utvärderas till $true. Mathias Jessen (@IISResetMe) har ett bra inlägg som dyker in i det scenariot.

PSScriptAnalyzer och VSCode

PSScriptAnalyzer-modulen har en regel som söker efter det här problemet med namnet PSPossibleIncorrectComparisonWithNull.

PS> Invoke-ScriptAnalyzer ./myscript.ps1

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

Eftersom VS Code även använder PSScriptAnalyser-reglerna markeras eller identifieras detta som ett problem i skriptet.

Enkel om-kontroll

Ett vanligt sätt att söka efter ett icke-$null värde är att använda en enkel if() instruktion utan jämförelsen.

if ( $value )
{
    Do-Something
}

Om värdet är $nullutvärderas detta till $false. Det här är lätt att läsa, men var försiktig så att den letar efter exakt det du förväntar dig att den ska leta efter. Jag läste kodraden som:

Om $value har ett värde.

Men det är inte hela historien. Den linjen säger faktiskt:

Om $value inte $null är eller 0 eller $false en tom sträng eller en tom matris.

Här är ett mer komplett exempel på den instruktionen.

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
}

Det är helt ok att använda en grundläggande if kontroll så länge du kommer ihåg att de andra värdena räknas som $false och inte bara att en variabel har ett värde.

Jag stötte på det här problemet när jag omstrukturerade kod för några dagar sedan. Den hade en grundläggande egenskapskontroll som denna.

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

Jag ville bara tilldela ett värde till objektegenskapen om den fanns. I de flesta fall hade det ursprungliga objektet ett värde som skulle utvärderas till $true i -instruktionen if . Men jag stötte på ett problem där värdet ibland inte sattes. Jag felsökde koden och upptäckte att objektet hade egenskapen men att det var ett tomt strängvärde. Detta förhindrade att den någonsin uppdaterades med den tidigare logiken. Så jag lade till en ordentlig $null kontroll och allt fungerade.

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

Det är små buggar som dessa som är svåra att upptäcka och få mig att aggressivt kontrollera värden för $null.

$null. Räkna

Om du försöker komma åt en egenskap för ett $null värde är egenskapen också $null. Egenskapen count är undantaget till den här regeln.

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

När du har ett $null värde är .count0 Den här specialegenskapen läggs till av PowerShell.

[PSCustomObject] Räkna

Nästan alla objekt i PowerShell har egenskapen count. Ett viktigt undantag är [PSCustomObject] i Windows PowerShell 5.1 (detta är åtgärdat i PowerShell 6.0). Den har ingen count-egenskap, så du får ett $null värde om du försöker använda den. Jag kallar det här så att du inte försöker använda .Count i stället för en $null check.

Om du kör det här exemplet på Windows PowerShell 5.1 och PowerShell 6.0 får du olika resultat.

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

Tomt null

Det finns en särskild typ av $null som fungerar annorlunda än de andra. Jag kommer att kalla det tomt $null men det är verkligen ett System.Management.Automation.Internal.AutomationNull. Den här tomma $null är den du får som resultat av en funktion eller ett skriptblock som inte returnerar något (ett void-resultat).

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

Om du jämför det med $nullfår du ett $null värde. När det används i en utvärdering där ett värde krävs är värdet alltid $null. Men om du placerar den i en matris behandlas den på samma sätt som en tom matris.

PS> $containempty = @( @() )
PS> $containnothing = @($nothing)
PS> $containnull = @($null)

PS> $containempty.count
0
PS> $containnothing.count
0
PS> $containnull.count
1

Du kan ha en matris som innehåller ett $null värde och dess count är 1. Men om du placerar ett tomt resultat i en matris räknas det inte som ett objekt. Antalet är 0.

Om du behandlar det tomma $null som en samling är det tomt.

Om du skickar ett tomt värde till en funktionsparameter som inte är starkt skrivet, tvingar PowerShell värdet nothing till ett $null värde som standard. Det innebär att i funktionen behandlas värdet som $null i stället för typen System.Management.Automation.Internal.AutomationNull .

Pipeline

Den primära platsen som du ser skillnaden är när du använder pipelinen. Du kan skicka ett $null värde men inte ett tomt $null värde.

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

Beroende på din kod bör du ta hänsyn till $null i din logik.

Kontrollera antingen först $null

  • Filtrera bort null i pipelinen (... | Where {$null -ne $_} | ...)
  • Hantera det i pipelinefunktionen

foreach

En av mina favoritfunktioner foreach i är att den inte räknas upp över en $null samling.

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

Detta gör att $null jag inte behöver kontrollera samlingen innan jag räknar upp den. Om du har en samling $null värden $node kan fortfarande vara $null.

Foreach började arbeta på det här sättet med PowerShell 3.0. Om du råkar ha en äldre version är detta inte fallet. Detta är en av de viktiga ändringarna att vara medveten om när bakåtporteringskod för 2.0-kompatibilitet.

Värdetyper

Tekniskt sett kan endast referenstyper vara $null. Men PowerShell är mycket generöst och gör att variabler kan vara av valfri typ. Om du väljer att ange en värdetyp starkt kan den inte vara $null. PowerShell konverterar $null till ett standardvärde för många typer.

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

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

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

Det finns vissa typer som inte har en giltig konvertering från $null. Dessa typer genererar ett Cannot convert null to type fel.

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

Funktionsparametrar

Det är mycket vanligt att använda ett starkt typifierat värde i funktionsparametrar. Vi lär oss vanligtvis att definiera typerna av våra parametrar även om vi tenderar att inte definiera typerna av andra variabler i våra skript. Du kanske redan har några starkt typifierade variabler i dina funktioner och inte ens inser det.

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

Så snart du anger parametertypen som en stringkan värdet aldrig vara $null. Det är vanligt att kontrollera om ett värde är $null att se om användaren har angett ett värde eller inte.

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

$Value är en tom sträng '' när inget värde anges. Använd den automatiska variabeln $PSBoundParameters.Value i stället.

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

$PSBoundParameters innehåller bara de parametrar som angavs när funktionen anropades. Du kan också använda ContainsKey metoden för att söka efter egenskapen .

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

IsNotNullOrEmpty

Om värdet är en sträng kan du använda en statisk strängfunktion för att kontrollera om värdet är $null eller en tom sträng samtidigt.

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

Jag använder ofta detta när jag vet att värdetypen ska vara en sträng.

När jag $null kontrollera

Jag är en defensiv scripter. Varje gång jag anropar en funktion och tilldelar den till en variabel söker jag efter $null.

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

Jag föredrar att använda if eller foreach över använda try/catch. Missförstå mig inte, jag använder try/catch fortfarande mycket. Men om jag kan testa för ett feltillstånd eller en tom uppsättning resultat kan jag tillåta att min undantagshantering är för sanna undantag.

Jag brukar också söka efter $null innan jag indexeras till ett värde eller anropar metoder på ett objekt. Dessa två åtgärder misslyckas för ett $null objekt, så jag tycker att det är viktigt att verifiera dem först. Jag har redan gått igenom dessa scenarier tidigare i det här inlägget.

Inget resultatscenario

Det är viktigt att känna till att olika funktioner och kommandon hanterar scenariot utan resultat på olika sätt. Många PowerShell-kommandon returnerar det tomma $null och ett fel i felströmmen. Men andra genererar undantag eller ger dig ett statusobjekt. Det är fortfarande upp till dig att veta hur de kommandon som du använder hanterar scenarier utan resultat och fel.

Initierar till $null

En vana som jag har plockat upp är att initiera alla mina variabler innan jag använder dem. Du måste göra detta på andra språk. Överst i min funktion eller när jag anger en foreach-loop definierar jag alla värden som jag använder.

Här är ett scenario som jag vill att du ska ta en närmare titt på. Det är ett exempel på en bugg som jag var tvungen att jaga tidigare.

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

Förväntningen här är att Get-Something returnerar antingen ett resultat eller ett tomt $null. Om det uppstår ett fel loggar vi det. Sedan kontrollerar vi att vi har fått ett giltigt resultat innan vi bearbetar det.

Felet som döljs i den här koden är när Get-Something utlöser ett undantag och inte tilldelar ett värde till $result. Det misslyckas före tilldelningen, så vi tilldelar $null inte ens till variabeln $result . $result innehåller fortfarande det tidigare giltiga $result från andra iterationer. Update-Something för att köra flera gånger på samma objekt i det här exemplet.

Jag ställer in $result$null rätt i foreach-loopen innan jag använder den för att åtgärda det här problemet.

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

Omfångsproblem

Detta hjälper också till att åtgärda omfångsproblem. I det exemplet tilldelar vi värden till $result om och om för en loop. Men eftersom PowerShell tillåter att variabelvärden utanför funktionen förblöder i omfånget för den aktuella funktionen, minimerar initieringen av dem i funktionen buggar som kan introduceras på det sättet.

En oinitierad variabel i funktionen är inte $null om den är inställd på ett värde i ett överordnat omfång. Det överordnade omfånget kan vara en annan funktion som anropar funktionen och använder samma variabelnamn.

Om jag tar samma Do-something exempel och ta bort slingan, skulle jag hamna med något som ser ut som i det här exemplet:

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

Om anropet till Get-Something genererar ett undantag hittar $result min $null kontroll från Invoke-Something. Det här problemet åtgärdas genom att värdet initieras i funktionen.

Det är svårt att namnge variabler och det är vanligt att en författare använder samma variabelnamn i flera funktioner. Jag vet att jag använder $nodehela$data$result tiden. Det skulle därför vara mycket enkelt för värden från olika omfång att visas på platser där de inte ska vara.

Omdirigera utdata till $null

Jag har talat om $null värden för hela den här artikeln men ämnet är inte komplett om jag inte nämnde omdirigera utdata till $null. Det finns tillfällen när du har kommandon som matar ut information eller objekt som du vill ignorera. Omdirigering av utdata till $null gör det.

Out-Null

Kommandot Out-Null är det inbyggda sättet att omdirigera pipelinedata till $null.

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

Tilldela till $null

Du kan tilldela resultatet av ett kommando till $null för samma effekt som med hjälp av Out-Null.

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

Eftersom $null är ett konstant värde kan du aldrig skriva över det. Jag gillar inte hur det ser ut i min kod men det presterar ofta snabbare än Out-Null.

Omdirigera till $null

Du kan också använda omdirigeringsoperatorn för att skicka utdata till $null.

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

Om du hanterar körbara kommandoradskörningar som matas ut på de olika strömmarna. Du kan omdirigera alla utdataströmmar så $null här:

git status *> $null

Sammanfattning

Jag täckte mycket mark på den här och jag vet att den här artikeln är mer fragmenterad än de flesta av mina djupdykningar. Det beror på att $null värden kan dyka upp på många olika platser i PowerShell och alla nyanser är specifika för var du hittar det. Jag hoppas att du lämnar detta med en bättre förståelse för $null och en medvetenhet om de mer dunkla scenarier du kan stöta på.