Alles wat u wilde weten over $null

PowerShell lijkt $null vaak eenvoudig, maar kent veel nuances. Laten we eens kijken, zodat $null u weet wat er gebeurt wanneer u onverwacht een waarde tegen komt $null .

Notitie

De oorspronkelijke versie van dit artikel is te vinden in de blog geschreven door @KevinMarquette. Het PowerShell-team bedankt Voor het delen van deze inhoud met ons. Bekijk zijn blog op PowerShellExplained.com.

Wat is NULL?

U kunt NULL zien 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 waarvoor een waarde is vereist en fouten worden gegenereerd 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 ter vergelijking en deze gebruiken als een plaatshouder 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

Steeds wanneer u probeert een variabele te gebruiken die u niet hebt initialiseerd, is de waarde $null. Dit is een van de meest voorkomende manieren waarop waarden $null in uw code worden gesluisd.

PS> $null -eq $undefinedVariable
True

Als u de naam van een variabele onjuist typt, wordt deze door PowerShell als een andere variabele en wordt de waarde .$null

De andere manier waarop u waarden $null vindt, 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 weer te geven.

In tekenreeksen

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

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

Dit is een van de redenen dat ik graag haakjes rond variabelen plaats wanneer ik ze in logboekberichten gebruik. Het is nog belangrijker om de randen van uw variabele waarden te identificeren wanneer de waarde aan het einde van de tekenreeks staat.

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 opleveren. Soms wordt $null geëvalueerd naar en 0 op andere momenten wordt het hele resultaat .$null Hier is een voorbeeld met vermenigvuldiging dat 0 geeft $null of afhankelijk is van de volgorde van de waarden.

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 daadwerkelijk nullis, krijgt u deze foutmelding: 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 toegang probeert te krijgen tot een element dat zich niet in de verzameling, 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 sub-eigenschap van een object dat niet de opgegeven eigenschap heeft, $null krijgt u een waarde zoals u zou doen voor een niet-gedefinieerde variabele. Het maakt in dit geval niet uit of de variabele $null of een daadwerkelijk object is.

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 $null een object, wordt 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 zin zie, You cannot call a method on a null-valued expression zijn het eerste wat ik zoek plaatsen waar ik een methode aanroep voor een variabele zonder eerst te controleren op $null.

Controleren op $null

Het is u misschien opgevallen dat ik de $null altijd aan de linkerkant plaats bij het controleren op $null in mijn voorbeelden. Dit is opzettelijk en geaccepteerd als een PowerShell-best practice. Er zijn enkele scenario's waarbij het plaatsen ervan aan de rechterkant 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 het eerste geëvalueerd naar $true en is ons bericht The array is $null. De trap hier is dat het mogelijk is om een te $value maken waarmee ze beide kunnen zijn $false

$value = @( $null )

In dit geval is de $value een matrix die een bevat $null. De -eq controleert elke waarde in de matrix en retourneert de $null die is gematcht. Dit wordt geëvalueerd als $false. De -ne retourneert alles wat niet overeen komt $null en in dit geval zijn er geen resultaten (dit resulteert ook in $false). Geen van beide is $true ondanks dat het lijkt alsof een van deze zou moeten zijn.

We kunnen niet alleen een waarde maken waardoor beide worden geëvalueerd naar $false. Het is ook mogelijk om een waarde te maken waarbij ze beide worden geëvalueerd als $true. Mathok Jessen (@IISResetMe) heeft een goed bericht waarin dat scenario wordt besmeed.

PSScriptAnalyzer en VSCode

De module PSScriptAnalyzer 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 aangegeven of geïdentificeerd als een probleem in uw script.

Eenvoudig indien controle

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

if ( $value )
{
    Do-Something
}

Als de waarde is $null, wordt dit geëvalueerd als $false. Dit is eenvoudig te lezen, maar let erop dat het precies zoekt naar wat u verwacht. Ik lees die regel code als:

