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

前端 未结 2 1921
深忆病人
深忆病人 2020-12-09 21:47

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

相关标签:
2条回答
  • 2020-12-09 22:38

    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
    
    0 讨论(0)
  • 2020-12-09 22:48

    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.

    0 讨论(0)
提交回复
热议问题