Tudo o que você queria saber sobre $null

O PowerShell $null muitas vezes parece ser simples, mas tem muitas nuances. Vamos dar uma olhada de perto para $null que você saiba o que acontece quando você inesperadamente se depara com um $null valor.

Nota

A versão original deste artigo apareceu no blog escrito por @KevinMarquette. A equipe do PowerShell agradece Kevin por compartilhar esse conteúdo conosco. Por favor, confira seu blog em PowerShellExplained.com.

O que é NULL?

Você pode pensar em NULL como um valor desconhecido ou vazio. Uma variável é NULL até que você atribua um valor ou um objeto a ela. Isso pode ser importante porque existem alguns comandos que exigem um valor e geram erros se o valor for NULL.

$null do PowerShell

$null é uma variável automática no PowerShell usada para representar NULL. Você pode atribuí-lo a variáveis, usá-lo em comparações e usá-lo como um espaço reservado para NULL em uma coleção.

O PowerShell trata $null como um objeto com um valor NULL. Isso é diferente do que você pode esperar se você vem de outro idioma.

Exemplos de $null

Sempre que você tentar usar uma variável que você não inicializou, o valor é $null. Essa é uma das maneiras mais comuns de os $null valores entrarem sorrateiramente em seu código.

PS> $null -eq $undefinedVariable
True

Se acontecer de você digitar incorretamente um nome de variável, o PowerShell o verá como uma variável diferente e o valor será $null.

A outra maneira de encontrar $null valores é quando eles vêm de outros comandos que não lhe dão resultados.

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

Impacto da $null

$null Os valores afetam seu código de forma diferente, dependendo de onde eles aparecem.

Em strings

Se você usar $null em uma cadeia de caracteres, então é um valor em branco (ou cadeia de caracteres vazia).

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

Esta é uma das razões pelas quais gosto de colocar colchetes em torno de variáveis ao usá-las em mensagens de log. É ainda mais importante identificar as bordas dos valores das variáveis quando o valor está no final da cadeia de caracteres.

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

Isso torna cadeias de caracteres e $null valores vazios fáceis de detetar.

Em equação numérica

Quando um $null valor é usado em uma equação numérica, seus resultados são inválidos se não derem um erro. Às vezes o $null avalia e 0 outras vezes faz todo o resultado $null. Aqui está um exemplo com multiplicação que dá 0 ou $null dependendo da ordem dos valores.

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

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

Em vez de uma coleção

Uma coleção permite que você use um índice para acessar valores. Se você tentar indexar em uma coleção que é realmente null, você recebe este erro: 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 você tiver uma coleção, mas tentar acessar um elemento que não está na coleção, obterá um $null resultado.

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

No lugar de um objeto

Se você tentar acessar uma propriedade ou subpropriedade de um objeto que não tenha a propriedade especificada, obterá um $null valor como faria para uma variável indefinida. Não importa se a variável é $null ou um objeto real neste caso.

PS> $null -eq $undefined.some.fake.property
True

PS> $date = Get-Date
PS> $null -eq $date.some.fake.property
True

Método em uma expressão de valor nulo

Chamar um método em um $null objeto lança um RuntimeExceptionarquivo .

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

Sempre que vejo a frase You cannot call a method on a null-valued expression , a primeira coisa que procuro são lugares onde estou chamando um método em uma variável sem primeiro verificá-lo para $null.

Verificando se há $null

Você deve ter notado que eu sempre coloco o $null à esquerda ao verificar $null meus exemplos. Isso é intencional e aceito como uma prática recomendada do PowerShell. Existem alguns cenários em que colocá-lo à direita não lhe dá o resultado esperado.

Veja este próximo exemplo e tente prever os resultados:

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

Se eu não definir $value, o primeiro avalia e $true a nossa mensagem é The array is $null. A armadilha aqui é que é possível criar um $value que permita que ambos sejam $false

$value = @( $null )

Neste caso, o $value é uma matriz que contém um $nullarquivo . O -eq verifica todos os valores na matriz e retorna o $null que é correspondido. Isto avalia a $false. O -ne devolve tudo o que não corresponde $null e, neste caso, não há resultados (isto também avalia a $false). Nenhum dos dois é $true , embora pareça que um deles deveria ser.

Não só podemos criar um valor que faça com que ambos avaliem $false, como também é possível criar um valor onde ambos avaliam $true. Mathias Jessen (@IISResetMe) tem um bom post que mergulha nesse cenário.

PSScriptAnalyzer e VSCode

O módulo PSScriptAnalyzer tem uma regra que verifica esse problema chamada PSPossibleIncorrectComparisonWithNull.

PS> Invoke-ScriptAnalyzer ./myscript.ps1

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

Como o VS Code também usa as regras do PSScriptAnalyser, ele também destaca ou identifica isso como um problema em seu script.

Simples se verificar

Uma maneira comum de as pessoas verificarem um valor não $null é usar uma instrução simples if() sem a comparação.

if ( $value )
{
    Do-Something
}

Se o valor for $null, isso será avaliado como $false. Isso é fácil de ler, mas tenha cuidado para que ele esteja procurando exatamente o que você espera que ele procure. Li essa linha de código como:

