Dela via


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 på $null så att du vet vad som händer när du oväntat stöter på ett $null värde.

Kommentar

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

Vad är NULL?

Du kan se 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 []

Detta gör tomma strängar och $null värden enkla att upptäcka.

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ärdeuttryck

Om du anropar en metod för ett $null objekt skapas 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 $null till vänster när du söker $null efter i mina exempel. Detta är avsiktligt och accepteras som en PowerShell-metod. 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 den 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. Kontrollerar -eq varje värde i matrisen och returnerar det $null 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 även om 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 värde som inte är $null ä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. Detta är lätt att läsa, men var försiktig så att det letar efter exakt vad du förväntar dig att det ska leta efter. Jag läste den 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 det 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 debugged koden och fann att objektet hade egenskapen men det var ett tomt strängvärde. Detta hindrade den från att någonsin uppdateras 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 från den här regeln.

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

När du har ett $null värde är det count 0. 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 ropar ut 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 i 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"
}

Uppräkningsbar null

Det finns en särskild typ av $null som fungerar annorlunda än de andra. Jag kommer att kalla det den uppräkningsbara null men det är verkligen en System.Management.Automation.Internal.AutomationNull. Den här uppräkningsbara null-värdet är den du får som resultat av ett funktions- eller skriptblock som inte returnerar något (ett ogiltigt 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 en tom matris i en matris räknas den inte som ett objekt. Antalet är 0.

Om du behandlar den uppräkningsbara null-värdet som en samling är den tom.

Om du skickar en uppräkningsbar null till en funktionsparameter som inte är starkt skriven, tvingar PowerShell den uppräkningsbara null-värdet 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 uppräkningsbart 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 på pipelinen (... | Where {$null -ne $_} | ...)
  • Hantera den i pipelinefunktionen

foreach

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

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

Detta sparar mig från att $null behöva kontrollera samlingen innan jag räknar upp den. Om du har en samling $null värden kan det $node fortfarande vara $null.

Foreach började fungera 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 vilken typ som helst. Om du väljer att ange en värdetyp starkt kan det 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 skrivet 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 inskrivna 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 detta ofta 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, kontrollerar jag den 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 för 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 veta att olika funktioner och kommandon hanterar scenariot utan resultat på ett annat sätt. Många PowerShell-kommandon returnerar den uppräkningsbara null-värdet och ett fel i felströmmen. Men andra utlöser undantag eller ger dig ett statusobjekt. Det är fortfarande upp till dig att veta hur kommandona du använder hanterar scenarierna utan resultat och fel.

Initiera 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 jag var tvungen att jaga förut.

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äntningarna här är att Get-Something returnerar antingen ett resultat eller en uppräkningsbar null. Om det finns ett fel loggar vi det. Sedan kontrollerar vi att vi har ett giltigt resultat innan vi bearbetar det.

Felet som döljs i den här koden är när Get-Something ett undantag genereras och inte tilldelar ett värde till $result. Det misslyckas före tilldelningen så att vi inte ens tilldelar $null 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 till $null höger 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 minimera omfångsproblem. I det exemplet tilldelar vi värden till $result om och om i en loop. Men eftersom PowerShell tillåter att variabelvärden utanför funktionen blöder in i den aktuella funktionens omfång, minskar initieringen av dem i funktionen buggar som kan introduceras på det sättet.

En ennitialiserad 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 tar bort slingan skulle jag få något som ser ut som 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. När du initierar värdet i funktionen minimeras det här problemet.

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 $node,$result,$data hela 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 då du har kommandon som matar ut information eller objekt som du vill utelämna. Omdirigering av utdata till $null gör det.

Out-Null

Out-Null-kommandot ä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 har att göra med 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 den. Jag hoppas att ni lämnar detta med en bättre förståelse för $null och en medvetenhet om de mer dunkla scenarier som du kan stöta på.