Als $value een waarde heeft.

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

Als $value niet of $null of 0 $false of of een is empty string

Hier is een vollediger voorbeeld van die instructie.

if ( $null -ne $value -and
        $value -ne 0 -and
        $value -ne '' -and
        $value -ne $false )
{
    Do-Something
}

Het is prima om een basiscontrole if te gebruiken, zolang u weet dat die andere waarden tellen als $false en niet alleen dat een variabele een waarde heeft.

Ik heb een paar dagen geleden met dit probleem te maken gehad bij het herfactoreren van code. Er is een basiscontrole van eigenschappen zoals deze.

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

Ik wilde alleen een waarde toewijzen aan de object-eigenschap als deze bestond. In de meeste gevallen had het oorspronkelijke object een waarde die in de $true instructie zou worden if geëvalueerd. Maar ik heb een probleem waarbij de waarde af en toe niet werd ingesteld. Ik heb de code debuggen en geconstateerd dat het object de eigenschap had, maar dat het een lege tekenreekswaarde was. Dit heeft voorkomen dat deze ooit wordt bijgewerkt met de vorige logica. Daarom heb ik een juiste controle $null toegevoegd en alles werkte.

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

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

$null. Tellen

Als u toegang probeert te krijgen tot een eigenschap van 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 waarde $null hebt, is de count 0. Deze speciale eigenschap wordt toegevoegd door PowerShell.

[PSCustomObject] Tellen

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

Als u dit voorbeeld 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"
}

Lege null

Er is één speciaal type dat $null anders werkt dan het andere. Ik noem het de $null lege, maar het is eigenlijk een System.Management.Automation.Internal.AutomationNull. Deze lege $null waarde krijgt u 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 deze vergelijkt met $null, krijgt u een $null waarde. Bij gebruik in een evaluatie waarbij een waarde vereist is, is de waarde altijd $null. Maar als u deze in een matrix plaats, wordt deze op dezelfde manier 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 waarde bevat $null en de ervan count is 1. Maar als u een leeg resultaat in een matrix zet, wordt het niet als een item geteld. Het aantal is 0.

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

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

Pijplijn

De belangrijkste plaats waar u het verschil ziet, is wanneer u de pijplijn gebruikt. U kunt een waarde doorseen $null , 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 in uw logica.

Ofwel eerst controleren op $null

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

foreach

Een van mijn favoriete functies van foreach is dat deze niet wordt besloemd over een $null verzameling.

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

Hierdoor hoeft ik de verzameling niet $null te controleren voordat ik deze opsnoem. Als u een verzameling waarden hebt $null , kan de $node nog steeds zijn $null.

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

Waardetypen

Technisch gezien kunnen alleen verwijzingstypen zijn $null. Maar PowerShell is zeer variabel en maakt het mogelijk om variabelen van elk type te zijn. Als u besluit een waardetype sterk te typen, mag dit niet zijn $null. PowerShell wordt voor $null veel typen ge converteert naar een standaardwaarde.

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 van hebben $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 getypeerd waarden in functieparameters is heel gebruikelijk. Over het algemeen leren we de typen van onze parameters te definiëren, zelfs als we de typen andere variabelen in onze scripts niet definiëren. Mogelijk hebt u al een aantal sterk getypeerd variabelen in uw functies en realiseert u deze niet eens.

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

Zodra u het type van de parameter in stelt als een string, mag de waarde nooit zijn $null. Het is gebruikelijk om te controleren of een waarde is $null om te zien of de gebruiker een waarde heeft opgegeven of niet.

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

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

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

$PSBoundParameters bevat alleen de parameters die zijn opgegeven toen de functie werd aangeroepen. U kunt ook de methode gebruiken ContainsKey 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 $null een lege tekenreeks op hetzelfde moment is.

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

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

Wanneer ik $null controleer

Ik ben een verdedigingsscript. Steeds wanneer ik een functie aanroep en deze aan een variabele toewijst, controleer ik deze op $null.

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

