什么决定一个值是否从 PowerShell 函数返回?

What decides if a value is returned from a PowerShell function?

我试图弄清楚是什么决定了是否从 PowerShell 函数返回一个值,我 运行 遇到了一些奇怪的事情。 about_return docs 说:

In PowerShell, the results of each statement are returned as output, even without a statement that contains the Return keyword.

但这似乎掩盖了细节。如果我 运行 这个:

function My-Function {
    1
    [System.Console]::WriteLine("Hello")
    $null
    $true
    $false
    0
    2
}

运行 这个 returns 数组(连同打印“Hello”):

1
True
False
0
2

这意味着 $null 不会自动返回。然后我尝试递增,因为我在函数中使用它:

function My-Function {
    $n = 1
    $n
    $n++
    ($n++)
    -join @(1, 2, 3)
    (-join @(1, 2, 3))
}

Returns:

1
2
123
123

所以,返回了 $n$n++,但没有返回 ($n++)?但是当与另一个运算符 (-join) 相比时,它在每种情况下都是相同的。为什么在 $n++ 周围加上括号会阻止它被返回,为什么其他运算符的行为不同?这更加令人困惑,因为 = 运算符似乎以相反的方式工作:

function My-Function {
    ($n = 1)
    $n
    $n++
    ($n++)
}

Returns:

1
1
2

现在包装赋值会导致它被返回,而包装 $n++ 会导致它不被返回。

总而言之,我只想知道一种直接的方法来查看函数中的一行代码,并确定它是否会导致返回值。

本节讨论示例函数中的特定语句。
有关背景信息,请参阅底部。

  • $n = 1$n++ 赋值 ,因此 产生输出。
  • $n 是一个 表达式 其值 输出
  • $null - 同上,但即使它是 output,默认情况下它也不会 display[=426] =]
  • ($n++) - 由于 (...) 中的封装 - 将 赋值 转换为 表达式 ,因此 是否 输出分配的值(也)。
    • 然而,因为你使用了post递增形式的赋值,它是old值那是输出,而不是 现在递增的 值;要首先增加 (pre-increment) 和 然后 输出,请使用 (++$n)
  • [System.Console]::WriteLine("Hello") 直接打印到控制台 ,从而 绕过 PowerShell 的输出流系统。
    • 这意味着您无法从 PowerShell 内部捕获或重定向此类输出(请参阅下一节)。

PowerShell 的输出(“return 值”)行为:

iRon 致敬以求他的帮助。

PowerShell,遵循传统 shell 的模型,围绕 streams 组织 - 请参阅概念性 about_Redirection 帮助主题,概述了 PowerShell 支持的所有 6 个流。[1]

也就是说,任何语句 - 因此可能 多个 - 在脚本或函数中可以写入任何输出流 .

主要输出流,旨在传达数据,是成功输出流(其编号为1),而只有是默认情况下通过 pipeline 发送 ,因此默认情况下只有 被捕获到变量中,被抑制或重定向到文件。

两种写入成功输出流的方法,即生成数据输出:

  • 显式,使用Write-Output调用- 尽管很少需要

  • 通常隐式,既不捕获、抑制也不重定向语句产生的输出

    • 换句话说:任何命令(例如Get-ChildItem *.txt)或表达式的输出(例如,1 + 2(42).ToString('x'))默认发送到成功输出流

    • 与传统编程语言不同,return 不需要 来产生输出 - 事实上, 它的主要目的是 独立地 退出范围产生的任何输出的封闭范围,尽管作为 语法便利性 您可以 结合 这两个方面:

      • return <command-or-expression> 实际上与以下 两个 语句相同,其中第一个(可能)产生输出,第二个退出范围:<command-or-expression>; return
    • 这种隐式输出行为很方便,允许简洁、富有表现力的代码,但也可能是一个陷阱很容易意外地产生输出——通常来自不需要return值的.NET方法(见 例如)。

      • iRon 的 GitHub feature request #15781 讨论了一种解决此问题的潜在方法:引入 opt-in 严格模式,仅允许使用 explicit输出语句(Write-Outputreturn)以产生输出。

      • 显示了 故障排除技巧 您可以使用当前可用的功能。

至于 assignments - 例如$n = 1; $n += 1; ++$n; $n--:

  • 默认情况下,它们产生输出
    • A hybrid case 是 multi-assignment 的链接形式,例如$a = $b = 1,它把1赋值给两个变量:语句-内部赋值是通过的,但是语句整体无输出
  • 但是,作为 opt-in,您可以让它们通过 到 [=424] 传递被分配的值=] 通过 (...)grouping operator;例如($n = 1) 都将 1 赋值给变量 $n 输出 1,这允许它参与更大的表达式,例如 ($n = 1) -gt 0
    • 注意相关的 $(...) (subexpression operator) and @(...) (array-subexpression operator)有那种效果——它们包装一个或多个整个语句,而不影响封闭语句的内在输出行为;例如$($n = 1) 产生输出,因为 $n = 1 本身不产生输出;但是,$(($n = 1)) ,因为 ($n = 1) 本身会。

至于输出枚举行为:

  • 默认情况下,PowerShell 枚举 正在输出的集合,本着的精神streaming输出:即它将一个集合的元素发送到管道,一个一个.

  • 在极少数情况下你确实需要输出一个集合作为一个整体 - 通常应该避免这种情况,以免混淆其他命令参与管道,通常会期望 object-by-object 输入 - 您有两个选择:

    • , $collection(原文如此;使用辅助。one-element 包装器数组)
    • 更明确,但效率较低:Write-Output -NoEnumerate $collection
    • 有关详细信息,请参阅

