Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is an empty PowerShell pipeline not the same as null?

I am trying to understand the behavior of the @() array constructor, and I came across this very strange test.

It seems that the value of an empty pipeline is "not quite" the same as $null, even though it is -eq $null

The output of each statement is shown after the ###

$y = 1,2,3,4 | ? { $_ -ge 5 }
$z = $null

if ($y -eq $null) {'y is null'} else {'y NOT null'}  ### y is null
if ($z -eq $null) {'z is null'} else {'z NOT null'}  ### z is null

$ay = @($y)
$az = @($z)

"ay.length = " + $ay.length  ### ay.length = 0
"az.length = " + $az.length  ### az.length = 1

$az[0].GetType()  ### throws exception because $az[0] is null

So the $az array has length one, and $az[0] is $null.

But the real question is: how is it possible that both $y and $z are both -eq $null, and yet when I construct arrays with @(...) then one array is empty, and the other contains a single $null element?

like image 281
John Rees Avatar asked Mar 12 '14 06:03

John Rees


People also ask

Is null or empty in PowerShell?

You can think of NULL as an unknown or empty value. A variable is NULL until you assign a value or an object to it.

How do you check variable is empty or not in PowerShell?

You can use the [string]::IsNullOrEmpty($version) method if it is a string.

What is pipeline variable in PowerShell?

PowerShell has a unique variable called the pipeline variable ($_ or $PSItem). Due to PowerShell's ability to pipe entire objects from one command to another, it needed a way to represent that object that was traversing the pipeline.


2 Answers

Expanding on Frode F.'s answer, "nothing" is a mostly magical value in PowerShell - it's called [System.Management.Automation.Internal.AutomationNull]::Value. The following will work similarly:

$y = 1,2,3,4 | ? { $_ -ge 5 }
$y = [System.Management.Automation.Internal.AutomationNull]::Value

PowerShell treats the value AutomationNull.Value like $null in most places, but not everywhere. One notable example is in a pipeline:

$null | % { 'saw $null' }
[System.Management.Automation.Internal.AutomationNull]::Value | % { 'saw AutomationNull.Value' }

This will only print:

saw $null

Note that expressions are themselves pipelines even if you don't have a pipeline character, so the following are roughly equivalent:

@($y)
@($y | Write-Output)

Understanding this, it should be clear that if $y holds the value AutomationNull.Value, nothing is written to the pipeline, and hence the array is empty.

One might ask why $null is written to the pipeline. It's a reasonable question. There are some situations where scripts/cmdlets need to indicate "failed" without using exceptions - so "no result" must be different, $null is the obvious value to use for such situations.

I've never run across a scenario where one needs to know if you have "no value" or $null, but if you did, you could use something like this:

function Test-IsAutomationNull
{
    param(
        [Parameter(ValueFromPipeline)]
        $InputObject)

    begin
    {
        if ($PSBoundParameters.ContainsKey('InputObject'))
        {
            throw "Test-IsAutomationNull only works with piped input"
        }
        $isAutomationNull = $true
    }
    process
    {
        $isAutomationNull = $false
    }
    end
    {
        return $isAutomationNull
    }
}

dir nosuchfile* | Test-IsAutomationNull
$null | Test-IsAutomationNull
like image 95
Jason Shirk Avatar answered Nov 08 '22 07:11

Jason Shirk


The reason you're experiencing this behaviour is becuase $null is a value. It's a "nothing value", but it's still a value.

PS P:\> $y = 1,2,3,4 | ? { $_ -ge 5 }

PS P:\> Get-Variable y | fl *

#No value survived the where-test, so y was never saved as a variable, just as a "reference"

Name        : y
Description : 
Value       : 
Visibility  : Public
Module      : 
ModuleName  : 
Options     : None
Attributes  : {}


PS P:\> $z = $null


PS P:\> Get-Variable z | fl *

#Our $null variable is saved as a variable, with a $null value.

PSPath        : Microsoft.PowerShell.Core\Variable::z
PSDrive       : Variable
PSProvider    : Microsoft.PowerShell.Core\Variable
PSIsContainer : False
Name          : z
Description   : 
Value         : 
Visibility    : Public
Module        : 
ModuleName    : 
Options       : None
Attributes    : {}

The way @() works, is that it guarantees that the result is delievered inside a wrapper(an array). This means that as long as you have one or more objects, it will wrap it inside an array(if it's not already in an array like multiple objects would be).

$y is nothing, it's a reference, but no variable data was stored. So there is nothing to create an array with. $z however, IS a stored variable, with nothing(null-object) as the value. Since this object exists, the array constructor can create an array with that one item.

like image 35
Frode F. Avatar answered Nov 08 '22 08:11

Frode F.