Delen via


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 dat is 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 een waarde of een object eraan 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 $null behandeld als een object met een waarde van NULL. Dit verschilt van 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 weggeluisd.

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 kunt vinden $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 een 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 geëvalueerd en 0 andere keren het hele resultaat $null. Hier volgt een voorbeeld van vermenigvuldigen dat 0 geeft of $null afhankelijk 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 voor toegang tot waarden. Als u probeert te indexeren in een verzameling die in feite 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 die vergelijkbaar is met een niet-gedefinieerde variabele. Het maakt in dit geval niet uit of de variabele of $null een werkelijk 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

Het aanroepen van een methode op een $null object genereert 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 You cannot call a method on a null-valued expression zie, dan is het eerste wat ik zoek naar plaatsen waar ik een methode aanroep op een variabele zonder het $nulleerst te controleren op .

Controleren op $null

U hebt misschien gemerkt dat ik altijd links $null plaats bij het controleren op $null 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 het niet definieer $value, wordt de eerste geëvalueerd $true en ons bericht is The array is $null. De val 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 ook in $false). 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 $false, het is mogelijk om een waarde te maken waarin ze beide evalueren $true. Machtigingen van Jessen (@IISResetMe) hebben een goed bericht 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 de PSScriptAnalyser-regels gebruikt, wordt dit ook gemarkeerd of geïdentificeerd als een probleem in uw script.

Eenvoudig als u dit controleert

Een veelvoorkomende 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 geen $null of 0$false een lege tekenreeks of een lege matrix is.

Hier volgt een completer 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 basiscontrole if te gebruiken zolang u die andere waarden onthoudt als $false en niet alleen dat een variabele een waarde heeft.

Ik heb dit probleem gelopen bij het herstructureren van een paar dagen geleden. Het had een basiseigenschapscontrole 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 evalueren$true. Maar er is een probleem opgetreden waarbij de waarde af en toe niet werd ingesteld. Ik heb fouten in de code opgespoord en vastgesteld dat het object de eigenschap had, maar dat het een lege tekenreekswaarde was. Dit voorkomt dat het ooit wordt 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 toegang probeert 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 0het count . 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). Het heeft geen eigenschap count, dus u krijgt een $null waarde als u deze wilt gebruiken. Ik noem dit hier, zodat je niet probeert te gebruiken .Count in plaats van een $null cheque.

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

Enumerable null

Er is één speciaal type $null dat anders werkt dan de andere. Ik noem het de enumerable null, maar het is echt een System.Management.Automation.Internal.AutomationNull. Deze enumerable 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 ervan is 1. Maar als u een lege matrix in een matrix plaatst, wordt deze niet meegeteld als een item. Het aantal is 0.

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

Als u een enumerable null doorgeeft aan een functieparameter die niet sterk is getypt, wordt de enumerable null 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 opsommingsbare 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.

Controleren op $null eerste

  • 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 werkt op deze manier met PowerShell 3.0. Als u een oudere versie gebruikt, is dit niet het geval. Dit is een van de belangrijke wijzigingen waarmee u rekening moet houden bij het terugzetten van code voor compatibiliteit met 2.0.

Waardetypen

Technisch gezien kunnen alleen referentietypen zijn $null. Maar PowerShell is erg gul en maakt het mogelijk om variabelen elk type te zijn. Als u besluit een waardetype sterk te typen, kan dat niet zijn $null. PowerShell wordt geconverteerd $null 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 erg 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 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 een waarde heeft opgegeven of niet.

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 op hetzelfde moment is $null .

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

Ik vind mezelf dit vaak gebruiken 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 deze toewijs aan een variabele, 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 over het gebruik try/catch. Begrijp me niet verkeerd, ik gebruik try/catch nog steeds veel. Maar als ik kan testen op een foutvoorwaarde of een lege set resultaten, kan ik toestaan dat mijn uitzonderingsafhandeling geldt voor echte uitzonderingen.

Ik controleer $null ook op voordat ik indexeer in een waarde of methoden voor een object aanroept. 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 enumerable null en een fout in de foutstroom. Maar anderen genereren 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 is 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 opsommingsbare null retourneert. Als er een fout optreedt, registreren we deze. Vervolgens controleren we of we een geldig resultaat hebben voordat we het verwerken.

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

Ik ben 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 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ïnitialiseerd, waardoor fouten die op die manier kunnen worden geïntroduceerd, worden beperkt.

Een niet-geïnitialiseerde variabele in uw functie is niet $null 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 eindigen met iets 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 te Get-Something genereren, vindt mijn $null controle de $result van Invoke-Something. Door de waarde in uw functie te initialiseren, wordt dit probleem opgelost.

Naamgevingsvariabelen zijn moeilijk en het is gebruikelijk dat een auteur dezelfde variabelenamen in meerdere functies gebruikt. Ik weet dat ik de$result$data hele tijd gebruik$node. Het zou dus heel eenvoudig zijn voor waarden uit verschillende bereiken om weer te geven op plaatsen waar ze niet mogen 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 omleiden van uitvoer $nullnaar niet vermeld. 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 opdracht 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 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 te $nullverzenden.

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 kunt vinden. Ik hoop dat je hier wegloopt met een beter begrip van $null en een beter begrip van de meer duistere scenario's die je kunt tegenkomen.