Se $value tem um valor.

Mas essa não é a história toda. Essa linha diz, na verdade:

Se $value não $null é ou 0 ou $false uma cadeia de caracteres vazia ou uma matriz vazia.

Aqui está uma amostra mais completa dessa afirmação.

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
}

Não há problema em usar uma verificação básica if , desde que você se lembre de que esses outros valores contam como $false e não apenas que uma variável tem um valor.

Eu me deparei com esse problema ao refatorar algum código há alguns dias. Tinha uma verificação de propriedade básica como esta.

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

Eu queria atribuir um valor à propriedade object somente se ela existisse. Na maioria dos casos, o objeto original tinha um valor que seria avaliado na $trueif instrução. Mas eu me deparei com um problema em que o valor ocasionalmente não estava sendo definido. Depurei o código e descobri que o objeto tinha a propriedade, mas era um valor de cadeia de caracteres em branco. Isso impediu que ele fosse atualizado com a lógica anterior. Então eu adicionei uma verificação adequada $null e tudo funcionou.

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

São pequenos bugs como esses que são difíceis de detetar e me fazem verificar agressivamente os valores para $null.

$null. Contagem

Se você tentar acessar uma propriedade em um $null valor, que a propriedade também $nullé . A count propriedade é a exceção a esta regra.

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

Quando você tem um $null valor, então o count é 0. Esta propriedade especial é adicionada pelo PowerShell.

[PSCustomObject] Contagem

Quase todos os objetos no PowerShell têm essa propriedade count. Uma exceção importante é o no Windows PowerShell 5.1 (isso é corrigido [PSCustomObject] no PowerShell 6.0). Ele não tem uma propriedade count, então você obtém um $null valor se tentar usá-lo. Eu chamo isso aqui para que você não tente usar .Count em vez de um $null cheque.

A execução deste exemplo no Windows PowerShell 5.1 e no PowerShell 6.0 oferece resultados diferentes.

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

Nulo vazio

Há um tipo especial de $null que age de forma diferente dos outros. Vou chamá-lo de vazio, $null mas é realmente um System.Management.Automation.Internal.AutomationNull. Este vazio $null é aquele que você obtém como resultado de uma função ou bloco de script que não retorna nada (um resultado vazio).

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

Se você compará-lo com $null, você obtém um $null valor. Quando usado em uma avaliação onde um valor é necessário, o valor é sempre $null. Mas se você colocá-lo dentro de uma matriz, ele será tratado da mesma forma que uma matriz vazia.

PS> $containempty = @( @() )
PS> $containnothing = @($nothing)
PS> $containnull = @($null)

PS> $containempty.count
0
PS> $containnothing.count
0
PS> $containnull.count
1

Você pode ter uma matriz que contém um $null valor e seu count é 1. Mas se você colocar um resultado vazio dentro de uma matriz, ele não será contado como um item. A contagem é 0.

Se você tratar o vazio $null como uma coleção, então ele está vazio.

Se você passar um valor vazio para um parâmetro de função que não é fortemente tipado, o PowerShell coage o valor nothing em um $null valor por padrão. Isso significa que dentro da função, o valor será tratado como $null em vez do tipo System.Management.Automation.Internal.AutomationNull .

Pipeline

O principal local onde você vê a diferença é ao usar o pipeline. Você pode canalizar um $null valor, mas não um valor vazio $null .

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

Dependendo do seu código, você deve levar em conta o $null em sua lógica.

Verifique primeiro $null

  • Filtrar nulo no pipeline (... | Where {$null -ne $_} | ...)
  • Manipulá-lo na função de pipeline

foreach

Uma das minhas características favoritas é foreach que ele não enumera sobre uma $null coleção.

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

Isso me evita ter $null que verificar a coleção antes de enumerá-la. Se você tiver uma coleção de $null valores, o $node ainda pode ser $null.

O foreach começou a funcionar dessa forma com o PowerShell 3.0. Se acontecer de você estar em uma versão mais antiga, então este não é o caso. Esta é uma das alterações importantes a ter em conta quando back-porting código para compatibilidade 2.0.

Tipos de valor

Tecnicamente, apenas os tipos de referência podem ser $null. Mas o PowerShell é muito generoso e permite que as variáveis sejam de qualquer tipo. Se você decidir digitar fortemente um tipo de valor, ele não poderá ser $null. O PowerShell converte $null em um valor padrão para muitos tipos.

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

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

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

Existem alguns tipos que não têm uma conversão válida de $null. Esses tipos geram um Cannot convert null to type erro.

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 função

O uso de valores fortemente tipados em parâmetros de função é muito comum. Geralmente aprendemos a definir os tipos de nossos parâmetros, mesmo que tendamos a não definir os tipos de outras variáveis em nossos scripts. Você já pode ter algumas variáveis fortemente tipadas em suas funções e nem perceber.

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

Assim que você definir o tipo do parâmetro como um string, o valor nunca poderá ser $null. É comum verificar se um valor é $null para ver se o usuário forneceu um valor ou não.

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

