这 PowerShell $null often appears to be simple but it has a lot of nuances. Let’s take a close look at $null so you know what happens when you unexpectedly run into a $null value.

指数

什么是零

您可以将空值视为未知或空值。变量为null,直到为其分配值或对象。这可能很重要,因为存在一些需要值的命令,如果值为null,则会生成错误。

powershell $ null.

$null 是用于表示空的PowerShell中的自动变量。您可以将其分配给变量,使用它在比较中,并将其用作集合中空的持有器。

电源外壳 treats $null as an object with a value of NULL. This is different then what you may expect if you come from another language.

$ null的示例

Any time you try to use a variable that you have not initialized with a value, it will be $null. This is one of the most common ways that $null values will sneak into your code.

    PS> $null -eq $undefinedVariable
    True

It you happen to mistype a variable name then PowerShell will see it as a different variable and the value will be $null.

这 other way you will find $null values is when they come from other commands that don’t give you any results.

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

$ null的影响

$null 值将根据他们出现的位置而不同地影响您的代码。

在字符串中

If you use $null in a string, then it will be a blank value (or empty string).

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

这是在日志消息中使用它们时,我喜欢将括号放置括号的原因之一。当值在字符串末尾时,识别变量值的边缘更为重要。

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

This makes empty strings and $null values easy to spot.

在数字方程中

When a $null value is used in a numeric equation then your results will be invalid if they don’t give an error. Sometimes the $null will evaluate to 0 and other times it will make the whole result $null. Here is an example with multiplication that gives 0 or $null depending on the order of the values.

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

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

代替集合

A collection allow you use an index to access values. If you try to index into a collect that is actually null, then you will get this 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

I you have a collection but try to access an element that is not in the collection then you will get a $null result.

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

代替物体

If you try to access a property or sub property of an object that does not have the specified property then you get a $null value like you would for an undefined variable. It does not matter if the variable is $null or an actual object in this case.

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

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

在空值表达式上的方法

Calling a method on a $null object will throw a RuntimeException exception.

    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

Whenever I see the phrase You cannot call a method on a null-valued expression then the first thing I do is look for is places that I am calling a method on a variable without first checking it for $null.

检查$ null

You may have noticed that I always place the $null on the left when checking for $null in my examples. This is intentional and accepted as a PowerShell best practice. There are some scenarios where placing it on the right will not give you the expected result.

看看下一个例子,并尝试预测结果将是什么:

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

If I do not define $value then the first one will evaluate to $true and our message will be 这 array is $null. The trap here is that it is possible to create a $value that will allow both of them to be $false

   $value = @( $null )

In this case the $value is an array that contains a $null. The -eq will check every value in the array and returns the $null that is matched (This evaluates to $false). The -ne will return everything that does not match $null and in this case there are no results (This also evaluates to $false). Neither one will be $true even though it looks like one of them should be.

Not only can we create a value that makes both of them evaluate to $false, it is possible to create a value where they both evaluate to $true. Mathias Jessen @Iisresetme. 有个 好帖子 潜入那种情况。

psscriptanalyzer和vscode.

psscriptanalyzer. module has a rule that checks for this issue called PSPossibleIncorrectComparisonWithNull.

    PS> Invoke-ScriptAnalyzer ./myscript.ps1

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

因为vscode也使用psscriptanAlyser规则,所以它也会困难或将其标识为脚本中的问题。

简单,如果检查

A common way that people check for a non $null value is to use a simple if() statement without the comparison.

    if ( $value )
    {
        Do-Something
    }

If the value is $null, this will evaluate to $false. This is simple and easy to read, but be careful that it is looking for exactly what you are expecting it to look for. I read that line of code as If $value has a value, but thats not the whole story. That line is actually saying If $value is not $null or 0 or $false or an empty string.

以下是该陈述的更完整的样本。

    if ( $null -ne $value -and
         $value -ne 0 -and
         $value -ne '' -and
         $value -ne $false )
    {
        Do-Something
    }

It is perfectly OK to use a basic if check as long as you remember those other values count as $false and not just that a variable has a value.

几天前重构一些代码时,我遇到了这个问题。它有一个基本的财产检查。

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

I wanted to assign a value to the object property only if it existed. In most cases, the original object had a value that would evaluate to $true in the if statement. But I ran into an issue where the value was occasionally not getting set. I debugged into and I found that the object did have the property but it was a blank string value. This prevented it from ever getting updated with the previous logic. So I added a proper $null check and everything worked.

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

It’s little bugs like this that are hard to spot that make me aggressivly check values for $null.

$ null.count.

If you try to access a property on a $null value, that the property will also be $null. The count property is the exception to this rule.

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

When you have a $null value, then the count will be 0. This special property is added by PowerShell.

[pscustomobject]计数

Almost all objects in PowerShell have that count property. One important exception is the [PSCustomObject] in Windows PowerShell 5.1 (This is fixed in PowerShell 6.0). It does not have a count property so you will get a $null value if you try to use it. I call this out here so that you don’t try an use .Count instead of a $null check.

在Windows PowerShell 5.1上运行此示例,PowerShell 6.0将为您提供不同的结果。

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

空无效

这 re is one special type of $null that acts differently than the others. I am going to call it the empty $null but its really a system.Management.Automation.Internal.AutomationNull.. This empty $null is the one you get as the result of a function or script block that returns nothing (a void result).

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

