PowerShell中函数中的"Write-Output"和"return"有什么区别?

25

我使用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?
如果这种行为特定于PowerShell的特定版本,则我的$PSVersionTable如下:
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

2个回答

29

return[pscustomobject]在某种程度上是误导性的信息。

重点是:

  • 隐式表达式输出 vs. cmdlet生成的输出;使用return(不带cmdlet调用)属于前者类别,使用Write-Output属于后者。

  • 输出对象在cmdlet生成的输出中只会被 - 大多数情况下不可见的 - [psobject]实例所包装。

# 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] 包装器是无害的 - 它基本上表现得像直接包装的对象一样 - 但也有一些情况会导致微妙的不同行为(见下文)。


注意,散列表并不是一个PS自定义对象 - 只有由于[pscustomobject]与[psobject]相同,对于任何[psobject]包装对象才会出现这种情况。

要检测真正的PS自定义对象-使用[pscustomobject]@{...}New-Object PSCustomObject/ New-Object PSObject创建或由cmdlet(如Select-Object和Import-Csv)生成时,请使用:

是否有一般化的方法可以确定代码的差异,而不仅仅是检查我在变量中拥有的每个散列表是否也是一个PSCustomObject?

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

请注意,使用相关的-as运算符与真正的PS自定义对象在Windows PowerShell v5.1 / PowerShell Core v6.1.0中已经失效 - 请参见下文。
作为一个额外[psobject]包装是无害的情况示例,您仍然可以直接测试包装后的对象类型:
(Write-Output @{ "aaa" = "bbb" }) -is [hashtable]  # $True

也就是说,尽管有包装器,-is 仍然能够识别 被包装的 类型。 因此,有点自相矛盾的是,在这种情况下,两个 表达式 -is [psobject]-is [hashtable] 都会返回 $True,即使这些类型是不相关的。

这些差异没有充分的理由,它们让我感觉像是漏洞的抽象实现:内部结构意外地从幕后窥视出来。

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


1
太棒了 - 感谢您提供全面的答案。我有一点知道 PowerShell 的包装器,但这解释了我看到的“奇怪”行为。在我的情况下,我已将 $x -is [pscustomobject] 更改为 $x -is [System.Management.Automation.PSCustomObject],现在它表现出我预期的行为。 - mclayton
这个解释值得1000分。我一直在为什么Write-Host(标准输出)被视为函数返回而苦恼。现在清楚了编写函数的两种替代方法:隐式和cmd-let输出。太棒了! - Leonardo T
很高兴听到答案对您有帮助,@LeonardoT;也许这只是一个笔误,但请注意区别在于一方面是隐式表达输出,另一方面是“Write-Output”,而不是“Write-Host”——后者会绕过成功的输出流(PowerShell中标准输出的等效物)并直接写入主机(控制台)——请参见此答案。简而言之,明确使用“Write-Output”的必要性很少——“隐式”输出不仅更简洁,而且更快。 - mklement0
1
另一个不同点是 return 结束了函数: function Invoke-X{ write-output "output"; return "return" }(返回包含 outputreturn 的数组) 与 function Invoke-Y{ return "return"; write-output "output" }(返回字符串 return)。 - sschoof
2
@sschoof 是的,return 是一种流程控制语句,它无条件地退出封闭的函数、脚本块或脚本 - 请参阅 about_Returnreturn <some-command-or-expression> 只是 <some-command-or-expression>; return 的语法糖。 - mklement0

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

4
如果你想要编写调试信息,最好使用 Write-Debug 或者 Write-Host,而 Write-Output 原本的设计是将值发送到管道中(详见 https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/write-output?view=powershell-7.1),这意味着它实际上成为函数返回值的一部分。请注意,不要改变原文意思。 - mclayton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接