Alles wat u wilde weten over $null

PowerShell $null lijkt vaak eenvoudig te zijn, maar het heeft veel nuances. Laten we eens kijken $null , zodat u weet wat er gebeurt wanneer u onverwacht een $null waarde tegenkomt.

Notitie

De oorspronkelijke versie van dit artikel verscheen op het blog geschreven door @KevinMarquette. Het PowerShell-team bedankt Kevin voor het delen van deze inhoud met ons. Bekijk zijn blog op PowerShellExplained.com.

Wat is NULL?

U kunt NULL beschouwen als een onbekende of lege waarde. Een variabele is NULL totdat u er een waarde of een object aan toewijst. Dit kan belangrijk zijn omdat er enkele opdrachten zijn die een waarde vereisen en fouten genereren als de waarde NULL is.

PowerShell-$null

$null is een automatische variabele in PowerShell die wordt gebruikt om NULL weer te geven. U kunt deze toewijzen aan variabelen, deze gebruiken in vergelijkingen en gebruiken als tijdelijke aanduiding voor NULL in een verzameling.

PowerShell wordt behandeld $null als een object met de waarde NULL. Dit is anders dan wat u kunt verwachten als u uit een andere taal komt.

Voorbeelden van $null

Wanneer u probeert een variabele te gebruiken die u niet hebt geïnitialiseerd, is $nullde waarde. Dit is een van de meest voorkomende manieren waarop $null waarden in uw code worden ingevoerd.

PS> $null -eq $undefinedVariable
True

Als u een naam van een variabele verkeerd typt, ziet PowerShell deze als een andere variabele en de waarde is $null.

De andere manier waarop u waarden vindt $null , is wanneer ze afkomstig zijn van andere opdrachten die u geen resultaten geven.

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

Impact van $null

$null waarden hebben een andere invloed op uw code, afhankelijk van waar ze worden weergegeven.

In tekenreeksen

Als u in een tekenreeks gebruikt $null , is dit een lege waarde (of lege tekenreeks).

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

Dit is een van de redenen waarom ik graag vierkante haken rond variabelen plaats bij gebruik in logboekberichten. Het is nog belangrijker om de randen van uw variabelewaarden te identificeren wanneer de waarde zich aan het einde van de tekenreeks bevindt.

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

Hierdoor zijn lege tekenreeksen en $null waarden gemakkelijk te herkennen.

In numerieke vergelijking

Wanneer een $null waarde wordt gebruikt in een numerieke vergelijking, zijn uw resultaten ongeldig als ze geen fout geven. Soms worden de $null resultaten en 0 andere keren geëvalueerd, waardoor het hele resultaat wordt.$null Hier volgt een voorbeeld met vermenigvuldigen die 0 of $null afhankelijk van de volgorde van de waarden geeft.

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

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

In plaats van een verzameling

Met een verzameling kunt u een index gebruiken om toegang te krijgen tot waarden. Als u probeert te indexeren in een verzameling die eigenlijk nullis, krijgt u deze fout: 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

Als u een verzameling hebt maar probeert toegang te krijgen tot een element dat zich niet in de verzameling bevindt, krijgt u een $null resultaat.

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

In plaats van een object

Als u toegang probeert te krijgen tot een eigenschap of subeigenschap van een object dat niet over de opgegeven eigenschap beschikt, krijgt u een $null waarde zoals u zou doen voor een niet-gedefinieerde variabele. Het maakt niet uit of de variabele in dit geval of een werkelijk object is $null .

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

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

Methode voor een expressie met null-waarden

Als u een methode aanroept voor een $null object, wordt er een 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

Wanneer ik de woordgroep You cannot call a method on a null-valued expression zie, dan is het eerste waar ik zoek naar plaatsen waar ik een methode aanroep voor een variabele zonder eerst te $nullcontroleren op .

Controleren op $null

Misschien heb je gemerkt dat ik altijd links $null plaats bij het controleren $null op in mijn voorbeelden. Dit is opzettelijk en geaccepteerd als best practice voor PowerShell. Er zijn enkele scenario's waarbij het plaatsen aan de rechterkant u niet het verwachte resultaat geeft.

Bekijk dit volgende voorbeeld en probeer de resultaten te voorspellen:

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

Als ik niet definieer $value, wordt de eerste geëvalueerd en $true ons bericht is The array is $null. De trap hier is dat het mogelijk is om een $value te maken waarmee beide kunnen worden $false

