Condividi tramite


Tutto quello che volevi sapere su $null

PowerShell $null sembra spesso semplice, ma presenta molte sfumature. Esaminiamo da vicino $null per sapere cosa succede quando ci si imbatte in modo imprevisto in un valore $null.

Annotazioni

La versione originale di questo articolo è apparsa sul blog scritto da @KevinMarquette. Il team di PowerShell ringrazia Kevin per aver condiviso questo contenuto con noi. È possibile visitare il suo blog all'indirizzo PowerShellExplained.com.

Che cos'è NULL?

È possibile considerare NULL come un valore sconosciuto o vuoto. Una variabile è NULL finché non si assegna un valore o un oggetto. Questo può essere importante perché esistono alcuni comandi che richiedono un valore e generano errori se il valore è NULL.

PowerShell $null

$null è una variabile automatica in PowerShell usata per rappresentare NULL. È possibile assegnarlo alle variabili, usarlo nei confronti e usarlo come segnaposto per NULL in una raccolta.

PowerShell considera $null come oggetto un valore NULL. Questo è diverso da quello che ci si può aspettare se si proviene da un'altra lingua.

Esempi di $null

Ogni volta che si tenta di usare una variabile non inizializzata, il valore è $null. Si tratta di uno dei modi più comuni in cui i valori $null si insinuano nel codice.

PS> $null -eq $undefinedVariable
True

Se si digita erroneamente un nome di variabile, PowerShell lo vede come variabile diversa e il valore è $null.

L'altro modo in cui si trovano $null i valori è quando provengono da altri comandi che non forniscono risultati.

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

Impatto di $null

$null i valori influiscono sul codice in modo diverso a seconda della posizione in cui vengono visualizzati.

In stringhe

Se si usa $null in una stringa, si tratta di un valore vuoto (o di una stringa vuota).

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

Questo è uno dei motivi per cui mi piace posizionare le parentesi intorno alle variabili quando vengono usate nei messaggi di log. È ancora più importante identificare i bordi dei valori delle variabili quando il valore si trova alla fine della stringa.

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

In questo modo, stringhe vuote e $null valori sono facili da individuare.

In un'equazione numerica

Quando un $null valore viene usato in un'equazione numerica, i risultati non sono validi se non generano un errore. A volte $null si valuta a 0 e altre volte rende l'intero risultato $null. Di seguito è riportato un esempio con moltiplicazione che fornisce 0 o $null a seconda dell'ordine dei valori.

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

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

Al posto di una raccolta

Una raccolta consente di usare un indice per accedere ai valori. Se si tenta di indicizzare in una raccolta che è effettivamente null, viene visualizzato questo errore: 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

Se si dispone di una raccolta ma si tenta di accedere a un elemento non incluso nella raccolta, si ottiene un $null risultato.

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

Al posto di un oggetto

Se si tenta di accedere a una proprietà o a una proprietà secondaria di un oggetto che non dispone della proprietà specificata, si ottiene un $null valore simile a quello di una variabile non definita. Non importa se la variabile è $null o un oggetto effettivo in questo caso.

PS> $null -eq $undefined.Some.Fake.Property
True

PS> $date = Get-Date
PS> $null -eq $date.Some.Fake.Property
True

Metodo su un'espressione con valori Null

La chiamata a un metodo su un oggetto $null provoca un'eccezione 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

Ogni volta che vedo la frase You cannot call a method on a null-valued expression , la prima cosa che cerco sono i luoghi in cui si chiama un metodo su una variabile senza prima controllarlo per $null.

Verifica della presenza di $null

Potresti aver notato che ho sempre posizionato $null a sinistra quando controllo per $null nei miei esempi. Questa operazione è intenzionale e accettata come procedura consigliata di PowerShell. Esistono alcuni scenari in cui posizionarlo a destra non fornisce il risultato previsto.

Esaminare questo esempio successivo e provare a stimare i risultati:

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

Se non si definisce $value, il primo restituisce $true e il messaggio è The array is $null. La trappola qui è che è possibile creare un $value che consente a entrambi di essere $false

$value = @( $null )

