Todo lo que le interesa sobre $null
El valor $null
de PowerShell puede parecer simple, pero tiene muchos matices. Echemos un vistazo a $null
para saber lo que ocurre cuando de forma inesperada se encuentra con un valor $null
.
Nota:
La versión original de este artículo apareció en el blog escrito por @KevinMarquette. El equipo de PowerShell agradece a Kevin que comparta este contenido con nosotros. Visite su blog en PowerShellExplained.com.
¿Qué es NULL?
Puede pensar en NULL como un valor desconocido o vacío. Una variable tiene categoría NULL hasta que se le asigna un valor o un objeto. Esto puede ser importante porque hay algunos comandos que requieren un valor y generan errores si el valor es NULL.
$null de PowerShell
$null
es una variable automática de PowerShell que se usa para representar un valor NULL. Puede asignarla a variables y usarla en comparaciones y como marcador de posición para NULL en una recopilación.
PowerShell trata $null
como un objeto con un valor NULL. Si proviene de otro lenguaje, verá que este proceso es diferente.
Ejemplos de $null
Siempre que intente usar una variable que no haya inicializado, el valor es $null
. Esta es una de las formas por las que los valores $null
se cuelan en su código.
PS> $null -eq $undefinedVariable
True
Si escribe incorrectamente un nombre de variable, PowerShell lo percibe como una variable diferente y el valor es $null
.
La otra forma de encontrar valores $null
es cuando proceden de otros comandos que no proporcionan ningún resultado.
PS> function Get-Nothing {}
PS> $value = Get-Nothing
PS> $null -eq $value
True
Repercusión de $null
Los valores $null
afectan al código de manera diferente dependiendo de dónde aparezcan.
En cadenas
Si utiliza $null
en una cadena, entonces es un valor en blanco (o una cadena vacía).
PS> $value = $null
PS> Write-Output "The value is $value"
The value is
Esta es una de las razones por las que me gusta poner corchetes alrededor de las variables cuando se usan en los mensajes de registro. Es incluso más importante identificar los límites de los valores de las variables cuando el valor está al final de la cadena.
PS> $value = $null
PS> Write-Output "The value is [$value]"
The value is []
De este modo, las cadenas vacías y los valores $null
son fáciles de identificar.
En ecuación numérica
Cuando se usa un valor $null
en una ecuación numérica, los resultados no son válidos si no dan un error. A veces, $null
se evalúa como 0
y otras veces hace que todo el resultado sea $null
.
Este es un ejemplo con multiplicación que proporciona 0 o $null
dependiendo del orden de los valores.
PS> $null * 5
PS> $null -eq ( $null * 5 )
True
PS> 5 * $null
0
PS> $null -eq ( 5 * $null )
False
En el lugar de una recopilación
Una recopilación le permite utilizar un índice para tener acceso a los valores. Si intenta indizar en una recopilación que es realmente null
, obtendrá este error: 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
Si tiene una recopilación pero intenta tener acceso a un elemento que no está en la recopilación, obtendrá un resultado $null
.
$array = @( 'one','two','three' )
$null -eq $array[100]
True
En el lugar de un objeto
Si intenta obtener acceso a una propiedad o subpropiedad de un objeto que no tiene la propiedad especificada, obtendrá un valor $null
igual que para una variable no definida. No importa si la variable es $null
o un objeto real en este caso.
PS> $null -eq $undefined.some.fake.property
True
PS> $date = Get-Date
PS> $null -eq $date.some.fake.property
True
Método en una expresión con valores NULL
La llamada a un método en un objeto $null
produce 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
Cada vez que veo la frase You cannot call a method on a null-valued expression
, lo primero que me fijo es los lugares en los que se llama a un método en una variable sin buscar antes $null
en ellos.
Comprobación de $null
Es posible que haya observado que siempre coloco $null
a la izquierda al buscar $null
en mis ejemplos. Lo hago a propósito y se acepta como procedimiento recomendado de PowerShell. Hay algunos escenarios en los que si se coloca a la derecha no se obtiene el resultado esperado.
Vea el ejemplo siguiente e intente predecir los resultados:
if ( $value -eq $null )
{
'The array is $null'
}
if ( $value -ne $null )
{
'The array is not $null'
}
Si no defino $value
, el primero se evalúa como $true
y nuestro mensaje es The array is $null
. La trampa aquí es que es posible crear un valor $value
que permita que ambos sean $false
$value = @( $null )
En este caso, $value
es una matriz que contiene $null
. -eq
comprueba cada valor de la matriz y devuelve el valor $null
que coincida. Se evalúa como $false
. -ne
devuelve todo lo que no coincide con $null
y, en este caso, no hay ningún resultado (esto también se evalúa como $false
). Ninguno de ellos es $true
, aunque parezca que uno de ellos debe serlo.
No solo podemos crear un valor que haga que ambos se evalúen como $false
; es posible crear un valor en el que ambos se evalúen como $true
. Mathias Jessen (@IISResetMe) tiene una excelente publicación en la que se profundiza en este escenario.
PSScriptAnalyzer y VSCode
El módulo PSScriptAnalyzer tiene una regla que busca este problema llamado PSPossibleIncorrectComparisonWithNull
.
PS> Invoke-ScriptAnalyzer ./myscript.ps1
RuleName Message
-------- -------
PSPossibleIncorrectComparisonWithNull $null should be on the left side of equality comparisons.
Dado que VS Code usa también las reglas de PSScriptAnalyser, resalta o identifica esto como un problema en el script.
Comprobación if simple
Una forma común para que los usuarios busquen un valor que no sea $null es usar una instrucción if()
simple sin la comparación.
if ( $value )
{
Do-Something
}
Si el valor es $null
, se evalúa como $false
. Resulta fácil de leer, pero fíjese en que esté buscando exactamente lo que espera que busque. He leído esa línea de código como:
Si
$value
tiene un valor.
Pero eso no es todo. Esa línea está diciendo realmente:
Si
$value
no es$null
,0
o$false
o una cadena o matriz vacía.
Este es un ejemplo más completo de esa instrucción.
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
}
Es absolutamente válido usar una comprobación básica if
siempre que recuerde que esos otros valores cuentan como $false
y no solo que una variable tiene un valor.
Me encontré con este problema al refactorizar código hace unos días. Tenía una comprobación de propiedad básica como esta.
if ( $object.property )
{
$object.property = $value
}
Quería asignar un valor a la propiedad del objeto solo si existiera. En la mayoría de los casos, el objeto original tenía un valor que se evaluaría como $true
en la instrucción if
. Pero se produjo un problema en el que, en ocasiones, el valor no se establecía. Depuré el código y vi que el objeto tenía la propiedad, pero era un valor de cadena en blanco. Esto impidió que se actualizara en algún caso con la lógica anterior. Por eso, agregué una comprobación $null
adecuada y todo funcionó.
if ( $null -ne $object.property )
{
$object.property = $value
}
Son pequeños errores como estos los que son difíciles de detectar y por los que tengo que comprobar de forma agresiva los valores de $null
.
$null.Count
Si intenta obtener acceso a una propiedad en un valor de $null
, la propiedad también es $null
. La propiedad count
es la excepción a esta regla.
PS> $value = $null
PS> $value.count
0
Cuando tiene un valor $null
, count
es 0
. PowerShell agrega esta propiedad especial.
[PSCustomObject] Count
Casi todos los objetos de PowerShell tienen esa propiedad count. Una excepción importante es [PSCustomObject]
en Windows PowerShell 5,.1 (esto se corrigió en PowerShell 6.0). No tiene una propiedad count, por lo que obtiene un valor $null
si intenta usarla. Lo menciono aquí para que no intente usar .Count
en lugar de una comprobación $null
.
La ejecución de este ejemplo en Windows PowerShell 5.1 y PowerShell 6.0 ofrece resultados diferentes.
$value = [PSCustomObject]@{Name='MyObject'}
if ( $value.count -eq 1 )
{
"We have a value"
}
Valor null enumerable
Hay un tipo especial de $null
que actúa de manera diferente que los demás. Voy a llamarlo el valor null enumerable, pero es realmente System.Management.Automation.Internal.AutomationNull.
Este valor null enumerable es el que se obtiene como resultado de una función o un bloque de script que no devuelve nada (resultado nulo).
PS> function Get-Nothing {}
PS> $nothing = Get-Nothing
PS> $null -eq $nothing
True
Si lo compara con $null
, obtiene un valor $null
. Cuando se usa en una evaluación en la que se requiere un valor, el valor siempre es $null
. Pero si lo coloca dentro de una matriz, se trata de la misma forma que una matriz vacía.
PS> $containempty = @( @() )
PS> $containnothing = @($nothing)
PS> $containnull = @($null)
PS> $containempty.count
0
PS> $containnothing.count
0
PS> $containnull.count
1
Puede incluso tener una matriz que contenga un valor $null
y su count
sea 1
. Pero si coloca una matriz vacía dentro de una matriz, no se cuenta como un elemento. El recuento es 0
.
Si trata el valor null enumerable como una colección, está vacío.
Si pasa un valor null enumerable a un parámetro de función que no está fuertemente tipado, PowerShell convierte el valor null enumerable en un valor $null
de manera predeterminada. Esto significa que, dentro de la función, el valor se trata como $null
en lugar del tipo System.Management.Automation.Internal.AutomationNull.
Canalización
Donde se aprecia fundamentalmente la diferencia es en el uso de la canalización. Puede canalizar un valor $null
pero no un valor null enumerable.
PS> $null | ForEach-Object{ Write-Output 'NULL Value' }
'NULL Value'
PS> $nothing | ForEach-Object{ Write-Output 'No Value' }
En función del código, debe tener en cuenta $null
en la lógica.
Compruebe primero $null
.
- Filtre null en la canalización (
... | Where {$null -ne $_} | ...
). - Adminístrelo en la función de canalización.
foreach
Una de mis características favoritas de foreach
es que no se enumera en una recopilación de $null
.
foreach ( $node in $null )
{
#skipped
}
Esto me evita tener que comprobar $null
en la recopilación antes de enumerarla. Si tiene una recopilación de valores $null
, $node
todavía puede ser $null
.
La instrucción foreach comenzó a funcionar de esta manera con PowerShell 3.0. Si tiene una versión anterior, entonces este no es el caso. Se trata de uno de los cambios importantes que se deben tener en cuenta al hacer posible la compatibilidad del código con la versión 2.0.
Tipos de valor
Técnicamente, solo los tipos de referencia pueden ser $null
. Sin embargo, PowerShell es muy generoso y permite que las variables sean de cualquier tipo. Si decide establecer un tipo de valor de forma inflexible, no puede ser $null
.
PowerShell convierte $null
en un valor predeterminado para muchos tipos.
PS> [int]$number = $null
PS> $number
0
PS> [bool]$boolean = $null
PS> $boolean
False
PS> [string]$string = $null
PS> $string -eq ''
True
Hay algunos tipos que no tienen una conversión válida desde $null
. Estos tipos generan un error 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
Parámetros de función
El uso de valores con establecimiento inflexible de tipos en los parámetros de función es muy común. En general, aprendemos a definir los tipos de nuestros parámetros, incluso si tendemos a no definir los tipos de otras variables en nuestros scripts. Es posible que ya tenga algunas variables con establecimiento inflexible de tipos en las funciones y que no se haya dado cuenta.
function Do-Something
{
param(
[String] $Value
)
}
En cuanto establezca el tipo del parámetro como string
, el valor nunca puede ser $null
. Es habitual comprobar si un valor es $null
para ver si el usuario proporcionó un valor o no.
if ( $null -ne $Value ){...}
$Value
es una cadena vacía ''
cuando no se proporciona ningún valor. En su lugar, use la variable automática $PSBoundParameters.Value
.
if ( $null -ne $PSBoundParameters.Value ){...}
$PSBoundParameters
solo contiene los parámetros que se especificaron cuando se llamó a la función.
También puede utilizar el método ContainsKey
para comprobar la propiedad.
if ( $PSBoundParameters.ContainsKey('Value') ){...}
IsNotNullOrEmpty
Si el valor es una cadena, puede usar una función de cadena estática para comprobar si el valor es $null
o una cadena vacía al mismo tiempo.
if ( -not [string]::IsNullOrEmpty( $value ) ){...}
Con frecuencia uso esto cuando sé que el tipo de valor debe ser una cadena.
Cuando compruebo $null
Soy un escritor de scripts conservador. Cada vez que llamo a una función y la asigno a una variable, compruebo si tiene valores $null
.
$userList = Get-ADUser kevmar
if ($null -ne $userList){...}
Prefiero usar if
o foreach
antes que try/catch
. No me entienda mal, sigo utilizando try/catch
mucho. Pero si puedo probar una condición de error o un conjunto vacío de resultados, puedo permitir que mi control de excepciones sea para verdaderas excepciones.
También suelo comprobar $null
antes de indizar en un valor o llamar a métodos en un objeto. Estas dos acciones producen un error en un objeto $null
, por lo que me parece importante validarlas primero. Ya he tratado estos escenarios anteriormente en esta publicación.
Escenario sin resultados
Es importante saber que las distintas funciones y comandos controlan el escenario sin resultados de forma diferente. Muchos comandos de PowerShell devuelven el valor null enumerable y un error en la secuencia de error. Pero otros inician excepciones o proporcionan un objeto de estado. Sigue siendo cosa suya saber de qué modo los comandos que usa aborda los escenarios sin resultados y con errores.
Inicialización en $null
Un hábito que he adquirido es inicializar todas las variables antes de utilizarlas. En otros lenguajes, es obligatorio hacerlo. En la parte superior de mi función o al escribir un bucle foreach, defino todos los valores que estoy usando.
Este es un escenario en el que quiero detenerme un poco. Es un ejemplo de un error que tuve que perseguir antes.
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
}
}
}
Aquí es de esperar que Get-Something
devuelva un resultado o un valor null enumerable. Si se produce un error, lo registramos. A continuación, comprobamos que obtuvimos un resultado válido antes de procesarlo.
El error que se esconde en este código es cuando Get-Something
produce una excepción y no asigna un valor a $result
. El problema ocurre antes de la asignación, por lo que ni siquiera se asigna $null
a la variable $result
. $result
todavía contiene el $result
válido anterior de otras iteraciones.
Update-Something
para ejecutar varias veces en el mismo objeto en este ejemplo.
Establezco $result
en $null
justo dentro del bucle foreach antes de usarlo para mitigar este problema.
foreach ( $node in 1..6 )
{
$result = $null
try
{
...
Problemas de ámbito
Esto también ayuda a mitigar los problemas de ámbito. En ese ejemplo, asignamos valores a $result
una y otra vez en un bucle. Sin embargo, dado que PowerShell permite que los valores de variable de fuera de la función interfieran en el ámbito de la función actual, su inicialización dentro de la función mitiga los errores que se pueden producir de este modo.
Una variable no inicializada en la función no es $null
si está establecida en un valor en un ámbito primario.
El ámbito primario podría ser otra función que llama a la función y usa los mismos nombres de variable.
Si tomo ese mismo ejemplo Do-something
y quito el bucle, terminaría con algo parecido a este ejemplo:
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
}
}
Si la llamada a Get-Something
produce una excepción, la comprobación de $null
encuentra $result
en Invoke-Something
. La inicialización del valor dentro de la función soluciona este problema.
Asignar nombres a las variables es difícil y es habitual que un autor use los mismos nombres de variable en varias funciones. Sé que utilizo $node
,$result
y $data
todo el tiempo. Por lo tanto, es muy fácil que los valores de distintos ámbitos se muestren en lugares donde no deberían estar.
Redirección de la salida a $null
He estado hablando acerca de los valores $null
durante todo este artículo, pero el tema queda incompleto si no se menciona la redirección de la salida a $null
. Hay ocasiones en las que tiene comandos que generan información u objetos que desea suprimir. La redirección de la salida a $null
se ocupa de ello.
Out-Null
El comando Out-Null es la forma integrada de redirigir los datos de canalización a $null
.
New-Item -Type Directory -Path $path | Out-Null
Asignación a $null
Puede asignar los resultados de un comando a $null
para obtener el mismo efecto que usar Out-Null
.
$null = New-Item -Type Directory -Path $path
Dado que $null
es un valor constante, nunca se puede sobrescribir. No me gusta el modo en el que se ve en el código, pero suele ser más rápido que Out-Null
.
Redirección a $null
También puede usar el operador de redireccionamiento para enviar los resultados a $null
.
New-Item -Type Directory -Path $path > $null
Si está tratando con ejecutables de línea de comandos que se generan en los diferentes flujos. Puede redirigir todos los flujos de salida a $null
como se indica a continuación:
git status *> $null
Resumen
He tratado gran cantidad de aspectos y sé que este artículo está más fragmentado que la mayoría de mis temas en profundidad. Esto se debe a que los valores de $null
pueden aparecer en muchos lugares diferentes de PowerShell y todos los matices son específicos del lugar donde los encuentre. Espero que después de haberlo leído entienda mejor $null
y conozca los escenarios más complejos con los que puede encontrarse.