$value = @( $null )

In dit geval is het $value een matrix die een $null. De -eq functie controleert elke waarde in de matrix en retourneert de $null overeenkomende waarde. Dit resulteert in $false. De -ne retourneert alles wat niet overeenkomt $null en in dit geval zijn er geen resultaten (dit resulteert $falseook in ). Geen van beide is $true , ook al lijkt het erop dat een van hen zou moeten zijn.

We kunnen niet alleen een waarde maken die beide evalueert, $falsehet is mogelijk om een waarde te maken waarin ze beide evalueren $true. Hoeft Jessen (@IISResetMe) een goed bericht te hebben dat in dat scenario duikt.

PSScriptAnalyzer en VSCode

De PSScriptAnalyzer-module heeft een regel die controleert op dit probleem met de naam PSPossibleIncorrectComparisonWithNull.

PS> Invoke-ScriptAnalyzer ./myscript.ps1

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

Omdat VS Code ook gebruikmaakt van de PSScriptAnalyser-regels, wordt dit ook gemarkeerd of geïdentificeerd als een probleem in uw script.

Eenvoudig als u dit controleert

Een algemene manier waarop mensen op een niet-$null waarde controleren, is door een eenvoudige if() instructie te gebruiken zonder de vergelijking.

if ( $value )
{
    Do-Something
}

Als de waarde is $null, resulteert dit in $false. Dit is gemakkelijk te lezen, maar wees voorzichtig met het zoeken naar precies wat u verwacht. Ik heb die coderegel gelezen als:

Als $value er een waarde is.

Maar dat is niet het hele verhaal. Die regel zegt eigenlijk:

Als $value dit niet $null of 0 een $false lege tekenreeks of een lege matrix is.

Hier volgt een uitgebreider voorbeeld van die instructie.

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
}

Het is prima om een eenvoudige if controle te gebruiken zolang u die andere waarden onthoudt als $false en niet alleen dat een variabele een waarde heeft.

Ik heb dit probleem gehad bij het herstructureren van code een paar dagen geleden. Het had een eenvoudige eigenschapscontrole als deze.

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

Ik wilde alleen een waarde toewijzen aan de objecteigenschap als deze bestond. In de meeste gevallen had het oorspronkelijke object een waarde die in de if instructie zou worden geëvalueerd$true. Maar er is een probleem opgetreden waarbij de waarde af en toe niet werd ingesteld. Ik heb de code opgespoord en vastgesteld dat het object de eigenschap had, maar dat het een lege tekenreekswaarde was. Hierdoor is het niet meer bijgewerkt met de vorige logica. Dus ik heb een goede $null controle toegevoegd en alles werkte.

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

Het zijn kleine bugs zoals deze die moeilijk te herkennen zijn en mij agressief waarden laten controleren op $null.

$null. Tellen

Als u probeert toegang te krijgen tot een eigenschap op een $null waarde, is de eigenschap ook $null. De count eigenschap is de uitzondering op deze regel.

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

Wanneer u een $null waarde hebt, is 0dat count . Deze speciale eigenschap wordt toegevoegd door PowerShell.

[PSCustomObject] Tellen

Bijna alle objecten in PowerShell hebben die count-eigenschap. Een belangrijke uitzondering is de [PSCustomObject] in Windows PowerShell 5.1 (dit is opgelost in PowerShell 6.0). Deze heeft geen eigenschap count, dus u krijgt een $null waarde als u deze probeert te gebruiken. Ik noem dit hier, zodat je niet probeert te gebruiken .Count in plaats van een $null cheque.

Als u dit voorbeeld uitvoert op Windows PowerShell 5.1 en PowerShell 6.0, krijgt u verschillende resultaten.

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

Leeg null

Er is één speciaal type $null dat anders werkt dan de andere. Ik noem het de lege $null , maar het is echt een System.Management.Automation.Internal.AutomationNull. Deze lege $null is degene die u krijgt als resultaat van een functie of scriptblok dat niets retourneert (een ongeldig resultaat).

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

Als u het vergelijkt met $null, krijgt u een $null waarde. Wanneer deze wordt gebruikt in een evaluatie waarbij een waarde vereist is, is de waarde altijd $null. Maar als u deze in een matrix plaatst, wordt deze hetzelfde behandeld als een lege matrix.

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

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