In questo caso, $value è una matrice che contiene un oggetto $null. Verifica -eq ogni valore nella matrice e restituisce l'oggetto $null che corrisponde. Il risultato è $false. Restituisce tutto ciò che non corrisponde a -ne, quindi in questo caso non ci sono risultati (viene valutato anche come $null). Nessuno dei due è $true anche se sembra che uno di essi dovrebbe esserlo.

Non solo è possibile creare un valore che fa sì che entrambi i valori siano valutati come $false, ma è anche possibile creare un valore in cui entrambi siano valutati come $true. Mathias Jessen (@IISResetMe) ha un buon post che si immerge in questo scenario.

PSScriptAnalyzer e VS Code

Il modulo PSScriptAnalyzer ha una regola che verifica la presenza di questo problema denominato PSPossibleIncorrectComparisonWithNull.

PS> Invoke-ScriptAnalyzer ./myscript.ps1

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

Poiché VS Code usa anche le regole PSScriptAnalyser, evidenzia o lo identifica come un problema nello script.

Controllo semplice se

Un modo comune in cui gli utenti controllano la presenza di un valore non $null consiste nell'usare una semplice if() istruzione senza il confronto.

if ( $value )
{
    Do-Something
}

Se il valore è $null, restituisce $false. Questo è facile da leggere, ma prestare attenzione che stia cercando esattamente quello che ci si aspetta di cercare. Ho letto la riga di codice come:

Se $value ha un valore.

Ma non è tutta la storia. Quella riga sta effettivamente dicendo:

Se $value non è $null, 0, $false, una stringa vuota o una matrice vuota.

Di seguito è riportato un esempio più completo di tale istruzione.

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
}

È perfettamente ok usare un controllo di base if purché si ricordino questi altri valori come $false e non solo che una variabile ha un valore.

Si è verificato questo problema durante il refactoring di codice qualche giorno fa. Aveva un controllo di proprietà di base come questo.

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

Volevo assegnare un valore alla proprietà dell'oggetto solo se esistesse. Nella maggior parte dei casi, l'oggetto originale ha un valore che restituisce $true nell'istruzione if . Ma si è verificato un problema a causa del quale il valore non veniva occasionalmente impostato. È stato eseguito il debug del codice e si è scoperto che l'oggetto aveva la proprietà, ma era un valore stringa vuoto. In questo modo non è mai stato possibile aggiornarlo con la logica precedente. Quindi ho aggiunto un controllo corretto $null e tutto ha funzionato.

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

Sono piccoli bug come questi che sono difficili da individuare e mi fanno controllare in modo aggressivo i valori per $null.

$null. Contare

Se tenti di accedere a una proprietà di un valore $null, anche questa proprietà è $null. La Count proprietà è l'eccezione a questa regola.

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

Quando si ha un valore di $null, l'oggetto Count è 0. Questa proprietà speciale viene aggiunta da PowerShell.

[PSCustomObject] Contare

Quasi tutti gli oggetti in PowerShell dispongono di tale Count proprietà. Un'eccezione importante è rappresentata da [pscustomobject] in Windows PowerShell 5.1 (questo problema è risolto in PowerShell 6.0). Non ha una Count proprietà, quindi ricevi un $null valore se provi a usarlo. Lo sottolineo qui in modo che non provi a usare Count invece di un controllo $null.

L'esecuzione di questo esempio in Windows PowerShell 5.1 e PowerShell 6.0 offre risultati diversi.

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

Enumerabile nullo

C'è un tipo speciale di $null che agisce in modo diverso rispetto agli altri. Lo chiamerò null enumerabile, ma è davvero un System.Management.Automation.Internal.AutomationNull. Questo valore Null enumerabile è quello che si ottiene come risultato di una funzione o di un blocco di script che non restituisce nulla (risultato void).

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

Se si confronta con $null, si ottiene un $null valore. Se usato in una valutazione in cui è necessario un valore, il valore è sempre $null. Ma se la si inserisce all'interno di una matrice, viene trattata come una matrice vuota.

PS> $containEmpty = @( @() )
PS> $containNothing = @($nothing)
PS> $containNull = @($null)

