Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
O PowerShell $null muitas vezes parece ser simples, mas tem muitas nuances. Vamos dar uma olhada em $null para que você saiba o que acontece quando você inesperadamente se depara com um valor $null.
Observação
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 há alguns comandos que exigem um valor e podem gerar 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 não tenha inicializado, o valor será $null. Esta é uma das maneiras mais comuns de $null valores se infiltrarem no 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.
Outra forma de encontrar valores $null é quando provêm de outros comandos que não produzem qualquer resultado.
PS> function Get-Nothing {}
PS> $value = Get-Nothing
PS> $null -eq $value
True
Impacto do $null
$null 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 vazias e valores de $null fáceis de detetar.
Em equação numérica
Quando um valor $null é usado em uma equação numérica, seus resultados são inválidos se não derem um erro. Às vezes, $null é avaliado como 0 e outras vezes torna 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ê receberá 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 resultado $null.
$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 valor $null 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 objeto $null lança um 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
Sempre que vejo a frase You cannot call a method on a null-valued expression então 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 nos 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 para $true e a nossa mensagem é The array is $null. A armadilha aqui é que é possível criar um $value que permita que ambos sejam $false
$value = @( $null )
Nesse caso, o $value é uma matriz que contém um $null. O -eq verifica todos os valores no array e retorna o $null que corresponde. Isso resulta em $false. O -ne retorna tudo o que não corresponde a $null e, neste caso, não há resultados (isto também se avalia como $false). Nenhum dos dois é $true, ainda que pareça que um deles deva ser.
Não só podemos criar um valor que faz com que ambos avaliem para $false, como também podemos criar um valor onde ambos avaliam para $true. Mathias Jessen (@IISResetMe) tem um post bom que mergulha nesse cenário.
PSScriptAnalyzer e VS Code
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.
Verificação simples de condição 'if'
Uma maneira comum de as pessoas verificarem um valor não $null é usar uma instrução if() simples 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
$valuetem um valor.
Mas essa não é a história toda. Essa linha diz, na verdade:
Se
$valuenão for$nullou0ou$falseou 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 if básica, 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 do objeto apenas se ela existisse. Na maioria dos casos, o objeto original tinha um valor que seria avaliado para $true na instrução if. 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 de $null adequada 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 tentar aceder a uma propriedade num valor $null, essa propriedade também é $null. A propriedade Count é a exceção a esta regra.
PS> $value = $null
PS> $value.Count
0
Quando você tem um valor $null, 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 [pscustomobject] no Windows PowerShell 5.1 (isso é corrigido no PowerShell 6.0). Ele não tem uma propriedade Count, então você obtém um valor $null se tentar usá-lo. Menciono isto aqui para que não tentem usar Count em vez de uma verificação $null.
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 enumerável
Existe um tipo especial de $null que age de forma diferente dos outros. Vou chamá-lo de null enumerável, mas é realmente um System.Management.Automation.Internal.AutomationNull.
Este nulo enumerável é 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 valor $null. 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 valor $null e seu Count é 1. Mas se você colocar uma matriz vazia dentro de uma matriz, ela não será contada como um item. A contagem é 0.
Se você tratar o null enumerável como uma coleção, ele estará vazio.
Se você passar um null enumerável para um parâmetro de função que não seja fortemente tipado, o PowerShell converte o null enumerável em um valor $null por padrão. Isso significa que, dentro da função, o valor é tratado como $null em vez do tipo de System.Management.Automation.Internal.AutomationNull.
Gasoduto
O principal local onde você vê a diferença é ao usar o pipeline. Você pode canalizar um valor $null, mas não um valor nulo enumerável.
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 $null primeiro
- Filtrar valores nulos na canalização (
... | where {$null -ne $_} | ...) - Gerenciá-lo na função do pipeline
para cada um
Uma das minhas funcionalidades favoritas do foreach é que não enumera sobre uma coleção $null.
foreach ( $node in $null )
{
#skipped
}
Isso me evita ter que $null verificar a coleção antes de enumerá-la. Se você tiver uma coleção de valores $null, o $node ainda poderá ser $null.
O foreach começou a trabalhar 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 ao adaptar código para compatibilidade com a versão 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 erro 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 função
O uso de valores fortemente tipificados 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ê pode já ter algumas variáveis fortemente tipadas nas 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 automática $PSBoundParameters.Value.
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 método ContainsKey para verificar se a propriedade existe.
if ( $PSBoundParameters.ContainsKey('Value') ){...}
NãoÉNuloOuVazio
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 verificar $null
Eu sou um programador de scripts defensivo. Sempre que eu chamo uma função e a atribuo a uma variável, eu verifico $null.
$userList = Get-ADUser kevmar
if ($null -ne $userList){...}
Eu prefiro usar if ou foreach do que usar try/catch. Não me interpretem mal, eu ainda uso muito try/catch. 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 costumo verificar se há $null antes de indexar num valor ou chamar métodos em um objeto. Essas duas ações falham para um objeto $null, 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 null enumerável e um erro no fluxo de erro. Mas outros lançam exceções ou lhe fornecem 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 a $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 num loop de foreach, defino todos os valores que estou a utilizar.
Aqui está um cenário que eu quero que você dê uma olhada de perto. É um exemplo de um bug que eu tive que localizar anteriormente.
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 nulo enumerável. 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 sequer atribuímos $null à variável $result.
$result ainda contém o anterior $result válido de outras iterações.
Update-Something executar várias vezes no mesmo objeto neste exemplo.
Eu defino $result em $null dentro do loop foreach antes de utilizá-lo para mitigar este 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 sejam transmitidos para o escopo da função atual, inicializá-los dentro da sua função reduz os erros que podem ser introduzidos dessa forma.
Uma variável não inicializada na sua função não será $null se estiver atribuída a um valor num 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 exemplo Do-something 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 a minha verificação de $null 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 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
Tenho falado sobre $null valores para este artigo inteiro, mas o tópico não está completo se eu não mencionasse 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 faz isso.
Out-Null
O comando Out-Null é a forma integrada 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 a $null para o mesmo efeito que usar Out-Null.
$null = New-Item -Type Directory -Path $path
Como $null é um valor constante, você nunca pode substituí-lo. Não gosto da forma como fica no meu código, mas frequentemente tem um desempenho mais rápido do que Out-Null.
Redirecionar para $null
Você também pode usar o operador de redirecionamento para enviar a saída para $null.
New-Item -Type Directory -Path $path > $null
Se estiveres a lidar com executáveis de linha de comando que geram saída em diferentes fluxos. Você pode redirecionar todos os fluxos de saída para $null assim:
git status *> $null
Resumo
Cobri muito terreno neste e sei que este artigo é mais fragmentado do que a maioria das minhas análises aprofundadas. Isso ocorre porque os valores $null podem aparecer em muitos lugares diferentes no PowerShell, e todas as nuances são específicas para onde são encontrados. Espero que você saia disso com uma melhor compreensão da $null e uma consciência dos cenários mais obscuros que você pode encontrar.