Compartir a través de


Todo lo que querías saber sobre $null

A menudo, PowerShell $null parece ser sencillo, pero tiene muchos matices. Analicemos detenidamente $null para que sepas qué ocurre cuando encuentras inesperadamente un valor de $null.

Nota:

La versión original de este artículo apareció en el blog escrito por @KevinMarquette. El equipo de PowerShell agradece a Kevin por compartir este contenido con nosotros. Consulte su blog en PowerShellExplained.com.

¿Qué es NULL?

Puede considerar NULL como un valor desconocido o vacío. Una variable es 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.

PowerShell $null

$null es una variable automática en PowerShell que se usa para representar NULL. Puede asignarla a variables, usarla en comparaciones y usarla como marcador de posición para NULL en una colección.

PowerShell trata $null como un objeto con un valor NULL. Esto es diferente de lo que puede esperar si proviene de otro idioma.

Ejemplos de $null

Cada vez que intente usar una variable que no se haya inicializado, el valor es $null. Esta es una de las formas más comunes en que los $null valores entran en el código.

PS> $null -eq $undefinedVariable
True

Si se produce un error en el tipo de un nombre de variable, PowerShell lo ve como una variable diferente y el valor es $null.

La otra forma de encontrar $null valores 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

Impacto de $null

$null los valores afectan al código de forma diferente en función de dónde se muestren.

En cadenas

Si usa $null en una cadena, se trata de 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 colocar corchetes en torno a las variables al usarlos en los mensajes de registro. Es aún más importante identificar los bordes 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 []

Esto hace que las cadenas vacías y los valores $null vacíos se detecten fácilmente.

En ecuación numérica

Cuando se usa un $null valor 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 $null sea todo el resultado. Este es un ejemplo con multiplicación que proporciona 0 o $null según el 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 colección permite usar un índice para acceder a los valores. Si intenta indexar en una colecció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 colección pero intenta acceder a un elemento que no está en la colección, obtendrá un $null resultado.

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

En lugar de un objeto

Si intenta tener acceso a una propiedad o una subpropiedad de un objeto que no tiene la propiedad especificada, obtendrá un $null valor como lo haría con una variable indefinida. 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 buscaré son lugares donde estoy llamando a un método en una variable sin comprobarlo primero para $null.

Búsqueda de $null

Es posible que haya observado que siempre coloco $null a la izquierda al buscar $null en mis ejemplos. Esto es intencionado y aceptado como procedimiento recomendado de PowerShell. Hay algunos escenarios en los que colocarlo a la derecha no le da el resultado esperado.

Examine este 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 defina $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 $value que permita que ambos sean $false

$value = @( $null )

En este caso, $value es una matriz que contiene un $null. -eq Comprueba todos los valores de la matriz y devuelve el $null que coincide. Esto da como resultado $false. -ne devuelve todo lo que no coincide $null y, en este caso, no hay resultados (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 donde ambos se evalúan como $true. Mathias Jessen (@IISResetMe) tiene una buena publicación que profundiza en ese escenario.

PSScriptAnalyzer y VS Code

El módulo PSScriptAnalyzer tiene una regla que comprueba si hay este problema denominado PSPossibleIncorrectComparisonWithNull.

PS> Invoke-ScriptAnalyzer ./myscript.ps1

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

Dado que VS Code también usa las reglas de PSScriptAnalyser, también resalta o lo identifica como un problema en el script.

Comprobación if simple

Una forma común de comprobar si hay un valor que no es $null es usar una instrucción simple if() 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. Leí esa línea de código como:

Si $value tiene un valor.

Pero eso no es toda la historia. De hecho, esa línea dice:

Si $value no es $null, 0, $false, una cadena vacía o una 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 perfectamente correcto usar una comprobación básica if siempre y cuando recuerde que esos otros valores cuentan como $false y no solo que una variable tiene un valor.

He tenido 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 existía. En la mayoría de los casos, el objeto original tenía un valor que se evaluaría como $true en la sentencia if. Pero me encontré con un problema en el que el valor ocasionalmente 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 impedía que se actualizara con la lógica anterior. Así que añadí una comprobación adecuada $null y todo funcionó.

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

Son pequeños errores como estos que son difíciles de detectar y me llevan a comprobar con ahínco los valores de $null.

$null. Contar

Si intenta acceder a una propiedad de un valor $null, esa propiedad también es $null. La Count propiedad es la excepción a esta regla.

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

Cuando tienes un valor $null, entonces el Count es 0. PowerShell agrega esta propiedad especial.

[PSCustomObject] Contar

Casi todos los objetos de PowerShell tienen esa Count propiedad. Una excepción importante es la [pscustomobject] en Windows PowerShell 5.1 (esto se corrigió en PowerShell 6.0). Como no tiene una Count propiedad, obtendrá un $null valor si intenta usarlo. Menciono esto aquí para que no intentes usar Count en lugar de una $null verificación.

Al ejecutar este ejemplo en Windows PowerShell 5.1 y PowerShell 6.0 se proporcionan 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 forma diferente a la de los demás. Voy a llamarlo null enumerable, pero es realmente un System.Management.Automation.Internal.AutomationNull. Este valor NULL enumerable es el que obtiene como resultado de una función o bloque de script que no devuelve nada (un resultado nulo).

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

Si lo compara con $null, obtendrá un $null valor. Cuando se usa en una evaluación donde un valor es requerido, el valor siempre es $null. Pero si lo coloca dentro de una matriz, se trata igual 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 tener una matriz que contenga un $null valor y su Count es 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 .

Tubería

El lugar principal que ve la diferencia es cuando se usa la canalización. Puede canalizar un $null valor 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 realiza enumeración sobre una colección de $null.

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

Esto me ahorra tener que $null comprobar la colección antes de enumerarla. Si tiene una colección de $null valores, $node todavía puede ser $null.

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 volver a portar el código para la compatibilidad con la versión 2.0.

Tipos de valor

Técnicamente, solo los tipos de referencia pueden ser $null. Pero 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 de $null. Estos tipos generan un Cannot convert null to type error.

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. Por lo general, aprendemos a definir los tipos de nuestros parámetros incluso si no tendemos a 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
    )
}

