【问题标题】:PowerShell - "Write-Output" vs "return" in functionsPowerShell - 函数中的“写入输出”与“返回”
【发布时间】:2018-12-13 03:26:41
【问题描述】:

我已经使用 PowerShell 很多年了,我认为我已经掌握了它的一些更“古怪”的行为,但我遇到了一个我无法解决的问题。 .

我一直使用“return”从函数返回值,但最近我想我应该看看 Write-Output 作为替代方案。但是,PowerShell 是 PowerShell,我发现了一些似乎没有意义的东西(至少对我来说):

function Invoke-X{ write-output @{ "aaa" = "bbb" } };
function Invoke-Y{ return @{ "aaa" = "bbb" } };

$x = Invoke-X;
$y = Invoke-Y;

write-host $x.GetType().FullName
write-host $y.GetType().FullName

write-host ($x -is [hashtable])
write-host ($y -is [hashtable])

write-host ($x -is [pscustomobject])
write-host ($y -is [pscustomobject])

输出:

System.Collections.Hashtable
System.Collections.Hashtable
True
True
True
False

$x 和 $y(或“write-output”和“return”)之间有什么区别,这意味着它们都是哈希表,但其中只有一个“-is”是 pscustomobject?除了明显检查变量中的每个哈希表是否也是 pscustomobject)之外,是否有一种通用的方法可以确定与代码的区别?

我的 $PSVersionTable 如下所示,以防此行为特定于 PowerShell 的特定版本:

Name                           Value
----                           -----
PSVersion                      5.1.16299.492
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.16299.492
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

干杯,

M