Ik geef veel de voorkeur aan het gebruik if van of foreach boven het gebruik van try/catch. Begrijp me niet verkeerd, ik gebruik nog steeds try/catch veel. Maar als ik kan testen op een foutvoorwaarde of een lege set resultaten, kan ik mijn uitzonderingsafhandeling toestaan voor echte uitzonderingen.

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

Scenario met geen resultaten

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

Initialiseren voor $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 wanneer ik een foreach-lus voer, definieert u alle waarden die ik gebruik.

Hier is een scenario dat u beter wilt bekijken. Het is een voorbeeld van een bug die ik eerder heb moeten afsbugden.

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 lege retourneert $null. Als er een fout is opgetreden, wordt deze in een logboek op logboeken weergegeven. Vervolgens controleren we of er een geldig resultaat is voordat we het verwerken.

De fout die in deze code wordt verborgen, is Get-Something wanneer een uitzondering west en geen waarde toewijst aan $result. Dit mislukt vóór de toewijzing, zodat we niet eens toewijzen $null aan de $result variabele. $result bevat nog steeds de vorige geldige $result van andere iteraties. Update-Something om in dit voorbeeld meerdere keren uit te voeren op hetzelfde object.

Ik stel in $result op $null rechts binnen de foreach-lus voordat ik deze gebruik om dit probleem te verhelpen.

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

Bereikproblemen

Dit helpt ook het bereik van problemen te beperken. In dat voorbeeld wijzen we in een $result lus waarden toe aan . Maar omdat PowerShell toestaat dat variabele waarden van buiten de functie binnen het bereik van de huidige functie vallen, vermindert het initialiseren van deze waarden in uw functie bugs die op die manier kunnen worden geïntroduceerd.

Een niet-gegenialiseerde variabele in uw functie is $null niet 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 voorbeeld neem Do-something en de lus verwijder, zou ik uiteindelijk iets zien dat lijkt op dit voorbeeld:

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 met de aanroep Get-Something van een uitzondering wordt aanroepen, vindt mijn $null controle de van $result Invoke-Something. Het initialiseren van de waarde in uw functie vermindert dit probleem.

Het benoemen van variabelen is moeilijk en het is gebruikelijk dat een auteur dezelfde variabelenamen gebruikt in meerdere functies. Ik weet dat ik $node,$result,$data altijd gebruik. Het zou dus heel eenvoudig zijn om waarden uit verschillende scopes weer te geven op plaatsen waar dat niet zou moeten zijn.

Uitvoer omleiden naar $null

Ik heb het over de waarden $null voor dit hele artikel, maar het onderwerp is niet volledig als ik het niet heb over het omleiden van uitvoer naar $null. Er zijn momenten waarop u opdrachten hebt die informatie of objecten uitvoeren die u wilt onderdrukken. Dat doet u door de uitvoer $null om te leiden naar .

Out-Null

De Out-Null 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 voor hetzelfde effect als het gebruik van Out-Null.

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

Omdat $null een constante waarde is, kunt u deze nooit overschrijven. Ik vind de manier waarop deze er in mijn code uitziet niet goed uit, maar het werkt vaak sneller dan Out-Null.

Omleiden naar $null

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

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

Als u met opdrachtregeluitvoerbare bestanden te maken hebt die worden uitgevoerd op de verschillende streams. U kunt alle uitvoerstromen als de volgende $null omleiden naar:

git status *> $null

Samenvatting

Ik heb hier veel aan de hand van behandeld en ik weet dat dit artikel gefragmenteerder is dan de meeste van mijn diepgaande informatie. Dat komt doordat $null waarden op veel verschillende plaatsen in PowerShell kunnen worden pop-up en alle nuances specifiek zijn voor waar u ze vindt. Ik hoop dat u dit kunt voorkomen met een beter $null begrip van en een beter begrip van de meer verborgen scenario's waar u tegen aan kunt lopen.