$Value é uma cadeia de caracteres '' vazia quando nenhum valor é fornecido. Em vez disso, use a variável $PSBoundParameters.Value automática.

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

$PSBoundParameters contém apenas os parâmetros que foram especificados quando a função foi chamada. Você também pode usar o ContainsKey método para verificar a propriedade.

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

IsNotNullOrEmpty

Se o valor for uma cadeia de caracteres, você poderá usar uma função de cadeia de caracteres estática para verificar se o valor é $null ou uma cadeia de caracteres vazia ao mesmo tempo.

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

Eu me pego usando isso com frequência quando sei que o tipo de valor deve ser uma cadeia de caracteres.

Quando eu $null verificar

Eu sou um roteirista defensivo. Sempre que chamo uma função e a atribuo a uma variável, verifico-a para $null.

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

Eu prefiro usar if ou foreach mais usar try/catch. Não me interpretem mal, eu ainda uso try/catch muito. Mas se eu puder testar uma condição de erro ou um conjunto vazio de resultados, posso permitir que meu tratamento de exceções seja para exceções verdadeiras.

Eu também tendo a verificar antes $null de indexar em um valor ou chamar métodos em um objeto. Essas duas ações falham para um $null objeto, então acho importante validá-las primeiro. Já abordei esses cenários anteriormente neste post.

Sem cenário de resultados

É importante saber que diferentes funções e comandos lidam com o cenário sem resultados de forma diferente. Muitos comandos do PowerShell retornam o vazio $null e um erro no fluxo de erros. Mas outros lançam exceções ou lhe dão um objeto de status. Ainda cabe a você saber como os comandos que você usa lidam com os cenários sem resultados e erros.

Inicializando para $null

Um hábito que eu peguei é inicializar todas as minhas variáveis antes de usá-las. Deve fazê-lo noutras línguas. Na parte superior da minha função ou quando entro em um loop foreach, defino todos os valores que estou usando.

Aqui está um cenário que eu quero que você dê uma olhada de perto. É um exemplo de um bug que eu tive 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
        }
    }
}

A expectativa aqui é que Get-Something retorne um resultado ou um vazio $null. Se houver um erro, nós o registramos. Em seguida, verificamos se obtivemos um resultado válido antes de processá-lo.

O bug oculto neste código é quando Get-Something lança uma exceção e não atribui um valor a $result. Ele falha antes da atribuição, então nem atribuímos $null à $result variável. $result ainda contém o anterior válido $result de outras iterações. Update-Something para executar várias vezes no mesmo objeto neste exemplo.

Eu defino $result para $null dentro do loop foreach antes de usá-lo para mitigar esse problema.

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

Questões relativas ao âmbito de aplicação

Isso também ajuda a mitigar problemas de escopo. Nesse exemplo, atribuímos valores a $result repetidamente em um loop. Mas como o PowerShell permite que valores variáveis de fora da função sangrem no escopo da função atual, inicializá-los dentro de sua função atenua bugs que podem ser introduzidos dessa forma.

Uma variável não inicializada em sua função não $null é definida como um valor em um escopo pai. O escopo pai pode ser outra função que chama sua função e usa os mesmos nomes de variáveis.

Se eu pegar esse mesmo Do-something exemplo e remover o loop, eu acabaria com algo que se parece com este exemplo:

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 a chamada para Get-Something lançar uma exceção, então minha $null verificação encontra o $result de Invoke-Something. Inicializar o valor dentro de sua função atenua esse problema.

Nomear variáveis é difícil e é comum que um autor use os mesmos nomes de variáveis em várias funções. Eu sei que eu uso $node,$result,$data o tempo todo. Assim, seria muito fácil que valores de diferentes escopos aparecessem em lugares onde não deveriam estar.

Redirecionar a saída para $null

Eu tenho falado sobre $null valores para este artigo inteiro, mas o tópico não está completo se eu não mencionei o redirecionamento de saída para $null. Há momentos em que você tem comandos que geram informações ou objetos que você deseja suprimir. Redirecionar a saída para $null fazer isso.

Out-Null

O comando Out-Null é a maneira interna de redirecionar dados de pipeline para $null.

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

Atribuir a $null

Você pode atribuir os resultados de um comando para $null o mesmo efeito que usar Out-Nullo .

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

Como $null é um valor constante, você nunca pode substituí-lo. Eu não gosto da aparência no meu código, mas muitas vezes tem um desempenho mais rápido do que Out-Nullo .

Redirecionar para $null

Você também pode usar o operador de redirecionamento para enviar a saída para $nullo .

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

Se você estiver lidando com executáveis de linha de comando que geram saída nos diferentes fluxos. Você pode redirecionar todos os fluxos de saída para $null assim:

git status *> $null

Resumo

Eu cobri muito terreno sobre este e sei que este artigo é mais fragmentado do que a maioria dos meus mergulhos profundos. Isso ocorre porque $null os valores podem aparecer em muitos lugares diferentes no PowerShell e todas as nuances são específicas de onde você os encontra. Espero que você saia disso com uma melhor compreensão $null e consciência dos cenários mais obscuros que você pode encontrar.