【问题讨论】:

    标签: powershell syntax psobject pscustomobject


    【解决方案1】:

    return[pscustomobject] 在某种程度上是红鲱鱼。

    归根结底是:

    • 隐式表达式输出与cmdlet产生的输出;使用return(没有cmdlet调用)属于前一类,使用Write-Output属于后者。

    • 包裹的输出对象 - 几乎不可见 - [psobject] 实例仅在 cmdlet 中产生 - 产生的输出。

    # Expression output: NO [psobject] wrapper:
    @{ "aaa" = "bbb" } -is [psobject] # -> $False
    
    # Cmdlet-produced output: [psobject]-wrapped
    (Write-Output @{ "aaa" = "bbb" }) -is [psobject]  # -> $True
    

    请注意 - 令人惊讶的是 - [pscustomobject][psobject] 相同:它们都引用类型 [System.Management.Automation.PSObject],这是 PowerShell 在幕后使用的通常不可见的帮助器类型。 (更令人困惑的是,一个单独的 [System.Management.Automation.PSCustomObject] 类型。)

    在大多数情况下,这个额外的 [psobject] 包装器是良性的 - 它的行为主要与被包装的对象直接相同 - 但在某些情况下它会导致细微的不同行为(见下文)。


    除了明显检查变量中的每个哈希表是否也是 pscustomobject 之外,是否有一种通用的方法可以确定与代码的区别

    请注意,哈希表不是 PS 自定义对象 - 由于[pscustomobject][psobject] 相同,因此它只出现在 - 任何 - [psobject]-wrapped 对象中。

    要检测真正的 PS 自定义对象 - 使用 [pscustomobject] @{ ... }New-Object PSCustomObject / New-Object PSObject 创建或由 Select-ObjectImport-Csv 等 cmdlet 生成 - 使用:

    $obj -is [System.Management.Automation.PSCustomObject] # NOT just [pscustomobject]!
    

    请注意,自 Windows PowerShell v5.1 / PowerShell Core v6.1.0 起,将相关的 -as 运算符与真正的 PS 自定义对象一起使用已被破坏 - 请参见下文。

    作为一个额外的[psobject] 包装器是良性的情况的示例,您仍然可以直接测试包装对象的类型:

    (Write-Output @{ "aaa" = "bbb" }) -is [hashtable]  # $True
    

    也就是说,尽管有包装器,-is 仍然可以识别 wrapped 类型。 因此,有点自相矛盾的是,both -is [psobject]-is [hashtable] 在这种情况下都返回 $True,即使这些类型不相关。


    这些差异没有充分的理由,它们给我的印象是抽象的漏洞(实现):内部构造意外地从幕后偷看。

    以下 GitHub 问题讨论了这些行为:

    【讨论】:

    • 太棒了 - 感谢您的全面回答。我对 PowerShell 的包装器有点了解,但这解释了我所看到的“奇怪”。就我而言,我已将 $x -is [pscustomobject] 更改为 $x -is [System.Management.Automation.PSCustomObject],它的行为符合我现在的预期。
    • 这个解释值1k分。我一直在为为什么将 Write-Host(标准输出)作为函数返回而苦苦挣扎。所以现在它明确了编写函数的两种选择:隐式与 cmd-let 输出。太棒了!
    • 我很高兴听到这个答案很有帮助,@LeonardoT;也许这只是一个错字,但请注意,一方面是隐式表达式输出,另一方面是Write-Output,而不是Write-Host - 后者绕过成功输出流( PowerShell 等效于标准输出)并直接写入主机(控制台) - 请参阅 this answer。简而言之,很少需要显式使用Write-Output - implicit 输出不仅更简洁而且更快。
    • 另一个区别是 return 结束函数:function Invoke-X{ write-output "output"; return "return" }(返回带有outputreturn的数组)与function Invoke-Y{ return "return"; write-output "output" }(返回字符串return
    • @sschoof 是的,return 是无条件退出封闭函数或脚本块或脚本的流控制语句 - 请参阅 about_Returnreturn <some-command-or-expression> 只是 <some-command-or-expression>; return 的语法糖。
    【解决方案2】:

    另请注意,添加Write-Output 调试消息,与.net 不同,会将返回类型更改为数组。添加写入行会破坏函数。

    function Invoke-X {
        $o1 = [pscustomobject] @{ foo = 1, 2 }
        return $o1
    }
    
    function Invoke-Y {
    
        $o1 = [pscustomobject] @{ foo = 1, 2 }
        Write-Output "Debug messageY"
        return $o1
     }
    
    function Invoke-Z {
        $o1 = [pscustomobject] @{ foo = 1, 2 }
        Write-Output "Debug messageZ"
        return ,$o1
     }
    
    $x = Invoke-X;
    $y = Invoke-Y;
    $z = Invoke-Z;
    
    Write-Host
    Write-Host "X  Type: " $x.GetType().FullName $x.foo
    Write-Host
    Write-Host "Y  Type: " $y.GetType().FullName
    Write-Host "Y0 Type: " $y[0].GetType().FullName $y[0]
    Write-Host "Y1 Type: " $y[1].GetType().FullName $y[1].foo
    Write-Host
    Write-Host "Z  Type: " $z.GetType().FullName
    Write-Host "Z0 Type: " $z[0].GetType().FullName $z[0]
    Write-Host "Z1 Type: " $z[1].GetType().FullName $z[1].foo
    
    

    给予:

    X 类型:System.Management.Automation.PSCustomObject 1 2 Y 类型:System.Object[] Y0 类型:System.String 调试消息Y Y1 类型:System.Management.Automation.PSCustomObject 1 2 Z 类型:System.Object[] Z0 类型:System.String 调试信息Z Z1 类型:System.Management.Automation.PSCustomObject 1 2

    【讨论】:

    • 如果您想编写调试消息,最好使用Write-DebugWrite-Host,而设计 Write-Output 将值发送到管道(请参阅@ 987654321@) 这实际上意味着它成为函数返回值的一部分。
    猜你喜欢
    • 2020-04-06
    • 2021-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-15
    • 1970-01-01
    • 2020-12-20
    相关资源
    最近更新 更多