U kunt een matrix hebben die één $null waarde bevat en de waarde count is 1. Maar als u een leeg resultaat in een matrix plaatst, wordt het niet geteld als een item. Het aantal is 0.

Als u de lege $null waarden als een verzameling behandelt, is deze leeg.

Als u een lege waarde doorgeeft aan een functieparameter die niet sterk is getypt, wordt de nietswaarde standaard door PowerShell omgezet in een $null waarde. Dit betekent dat de waarde binnen de functie wordt behandeld als $null in plaats van het type System.Management.Automation.Internal.AutomationNull .

Pijplijn

De primaire plaats waar u het verschil ziet, is wanneer u de pijplijn gebruikt. U kunt een $null waarde doorsluisen, maar geen lege $null waarde.

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

Afhankelijk van uw code moet u rekening houden met de $null logica.

Controleer eerst op $null

  • Null filteren op de pijplijn (... | Where {$null -ne $_} | ...)
  • Afhandelen in de pijplijnfunctie

foreach

Een van mijn favoriete functies foreach is dat het niet opsommen over een $null verzameling.

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

Dit bespaart me dat ik de verzameling moet $null controleren voordat ik deze opsommen. Als u een verzameling $null waarden hebt, kan dit $node nog steeds zijn $null.

De foreach begon op deze manier te werken met PowerShell 3.0. Als u een oudere versie gebruikt, is dit niet het geval. Dit is een van de belangrijke wijzigingen waar u rekening mee moet houden bij back-porting code voor compatibiliteit met 2.0.

Waardetypen

Technisch gezien kunnen alleen referentietypen worden $nullgebruikt. Maar PowerShell is erg gul en maakt het mogelijk om variabelen elk type te zijn. Als u besluit een waardetype sterk te typen, kan dit niet zijn $null. PowerShell wordt $null geconverteerd naar een standaardwaarde voor veel typen.

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

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

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

Er zijn enkele typen die geen geldige conversie hebben van $null. Deze typen genereren een Cannot convert null to type fout.

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

Functieparameters

Het gebruik van sterk getypte waarden in functieparameters is zeer gebruikelijk. Over het algemeen leren we de typen van onze parameters te definiëren, zelfs als we meestal niet de typen andere variabelen in onze scripts definiëren. Mogelijk hebt u al een aantal sterk getypte variabelen in uw functies en realiseert u deze niet eens.

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

Zodra u het type van de parameter als een stringinstelt, kan de waarde nooit zijn $null. Het is gebruikelijk om te controleren of een waarde is $null om te zien of de gebruiker al dan niet een waarde heeft opgegeven.

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

$Value is een lege tekenreeks '' wanneer er geen waarde wordt opgegeven. Gebruik in plaats daarvan de automatische variabele $PSBoundParameters.Value .

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

$PSBoundParameters bevat alleen de parameters die zijn opgegeven toen de functie werd aangeroepen. U kunt ook de ContainsKey methode gebruiken om te controleren op de eigenschap.

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

IsNotNullOrEmpty

Als de waarde een tekenreeks is, kunt u een statische tekenreeksfunctie gebruiken om te controleren of de waarde of een lege tekenreeks tegelijk is $null .

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

Ik gebruik dit vaak als ik weet dat het waardetype een tekenreeks moet zijn.

Wanneer ik $null controleren

Ik ben een defensieve scripter. Wanneer ik een functie aanroep en toewijs aan een variabele, controleer ik deze op $null.

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

Ik geef veel de voorkeur aan of ifforeach over het gebruik.try/catch Begrijp me niet verkeerd, ik gebruik try/catch nog veel. Maar als ik kan testen op een foutvoorwaarde of een lege set resultaten, kan ik toestaan dat mijn uitzonderingsafhandeling waar uitzonderingen betreft.

Ik controleer ook op $null voordat ik indexeer in een waarde of methoden voor een object aanroep. Deze twee acties mislukken voor een $null object, dus ik vind het belangrijk om ze eerst te valideren. Ik heb deze scenario's al eerder in dit bericht behandeld.

Geen resultatenscenario

Het is belangrijk om te weten dat verschillende functies en opdrachten het scenario zonder resultaten anders verwerken. Veel PowerShell-opdrachten retourneren de lege $null en een fout in de foutstroom. Maar anderen gooien uitzonderingen of geven u een statusobject. Het is nog steeds aan u om te weten hoe de opdrachten die u gebruikt, omgaan met de geen resultaten en foutscenario's.

