Share via


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

Null vacío

Hay un tipo especial de $null que actúa de manera diferente que los demás. Voy a llamarlo el elemento $null vacío, pero es realmente System.Management.Automation.Internal.AutomationNull. Este $null vacío 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 un resultado vacío dentro de una matriz, no se cuenta como un elemento. El recuento es 0.

Si trata el elemento $null vacío como una recopilación, estará vacía.

Si pasa un valor vacío a un parámetro de función que no está fuertemente tipado, PowerShell convierte el valor nothing 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 vacío.

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 vacío 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 $null vacío. 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.