Tutto quello che c'è da sapere su $null
$null
di PowerShell spesso sembra semplice ma presenta molte sfumature. Diamo un'occhiata a $null
in modo da sapere cosa accade quando ci si imbatte in modo imprevisto in un valore $null
.
Nota
La versione originale di questo articolo è apparsa sul blog scritto da @KevinMarquette. Il team di PowerShell ringrazia Kevin per averne condiviso il contenuto. È 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 le viene assegnato un valore o un oggetto. Questo può essere importante perché sono disponibili alcuni comandi che richiedono un valore e generano errori se il valore è NULL.
PowerShell $null
$null
è una variabile automatica in PowerShell utilizzata per rappresentare NULL. È possibile assegnarla a variabili, utilizzarla nei confronti e utilizzarla come segnaposto per NULL in una raccolta.
PowerShell considera $null
come un oggetto con valore NULL. Questo comportamento è diverso rispetto a quello che si può prevedere se si è abituati a usare un altro linguaggio.
Esempi di $null
Quando si tenta di usare una variabile che non è stata inizializzata, il valore è $null
. Questo è uno dei modi più comuni in cui i valori $null
si intrufolano nel codice.
PS> $null -eq $undefinedVariable
True
Se si digita il nome di una variabile in modo scorretto, PowerShell la considera come una variabile diversa con valore $null
.
Un altro motivo per cui si incontrano valori $null
è quando provengono da altri comandi che non forniscono alcun risultato.
PS> function Get-Nothing {}
PS> $value = Get-Nothing
PS> $null -eq $value
True
Conseguenze di $null
I valori $null
influiscono sul codice in modo diverso a seconda della posizione in cui compaiono.
Nelle stringhe
Se si usa $null
in una stringa, è un valore vuoto (o stringa vuota).
PS> $value = $null
PS> Write-Output "The value is $value"
The value is
Questo è uno dei motivi per cui può essere utile inserire parentesi quadre tra le variabili quando vengono usate nei messaggi di log. È ancora più importante identificare gli estremi 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 è facile individuare stringhe vuote e valori $null
.
Nell’equazione numerica
Quando un valore $null
viene usato in un'equazione numerica, i risultati non sono validi se non restituiscono un errore. In alcuni casi $null
restituisce 0
e altre volte rende l'intero risultato $null
.
Di seguito è riportato un esempio con una moltiplicazione che restituisce 0 o $null
in base all'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 eseguire l'indicizzazione in una raccolta 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 che non è presente nella raccolta, si ottiene un risultato $null
.
$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 valore $null
come per una variabile non definita. Non è importante 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 di un metodo su un oggetto $null
genera un 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 viene visualizzata la frase You cannot call a method on a null-valued expression
la prima cosa che si cerca sono i punti in cui si chiama un metodo su una variabile senza prima verificare $null
.
Ricerca di $null
È possibile che si sia notato che $null
viene sempre posizionato a sinistra quando si verificano $null
negli esempi. Questo comportamento è intenzionale e accettato come procedura consigliata per PowerShell. Esistono alcuni scenari in cui il posizionamento a destra non fornisce il risultato previsto.
Guardiamo l'esempio seguente e proviamo 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
. Il problema qui è che è possibile creare un $value
che consente a entrambi di essere $false
$value = @( $null )
In questo caso, $value
è una matrice che contiene un $null
. -eq
controlla ogni valore nella matrice e restituisce il $null
corrispondente. Restituisce $false
. -ne
restituisce tutti gli elementi che non corrispondono a $null
e in questo caso non sono presenti risultati (questo restituisce anche $false
). Nessuno dei due è $true
anche se sembra che uno di essi debba esserlo.
Non solo è possibile creare un valore che fa in modo che entrambi restituiscano $false
, ma è anche possibile creare un valore in cui entrambi restituiscono $true
. Mathias Jessen (@IISResetMe) ha un buon post che si immerge in questo scenario.
PSScriptAnalyzer e VSCode
Il modulo PSScriptAnalyzer dispone di 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 di PSScriptAnalyser, evidenzia o identifica questo come un problema nello script.
Semplice controllo If
Un modo comune per verificare la presenza di un valore non $null consiste nell'utilizzare una semplice istruzione if()
senza il confronto.
if ( $value )
{
Do-Something
}
Se il valore è $null
, viene restituito $false
. Questa operazione è facile da leggere, ma è opportuno prestare attenzione a cercare esattamente ciò che ci si aspetta. Questa riga di codice è stata letta come:
Se
$value
dispone di un valore.
Ma non è tutto qui. Questa riga dice effettivamente:
Se
$value
non$null
è o0
o$false
o 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 accettabile usare un controllo di if
di base purché si ricordi che gli altri valori vengono conteggiati come $false
e non solo che una variabile ha un valore.
L’autore ha riscontrato questo problema durante il refactoring del codice alcuni giorni fa. Presentava un controllo delle proprietà di base simile al seguente.
if ( $object.property )
{
$object.property = $value
}
Si desiderava assegnare un valore alla proprietà dell'oggetto solo se esisteva. Nella maggior parte dei casi, l'oggetto originale aveva un valore che restituiva $true
nell'istruzione if
. Tuttavia, si è verificato un problema per cui il valore talvolta non era 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 era possibile aggiornarlo con la logica precedente. Quindi, è stato aggiunto un controllo $null
appropriato e ha funzionato.
if ( $null -ne $object.property )
{
$object.property = $value
}
Si tratta di piccoli bug, come questi, difficili da individuare e che inducono controllare in modo aggressivo i valori per $null
.
$null.Count
Se si tenta di accedere a una proprietà su un valore $null
, anche la proprietà è $null
. La proprietà count
costituisce l'unica eccezione a questa regola.
PS> $value = $null
PS> $value.count
0
Quando si dispone di un valore $null
, allora count
è 0
. Questa proprietà speciale viene aggiunta da PowerShell.
[PSCustomObject] Count
Quasi tutti gli oggetti in PowerShell hanno la proprietà Count. Un'eccezione importante è [PSCustomObject]
in Windows PowerShell 5.1 (questo problema è stato risolto in PowerShell 6.0). Non dispone di una proprietà Count, pertanto si ottiene un valore $null
se si tenta di usarlo. Lo sottolineiamo in modo che l’utente non provi a usare .Count
anziché un controllo di $null
.
L'esecuzione di questo esempio in Windows PowerShell 5.1 e PowerShell 6.0 fornisce risultati diversi.
$value = [PSCustomObject]@{Name='MyObject'}
if ( $value.count -eq 1 )
{
"We have a value"
}
Enumerabile null
Esiste 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 viene confrontato con $null
, si ottiene un valore di $null
. Quando viene usato in una valutazione in cui è richiesto un valore, il valore è sempre $null
. Tuttavia, se si inserisce l'oggetto all'interno di una matrice, questa viene considerato 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 disporre di una matrice che contiene un valore $null
e il 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 valore Null enumerabile a un parametro di funzione che non è fortemente tipizzato, PowerShell comegue l'enumerabile null in un $null
valore per impostazione predefinita. Ciò significa che all'interno della funzione il valore viene considerato come $null
anziché il tipo System.Management.Automation.Internal.AutomationNull .
Pipeline
Il punto principale in cui si verifica 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 di tutto $null
- Filtrare i valori null sulla pipeline (
... | Where {$null -ne $_} | ...
) - Gestire nella funzione della pipeline
foreach
Una delle caratteristiche che preferisco di foreach
è il fatto che non esegue l'enumerazione di una raccolta di $null
.
foreach ( $node in $null )
{
#skipped
}
In questo modo si evita di dover controllare $null
nella raccolta prima di enumerarla. Se si dispone di una raccolta di valori di $null
, è ancora possibile che $node
sia $null
.
Foreach ha iniziato a funzionare in questo modo con PowerShell 3.0. Se si utilizza una versione precedente, questo non accade. Si tratta di una delle modifiche importanti da tenere presente quando si esegue il back-porting del codice per la compatibilità 2.0.
Tipi valore
Tecnicamente, solo i tipi di riferimento possono essere $null
. Ma PowerShell è molto generoso e consente alle variabili di essere di qualsiasi tipo. Se si decide di tipizzare fortemente un tipo di valore, questo 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 errore Cannot convert null to type
.
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 di funzione
L'uso di un valore fortemente tipizzato nei parametri della funzione è molto comune. In genere impariamo a definire i tipi di parametri anche se tendiamo a non definire i tipi di altre variabili negli script. È possibile che le funzioni presentino già alcune variabili fortemente tipizzate e che non ce ne si renda neanche conto.
function Do-Something
{
param(
[String] $Value
)
}
Non appena si imposta il tipo di parametro come string
, il valore non può mai essere $null
. È comune verificare se un valore è $null
per verificare se l'utente ha fornito un valore o meno.
if ( $null -ne $Value ){...}
$Value
è una stringa vuota ''
se non viene specificato alcun valore. In alternativa, usare la variabile automatica $PSBoundParameters.Value
.
if ( $null -ne $PSBoundParameters.Value ){...}
$PSBoundParameters
contiene solo i parametri specificati quando è stata chiamata la funzione.
È anche possibile usare il metodo ContainsKey
per verificare la proprietà.
if ( $PSBoundParameters.ContainsKey('Value') ){...}
IsNotNullOrEmpty
Se il valore è una stringa, è possibile usare una funzione di stringa statica per verificare se il valore è $null
o una stringa vuota nello stesso momento.
if ( -not [string]::IsNullOrEmpty( $value ) ){...}
Mi trovo spesso a usare questo approccio quando so che il tipo di valore deve essere una stringa.
Quando si esegue il controllo $null
All’autore dell’articolo piace andare sul sicuro. Ogni volta che si chiama una funzione e la si assegna a una variabile, si esegue la ricerca di $null
.
$userList = Get-ADUser kevmar
if ($null -ne $userList){...}
Si preferisce usare if
o foreach
rispetto a try/catch
. try/catch
viene comunque usato spesso. Tuttavia, se è possibile verificare la presenza di una condizione di errore o di un set di risultati vuoto, è possibile consentire la gestione delle eccezioni per le eccezioni true.
Inoltre si tende a controllare $null
prima di indicizzare un valore o chiamare metodi su un oggetto. Queste due azioni hanno esito negativo per un oggetto $null
, quindi è importante convalidarle per prime. Questi scenari sono già stati affrontati in precedenza in questo post.
Scenario senza risultati
È importante tenere presente che funzioni e comandi diversi gestiscono lo scenario senza risultati in modo diverso. Molti comandi di PowerShell restituiscono il valore Null enumerabile e un errore nel flusso di errori. Altri, invece, generano eccezioni o forniscono un oggetto di stato. È comunque l’utente a dover sapere in che modo i comandi usati gestiscono gli scenari senza risultati e errori.
Inizializzazione a $null
Un'abitudine che è possibile sviluppare è quella di eseguire l'inizializzazione di tutte le variabili prima di usarle. Questa operazione è necessaria in altri linguaggi. Nella parte superiore della funzione o quando si immette un ciclo foreach, si definiscono tutti i valori che si stanno usando.
Ecco uno scenario che è utile esaminare attentamente. Si tratta di un esempio di un bug che l’autore ha dovuto risolvere 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. Verifichiamo quindi di avere ottenuto un risultato valido prima di elaborarlo.
Il bug nascosto in questo codice si verifica quando Get-Something
genera un'eccezione e non assegna un valore a $result
. Si verifica un errore prima dell'assegnazione, quindi non viene nemmeno assegnata $null
alla variabile $result
. $result
contiene ancora i $result
validi precedenti di altre iterazioni.
Update-Something
per eseguire più volte sullo stesso oggetto in questo esempio.
Ho impostato $result
su $null
all'interno del ciclo foreach prima di usarlo per attenuare il problema.
foreach ( $node in 1..6 )
{
$result = $null
try
{
...
Problemi di ambito
Questo consente anche di attenuare i problemi di ambito. In questo esempio, i valori vengono assegnati a $result
più volte in un ciclo. Tuttavia, poiché PowerShell consente ai valori variabili dall'esterno della funzione di inserirsi nell'ambito della funzione corrente, l'inizializzazione all'interno della funzione attenua i bug che possono essere introdotti in questo modo.
Una variabile non inizializzata nella funzione non è $null
se è impostata su un valore in un ambito padre.
L'ambito padre può essere un'altra funzione che chiama la funzione e usa gli stessi nomi di variabile.
Se prendessimo lo stesso esempio Do-something
ed eliminassimo il ciclo, otterremmo 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, il controllo $null
trova $result
da Invoke-Something
. L'inizializzazione del valore all'interno della funzione attenua questo problema.
L’assegnazione di un nome alle variabili è difficile ed è frequente che un autore usi gli stessi nomi di variabile in più funzioni. L’autore dell’articolo, per esempio, usa sempre $node
,$result
,$data
. È quindi molto facile che i valori di ambiti diversi appaiano in posizioni in cui non dovrebbero essere.
Reindirizzamento dell'output a $null
Abbiamo parlato dei valori $null
in tutto articolo, ma l'argomento non è completo se non parliamo del reindirizzamento dell'output a $null
. A volte abbiamo comandi che restituiscono informazioni o oggetti che desideriamo 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
Assegnazione a $null
È possibile assegnare i risultati di un comando a $null
per ottenere lo stesso effetto dell'uso di Out-Null
.
$null = New-Item -Type Directory -Path $path
Poiché $null
è un valore costante, non è mai possibile sovrascriverlo. Non è particolarmente bello il modo in cui viene visualizzato nel codice, ma spesso viene eseguito 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 usano file eseguibili da riga di comando che restituiscono flussi diversi, è possibile reindirizzare tutti i flussi di output a $null
come segue:
git status *> $null
Riepilogo
Abbiamo parlato di molti argomenti in questo articolo e si tratta di un articolo più frammentato rispetto alla maggior parte delle solite analisi approfondite. Ciò è dovuto al fatto che i valori $null
possono apparire in molte posizioni diverse in PowerShell e le sfumature sono specifiche per la posizione in cui si trovano. Ci auguriamo che abbiate ottenuto una migliore comprensione di $null
e una consapevolezza degli scenari più oscuri che potreste incontrare.