PS> $containEmpty.Count
0
PS> $containNothing.Count
0
PS> $containNull.Count
1

È possibile avere una matrice che contiene un $null valore e il relativo Count è 1. Tuttavia, se si inserisce una matrice vuota all'interno di una matrice, non viene conteggiata come elemento. Il conteggio è 0.

Se si considera null enumerabile come una raccolta, il valore è vuoto.

Se si passa un enumerabile null a un parametro di funzione che non è fortemente tipizzato, PowerShell trasforma l'enumerabile null in un valore $null per impostazione predefinita. Ciò significa che all'interno della funzione il valore viene considerato come $null anziché il tipo System.Management.Automation.Internal.AutomationNull .

Oleodotto

La posizione principale in cui si vede la differenza è quando si usa la pipeline. È possibile inviare tramite pipe un $null valore ma non un valore Null enumerabile.

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

A seconda del codice, è necessario tenere conto di $null nella logica.

Verificare prima la presenza $null di

  • Filtra i valori null nella pipeline (... | where {$null -ne $_} | ...)
  • Gestiscilo nella funzione della pipeline

foreach

Una delle mie caratteristiche preferite di foreach è che non enumera su una $null raccolta.

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

In questo modo è possibile evitare di dover $null controllare la raccolta prima di enumerarla. Se si dispone di una raccolta di $null valori, l'oggetto $node può comunque essere $null.

Il foreach ha iniziato a lavorare in questo modo con PowerShell 3.0. Se si usa una versione precedente, non è così. Si tratta di una delle modifiche importanti da essere consapevoli di quando si fa back-porting del codice per la compatibilità alla 2.0.

Tipi di valori

Tecnicamente, solo i tipi riferimento possono essere $null. Tuttavia, PowerShell è molto generoso e consente alle variabili di essere qualsiasi tipo. Se si decide di digitare fortemente un tipo di valore, non può essere $null. PowerShell converte $null in un valore predefinito per molti tipi.

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

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

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

Esistono alcuni tipi che non dispongono di una conversione valida da $null. Questi tipi generano un Cannot convert null to type errore.

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

Parametri della funzione

L'uso di valori fortemente tipizzati nei parametri della funzione è molto comune. In genere si apprenderà a definire i tipi dei parametri anche se si tende a non definire i tipi di altre variabili negli script. Potrebbero essere già presenti alcune variabili fortemente tipate nelle funzioni e non lo si rende neanche conto.

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

Non appena si imposta il tipo del parametro come , stringil valore non può mai essere $null. È comune verificare se un valore è $null verificare se l'utente ha fornito o meno un valore.

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

$Value è una stringa '' vuota quando non viene specificato alcun valore. Usare invece la variabile $PSBoundParameters.Value automatica.

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

$PSBoundParameters contiene solo i parametri specificati quando è stata chiamata la funzione . È anche possibile utilizzare il ContainsKey metodo per verificare la proprietà .

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

IsNotNullOrEmpty

Se il valore è una stringa, è possibile usare una funzione stringa statica per verificare se il valore è $null o una stringa vuota contemporaneamente.

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

Mi trovo a usare questo spesso quando so che il tipo di valore deve essere una stringa.

Quando effettuo un controllo $null

Sono uno scripter difensivo. Ogni volta che chiamo una funzione e la assegno a una variabile, la controllo per $null.

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

Preferisco usare if o foreach più usando try/catch. Non mi sbaglio, uso try/catch ancora molto. Tuttavia, se è possibile verificare la presenza di una condizione di errore o un set vuoto di risultati, è possibile consentire la gestione delle eccezioni per le eccezioni vere.

Tendo anche a controllare $null prima di indicizzare un valore o invocare metodi su un oggetto. Queste due azioni hanno esito negativo per un $null oggetto, quindi trovo importante convalidarle per prime. Questi scenari sono già stati illustrati in precedenza in questo post.

Nessun scenario di risultati

