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 $null
vä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 $value
utvä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 $null
utvä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 eller0
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 $null
få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 string
kan 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å.