至于输出$null:

  • $null 输出到管道,但是默认不显示.

    • $null 本身不产生 visible 输出,

    • 但是下面的returns $true,证明值发送的:

      $null | ForEach-Object { $null -eq $_ } # -> $true
      
  • 请注意,PowerShell 也有一个“array-valued $null”值,用于表示 缺少命令的输出,在技术上表示为 [System.Management.Automation.Internal.AutomationNull]::Value singleton. In expression contexts, this values is treated the same as $null, but in the pipeline it behaves like an enumerable without elements and therefore sends nothing through the pipeline - see 以获取更多信息。

至于抑制(丢弃)不需要的输出/重定向到文件:

  • 抑制语句成功输出的最佳general-purpose方法是分配给$null ($null = ...);例如:

     # .Add() outputs a value, which is usually unwanted.
     $null = ($list = [System.Collections.ArrayList]::new()).Add('hi')
    
    • 有关讨论和备选方案,请参阅
  • 注意:下面讨论输出 抑制,通过 $null 作为重定向目标,但类似于将输出重定向到 file,通过指定文件名或路径作为目标。[2]

    • 有选择地抑制不同的输出流,在>$null前加上它的number;例如3>$null 抑制警告流输出。

    • 抑制来自所有的输出,对于外部程序 涵盖标准输出和标准错误,使用重定向 *>$null.

至于合并输出流:

  • 只有成功输出流(流号1)可以合并到.
  • 您可以有选择地将一个或多个输出流合并到其中(例如2>&1 and/or 3>&1), 或者 merge all (others): *>&1
  • 在生成的合并成功输出流中,您仍然可以通过检查其类型来识别给定对象来自哪个 (non-success) 流;例如,错误 流对象(流 2)是 [System.Management.Automation.ErrorRecord] instances - see 以获取更多信息。

至于绕过PowerShell的流系统:

  • Out-Host and [Console]::WriteLine() calls bypass PowerShell's output streams and write directly to the host / console (terminal). (A host is any environment that hosts the PowerShell engine, which is typically, but not necessarily a console (terminal); examples of other hosts are the PowerShell SDK and the host used in PowerShell remoting).

    • 因此,他们的输出无法从 PowerShell 内部捕获、抑制或重定向。
  • Write-Host formerly unconditionally bypassed PowerShell's output streams and still goes to the host by default, but - since PowerShell version 5 - routes its output via the information stream (stream number 6), where it can be captured / redirected on demand - see 了解更多信息。

至于输出如何格式化:

  • 如果输出未被捕获、抑制或重定向,默认情况下会将其发送到主机(控制台),并根据 PowerShell 丰富且可自定义的 for-displayoutput-formatting系统。有关简明概述,请参阅 this answer

  • 请注意,生成的表示是为人类观察者设计的,而不是为程序化处理设计的。虽然 PowerShell 在实际 数据 其表示 之间保持明确的分离,但 需要注意的是 在以下情况下您最终只会得到 for-display,字符串表示形式:

    • 当您使用 Out-File 或其有效别名时,重定向运算符 >>>
    • 当您(隐含地)将输出发送到外部世界(一个调用方的标准输出流 - 见下文)。
    • 在这两种情况下,如果以后需要进行程序化处理,则必须将数据转换为结构化文本格式,例如CSV (Export-Csv or ConvertTo-Csv) or JSON (using ConvertTo-Json)。

至于外界如何看待PowerShell的输出流:

  • IPC(inter-process通信)在OS级别只知道两个输出流:stdout(标准输出)和 stderr(标准错误),这会强制 PowerShell 将其 6 个输出流映射到这两个上,以便 return 将输出流式传输到外部来电者。

  • 虽然将 PowerShell 的成功输出流映射到 stdout 并将 所有其他流 映射到 stderr 是有意义的,不幸的是 all 流从 PowerShell 7.2 默认情况下通过 stdout 报告 - 尽管在调用过程中选择性地重定向 stderr(通常使用 2> ) 确实 将 PowerShell 的 错误流 (仅)发送到该重定向目标。有关详细信息,请参阅 this answer 的底部部分。

  • 另请注意,从 7.2 版开始,PowerShell 仅通过 text(字符串)外部调用者以及从PowerShell会话内部调用的外部程序,这意味着character-encoding问题 可能会出现 - 请参阅 了解更多信息。


[1] 请注意,PowerShell 本身没有 input 流的概念,因此 not 支持标准输入重定向运算符 <,这是其他 shell 所熟悉的。相反,命令通过管道 接收流式输入(仅)。为了通过 PowerShell CLI's stdin stream, the automatic $input variable must be used - see .

从外部世界 接收数据

[2] 使用 >(或 >>)重定向到文件有效地使用了幕后的 Out-File cmdlet,因此 its默认字符编码,在WindowsPowerShell中为“Unicode”(UTF-16LE),在BOM-lessPowerShell中为UTF-8 PowerShell(核心)7+。但是,在 PowerShell 5.1 及更高版本中,您可以通过 $PSDefaultParameterValues 首选项变量 控制此编码 - 请参阅 .