Initialiseren naar $null

Een gewoonte die ik heb opgepikt, is het initialiseren van al mijn variabelen voordat ik ze gebruik. U moet dit doen in andere talen. Boven aan mijn functie of als ik een foreach-lus invoer, definieer ik alle waarden die ik gebruik.

Hier volgt een scenario dat ik wil dat je het goed bekijkt. Het is een voorbeeld van een bug die ik eerder moest achtervolgen.

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

De verwachting hier is dat Get-Something een resultaat of een leeg resultaat retourneert $null. Als er een fout optreedt, registreren we deze. Vervolgens controleren we of we een geldig resultaat hebben gekregen voordat we het verwerken.

De fout die zich in deze code verbergt, is wanneer Get-Something er een uitzondering wordt gegenereerd en er geen waarde aan $resultwordt toegewezen. Het mislukt voordat de toewijzing wordt toegewezen, zodat we niet eens aan de $result variabele worden toegewezen$null. $result bevat nog steeds de vorige geldige $result uit andere iteraties. Update-Something om meerdere keren op hetzelfde object in dit voorbeeld uit te voeren.

Ik heb ingesteld $result op $null rechts in de foreach-lus voordat ik het gebruik om dit probleem te verhelpen.

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

Bereikproblemen

Dit helpt ook bij het beperken van bereikproblemen. In dat voorbeeld wijzen we waarden toe aan $result over en over in een lus. Maar omdat PowerShell variabele waarden van buiten de functie toestaat om in het bereik van de huidige functie te bloeden, worden deze in uw functie geïnitieerd, waardoor fouten die op die manier kunnen worden geïntroduceerd, worden beperkt.

Een niet-geïnitialiseerde variabele in uw functie is niet $null als deze is ingesteld op een waarde in een bovenliggend bereik. Het bovenliggende bereik kan een andere functie zijn die uw functie aanroept en dezelfde variabelenamen gebruikt.

Als ik hetzelfde Do-something voorbeeld neem en de lus verwijder, zou ik uiteindelijk iets hebben dat er als volgt uitziet:

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

Als de aanroep om een uitzondering genereert Get-Something , vindt mijn $null controle de $result van Invoke-Something. Door de waarde in uw functie te initialiseren, wordt dit probleem verholpen.

Naamgevingsvariabelen zijn moeilijk en het is gebruikelijk dat een auteur dezelfde namen van variabelen in meerdere functies gebruikt. Ik weet dat ik altijd$result gebruik$node.$data Het zou dus heel eenvoudig zijn voor waarden uit verschillende bereiken om te worden weergegeven op plaatsen waar ze niet zouden moeten zijn.

Uitvoer omleiden naar $null

Ik heb het gehad over $null waarden voor dit hele artikel, maar het onderwerp is niet voltooid als ik het niet vermeld om uitvoer omleiden naar $null. Er zijn momenten waarop u opdrachten hebt die informatie of objecten uitvoeren die u wilt onderdrukken. Uitvoer omleiden om dat te $null doen.

Out-Null

De Out-Null opdracht is de ingebouwde manier om pijplijngegevens om te leiden naar $null.

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

Toewijzen aan $null

U kunt de resultaten van een opdracht toewijzen aan $null hetzelfde effect als het gebruik.Out-Null

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

Omdat $null dit een constante waarde is, kunt u deze nooit overschrijven. Ik hou niet van de manier waarop het eruitziet in mijn code, maar het presteert vaak sneller dan Out-Null.

Omleiden naar $null

U kunt ook de omleidingsoperator gebruiken om uitvoer naar $nullte verzenden.

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

Als u te maken hebt met uitvoerbare opdrachtregelbestanden die worden uitgevoerd op de verschillende streams. U kunt alle uitvoerstromen als $null volgt omleiden:

git status *> $null

Samenvatting

Ik heb veel aandacht besteed aan dit artikel en ik weet dat dit artikel meer gefragmenteerd is dan de meeste van mijn diepe duiken. Dat komt doordat $null waarden op veel verschillende plaatsen in PowerShell kunnen verschijnen en alle nuances specifiek zijn voor waar u deze vindt. Ik hoop dat u hieruit wegloopt met een beter begrip van $null en een beter begrip van de meer duistere scenario's die u kunt tegenkomen.