È importante sapere che funzioni e comandi diversi gestiscono in modo diverso lo scenario senza risultati. Molti comandi di PowerShell restituiscono il valore Null enumerabile e un errore nel flusso di errori. Ma altri lanciano eccezioni o forniscono un oggetto di stato. È comunque necessario conoscere il modo in cui i comandi utilizzati gestiscono l'assenza di risultati e gli scenari di errore.

Inizializzazione a $null

Un'abitudine che ho preso è inizializzare tutte le mie variabili prima di usarle. È necessario eseguire questa operazione in altre lingue. In cima alla mia funzione o quando entro in un foreach ciclo, definisco tutti i valori che sto utilizzando.

Ecco uno scenario che voglio che tu prenda un'occhiata da vicino. È un esempio di bug che ho dovuto inseguire in passato.

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

L'aspettativa è che Get-Something restituisce un risultato o un valore Null enumerabile. Se si verifica un errore, viene registrato. Verificare quindi di avere ottenuto un risultato valido prima di elaborarlo.

Il bug nascosto in questo codice è quando Get-Something genera un'eccezione e non assegna un valore a $result. L'operazione ha esito negativo prima dell'assegnazione, quindi non viene nemmeno assegnata $null alla $result variabile. $result contiene ancora il precedente valido $result dalle altre iterazioni. Update-Something per eseguire più volte sullo stesso oggetto in questo esempio.

Ho impostato $result a $null direttamente all'interno del foreach ciclo prima di usarlo per attenuare questo problema.

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

Problemi di ambito

Ciò consente anche di attenuare i problemi di ambito. In questo esempio, i valori vengono assegnati ripetutamente a $result in un ciclo. Tuttavia, poiché PowerShell consente ai valori delle variabili esterni alla funzione di influire nell'ambito della funzione corrente, inizializzarli all'interno della funzione riduce i bug che possono essere introdotti in questo modo.

Una variabile non inizializzata nella tua funzione non è $null se viene impostata su un valore in un ambito genitore. L'ambito padre potrebbe essere un'altra funzione che chiama la tua funzione e usa gli stessi nomi di variabile.

Se si accetta lo stesso Do-something esempio e si rimuove il ciclo, si ottiene un risultato simile all'esempio seguente:

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

Se la chiamata a Get-Something genera un'eccezione, allora il mio controllo $null trova il $result da Invoke-Something. L'inizializzazione del valore all'interno della funzione attenua questo problema.

La denominazione delle variabili è difficile ed è comune che un autore usi gli stessi nomi di variabile in più funzioni. So che uso $node,$result$data tutto il tempo. Quindi sarebbe molto facile che i valori di ambiti diversi vengano visualizzati in posizioni in cui non dovrebbero essere.

Reindirizzare l'output a $null

Ho parlato di $null valori per l'intero articolo, ma l'argomento non è completo se non menzionassi il reindirizzamento dell'output a $null. In alcuni casi si dispone di comandi che generano informazioni o oggetti che si desidera eliminare. Il reindirizzamento dell'output a $null esegue questa operazione.

Out-Null

Il comando Out-Null è il modo predefinito per reindirizzare i dati della pipeline a $null.

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

Assegna a $null

È possibile assegnare i risultati di un comando a $null per lo stesso effetto dell'uso Out-Nulldi .

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

Poiché $null è un valore costante, non è mai possibile sovrascriverlo. Non mi piace il modo in cui appare nel codice, ma spesso esegue più velocemente di Out-Null.

Reindirizzamento a $null

È anche possibile usare l'operatore di reindirizzamento per inviare l'output a $null.

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

Se si riguardano file eseguibili da riga di comando che generano output sui diversi flussi. È possibile reindirizzare tutti i flussi di output in questo modo: $null

git status *> $null

Riassunto

Ho trattato molti argomenti in questo caso e so che questo articolo è più frammentato della maggior parte delle mie analisi approfondite. Ciò è dovuto al fatto che $null i valori possono essere visualizzati in molte posizioni diverse in PowerShell e tutte le sfumature sono specifiche della posizione in cui si trova. Spero che ci si allontana da questo con una migliore comprensione di $null e una consapevolezza degli scenari più oscuri che si potrebbero affrontare.