If you compare it with $null then you will get a $null value. When used in an evaluation where a value is required, the value is always $null. But if you place it inside an array, it is treated the same as an empty array.

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

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

You can have an array that contains one $null value and its count will be 1. But if you place an empty result inside an array then it is not counted as an item. The count will be 0.

If you treat the empty $null like a collection, then it is empty.

管道

这 primary place you will see the difference is when using the pipeline. You can pipe a $null value but not an empty $null value.

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

Depending on your code, you should account for the $null in your logic.

  • Either check for $null first
  • Filter out null on the pipeline (... | Where {$null -ne $_} | ...)
  • 在管道功能中处理它

Foreach.

One of my favorite features of Foreach. is that it does not enumerate over a $null collection.

    Foreach. ( $node in $null )
    {
        #skipped
    }

This saves me from having to $null check the collection before I enumerate it. If you have a collection of $null values then the $node can still be $null.

Foreach开始用PowerShell 3.0以这种方式工作。如果您碰巧在较旧版本上,那就是这样的情况。这是要注意的重要更改之一,以便在兼容2.0兼容性时要注意。

价值类型

Technically, only reference types can be $null. But PowerShell is very generous and allows for variables to be any type. If you decide to stronly type a value type then it cannot be $null. PowerShell can convert $null to a default value for many types.

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

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

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

这 re are some types that do not have a valid conversion from $null. These types will generate a 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

功能参数

使用强类型值在功能参数中非常常见。即使我们倾向于在脚本中定义其他变量的类型,我们通常会学会定义我们参数的类型。您可能已经在功能中有一些强烈的变量,甚至没有意识到它。

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

As soon as you set the type of the parameter as a string, the value can never be $null. It is very common to check if a value is $null to see if the user provided a value or not.

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

$Value will be an empty string '' by default in this case if no value is provided. Use the automatic varible $PSBoundParameters.Value instead.

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

$PSBoundParameters only contains parameter that were specified when the function was called. You can also use the ContainsKey method to check for the property.

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

Isnotnullolempty

If the value is a string, you can use a static string function to check if the value is $null or an empty string at the same time.

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

当我知道值类型应该是一个字符串时,我发现自己经常使用这个。

当我每次核

I am a defensive scripter. Anytime I call a function and assign it to a variable, I will check it for $null.

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

I much prefer using if or Foreach. over using try/catch. Don’t get me wrong, I still use try/catch a lot. But if I can test for an error condition or an empty set of results, I can allow my exception handling be for true exceptions.

I also tend to check for $null before I index into a value or call methods on an object. These two actions will fail if they are on a $null object so I find it important to validate them first. I already covered those senarios earlier in this post.

没有结果佛陀

It is important to know that different functions and commands handle the no results scenario differently. Many PowerShell commands return the empty $null and an error in the error stream. But other will throw exceptions or give you a status object. It is still up to you to know how the commands you are calling deal with the no results and error scenarios.

初始化到$ null

在我使用它们之前,我已拾起的一个人是初始化所有变量。您必须用其他语言执行此操作。在我的函数之上或当我输入Foreach循环时,我将定义我将使用的所有值。

这是一个我希望你仔细看看的情景。这是一个令我不得不追逐之前的虫子的娱乐。

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

这 expectation here is that Get-Something will either return a result or an empty $null. If there is an error then we log it. Then we check to make sure we got a valid result before processing it.

这 bug hidding in this code is when Get-Something throws an exception and does not assign a value to $result. It fails before the assignment so we don’t even assign $null to the $result varialbe. $result will still contain the previous valid $result from other iterations. Update-Something to execute multiple times on the same object in this example.

I will set $result to $null right inside the foreach loop before I use it to mitigate this issue.

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

范围问题

This also helps mitigate scoping issues. In that example, we assign values to $result over and over in a loop. But because PowerShell allows variable values from outside the function to bleed into the scope of the current function, initalizing them inside your function will mitigate bugs that can be introduced that way.

An uninitalized variable in your function will not be $null if it is set to a value in a parent scope. This can be other functions callig your function that happen to use the same variable names.

If I take that same Do-something example and remove the loop I would end up with something that looks like this example:

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

If the call to Get-Something were to throw an exception, then my $null check will find the $result from Invoke-Something. Initalizing the value inside your function mitigates this issue.

Naming variables is hard and it is common for an author to use the same variable names in multiple functions. I know I use $node,$result,$data all the time. So it would be very easy for values from different scopes to show up in places where they should not be.

将输出重定向到$ null

I have been talking about $null values for this entire article but the topic is not complete if I didn’t mention redirecting output to $null. There will be times where you will have commands that output information or objects that you want to suppress. Redirecting output to $null does exactly that.

out-null.

这 Out-Null command is the built-in way to redirect pipeline data to $null.

    New-Item -Type Directory -Path $path | out-null.

分配给$ null

You can assign the results of a command to $null for the same effect as using out-null..

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

Because $null is a constant value, you can never overwrite it. I don’t like the way it looks in my code but it often performs faster than out-null..

重定向到$ null

You can also use the redirection opperator to send output to $null.

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

If you are dealing with command line executables that output on the different streams. You can redirect all output streams to $null like this:

   git status *> $null

概括

I covered a lot of ground on this one and I know this article is more fragmented than most of my deep dives. That is because $null values can pop up in many different places in PowerShell and all the nuances are specific to where you find it. I hope you walk away from this with a better understanding of $null and an awareness of the more obscure scenarios you may run into.