Tan pronto como establezca el tipo del parámetro como string, el valor nunca puede ser $null. Es habitual comprobar si un valor es $null 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. Use la variable $PSBoundParameters.Value automática en su lugar.

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

$PSBoundParameters solo contiene los parámetros especificados cuando se llamó a la función. También puede utilizar el método ContainsKey para comprobar la propiedad.

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

No es nulo ni está vacío

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 ) ){...}

Me encuentro usando esto a menudo cuando sé que el tipo de valor debe ser una cadena.

Cuando compruebo $null

Soy un scripter defensivo. Cada vez que llame a una función y asígnela a una variable, la comprué para $null.

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

Prefiero mucho usar if o foreach sobre usar try/catch. No me equivoques, todavía uso 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 excepciones verdaderas.

También tiendo a buscar $null antes de indexar en un valor o llamar a métodos en un objeto. Estas dos acciones no se pueden realizar para un $null objeto, por lo que creo que es importante validarlas primero. Ya he tratado esos escenarios anteriormente en esta publicación.

Sin escenario de resultados

Es importante saber que las diferentes 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 errores. Pero otros inician excepciones o proporcionan un objeto de estado. Todavía depende de ti saber cómo los comandos que utilizas gestionan las situaciones de errores y de no resultados.

Inicialización en $null

Un hábito que he recogido es inicializar todas mis variables antes de usarlas. Debe hacerlo en otros idiomas. En la parte superior de mi función o al entrar en un foreach bucle, defino todos los valores que estoy usando.

Este es un escenario en el que quiero que eche un vistazo. 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
        }
    }
}

La expectativa aquí es que Get-Something devuelva un resultado o un valor NULL enumerable. Si se produce un error, lo registramos. A continuación, comprobamos para asegurarnos de que tenemos un resultado válido antes de procesarlo.

El error presente en este código ocurre cuando Get-Something lanza una excepción y no asigna un valor a $result. Se produce un error antes de la asignación, por lo que ni siquiera se asigna $null a la $result variable . $result todavía contiene el valor válido $result anterior de otras iteraciones. Update-Something para ejecutar varias veces en el mismo objeto de este ejemplo.

He establecido $result en $null justo dentro del foreach bucle 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 Do-something ejemplo y quite el bucle, terminaría con algo similar 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. Al inicializar el valor dentro de la función, se mitiga este problema.

La nomenclatura de variables es difícil y es habitual que un autor use los mismos nombres de variable en varias funciones. Sé que uso $node,$result$data todo el tiempo. Por lo tanto, sería muy fácil que los valores de diferentes ámbitos se muestren en lugares donde no deberían ser.

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 o objetos que desea suprimir. Redirigir la salida a $null lo hace.

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 el mismo efecto que mediante Out-Null.

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

Dado que $null es un valor constante, nunca se puede sobrescribir. No me gusta la forma en que se ve en mi código, pero a menudo funciona más rápido que Out-Null.

Redireccionamiento a $null

También puede usar el operador de redireccionamiento para enviar la salida 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 todas las secuencias de salida a $null como esta:

git status *> $null

Resumen

He tratado mucho sobre este tema y sé que este artículo está más fragmentado que la mayoría de mis inmersiones profundas. 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.