PowerShell 函数是否返回值由什么决定?

Car*_*ate 11 powershell function powershell-7.0

我试图弄清楚是什么决定了是否从 PowerShell 函数返回一个值,并且我遇到了一些奇怪的情况。about_return 文档说:

在 PowerShell 中,每个语句的结果都会作为输出返回,即使没有包含 Return 关键字的语句也是如此。

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

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

运行此命令将返回一个数组(并打印“Hello”):

1
True
False
0
2
Run Code Online (Sandbox Code Playgroud)

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

function My-Function {
    $n = 1
    $n
    $n++
    ($n++)
    -join @(1, 2, 3)
    (-join @(1, 2, 3))
}
Run Code Online (Sandbox Code Playgroud)

返回:

1
2
123
123
Run Code Online (Sandbox Code Playgroud)

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

function My-Function {
    ($n = 1)
    $n
    $n++
    ($n++)
}
Run Code Online (Sandbox Code Playgroud)

返回:

1
1
2
Run Code Online (Sandbox Code Playgroud)

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

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

mkl*_*nt0 16

本节讨论示例函数中的特定语句。
请参阅下一节了解背景信息。

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

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

向iRon的帮助表示敬意。

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

也就是说,脚本或函数中的任何语句(因此可能有多个语句)都可以写入任何输出流

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

两种方法可以写入成功输出流,即产生数据输出

  • 明确地,通过Write-Output调用(echo是其内置别名)——尽管很少需要这样做

  • 通常是隐式的,既不捕获、抑制也不重定向表达式、命令或语言语句生成的输出

    • 换句话说:默认情况,任何命令(例如,Get-ChildItem *.txt)或表达式(例如,1 + 2(42).ToString('x'))甚至整个语言语句(例如,foreach ($i in 0..2) { 1 + $i })的输出都会发送到成功输出流

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

      • return <command-or-expression>实际上与以下两个语句相同,其中第一个语句(可能)产生输出,第二个语句退出作用域:<command-or-expression>; return
    • 这种隐式输出行为很方便,并且允许简洁、富有表现力的代码,但也可能是一个陷阱很容易意外地生成输出- 通常来自不需要返回值的 .NET 方法(请参阅此问题以获取示例) )。

      • iRon 的GitHub 功能请求 #15781讨论了解决此问题的一种潜在方法:引入选择加入严格模式,仅允许使用显式输出语句 ( Write-Output, return) 来生成输出。

      • 此答案显示了可与当前可用功能一起使用的故障排除技术。

至于作业- 例如$n = 1; $n += 1; ++$n; $n--

  • 默认情况下它们产生输出
    • 混合情况是多重赋值的链接形式,例如$a = $b = 1,它分配1两个变量:语句-在内部 传递赋值值,但整个语句没有输出。
  • 但是,作为选择加入,您可以让它们传递通过via(...)分组运算符)分配的值;例如($n = 1),两者都分配1给变量$n 输出1,这允许它参与更大的表达式,例如($n = 1) -gt 0
    • 请注意,相关的$(...)(子表达式运算符) 和@(...)(数组子表达式运算符)没有这种效果 - 它们包装一个或多个完整语句,而不影响所包含语句的内在输出行为;例如$($n = 1)不产生输出,因为它本身不产生输出;然而,确实如此,因为它本身确实如此。$n = 1$(($n = 1)) ($n = 1)

至于输出枚举行为

  • 默认情况下,PowerShell本着流输出的精神枚举正在输出的集合:也就是说,它将集合的元素一一发送到管道

  • 在极少数情况下,您确实需要输出整个集合- 通常应该避免这种情况,以免混淆参与管道的其他命令,这些命令通常需要逐个对象输入 - 您有两种选择:

    • , $collection(原文如此;使用辅助单元素包装数组)
    • 更明确,但效率较低:Write-Output -NoEnumerate $collection
    • 请参阅此答案以获取更多信息。

至于输出$null

  • $null 输出到管道,但默认情况下不显示

  • 请注意,PowerShell 还有一个“数组值$null”值,用于表示命令缺少输出[System.Management.Automation.Internal.AutomationNull]::Value,该值在技术上表示为单例。表达式上下文中,此值的处理方式与 相同$null,但管道中,它的行为就像没有元素的可枚举,因此不会通过管道发送任何内容-有关更多信息,请参阅此答案。

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

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

     # .Add() outputs a value, which is usually unwanted.
     $null = ($list = [System.Collections.ArrayList]::new()).Add('hi')
    
    Run Code Online (Sandbox Code Playgroud)
    • 请参阅此答案以获取讨论和替代方案。
  • 注意:下面讨论输出抑制,通过$null作为重定向目标,但类似地适用于通过指定文件名或路径作为目标将输出重定向到文件。[2]

    • 选择性地抑制不同的输出流,请>$null使用其编号作为前缀;例如,3>$null抑制警告流输出。

    • 要抑制所有的输出(在外部程序的情况下涵盖 stdout 和 stderr),请使用重定向*>$null

至于合并输出流

  • 只有成功的输出流(流号1)才能合并到.
  • 您可以有选择地将一个或多个输出流合并到其中(例如2>&1和/或3>&1),或合并所有(其他)*>&1
  • 在生成的合并成功输出流中,您仍然可以通过检查给定对象的类型来识别给定对象来自哪个(非成功)流;例如,错误流对象(stream 2)是[System.Management.Automation.ErrorRecord]实例 -有关更多信息,请参阅此答案。

至于绕过PowerShell的流系统:

  • Out-Host并且[Console]::WriteLine()调用绕过PowerShell的输出流并直接写入主机/控制台(终端)。(主机是托管 PowerShell 引擎的任何环境,通常是但不一定是控制台(终端);其他主机的示例包括PowerShell SDK和 PowerShell远程处理中使用的主机)。

    • 因此,无法从 PowerShell 内部捕获、抑制或重定向它们的输出
  • Write-Host 以前无条件绕过 PowerShell 的输出流,默认情况下仍会转到主机,但是 - 自 PowerShell 版本 5 - 通过信息流(流编号)路由其输出6可以根据需要捕获/重定向 - 请参阅此答案以获取更多信息。

至于输出的格式如何:

  • 如果未捕获、抑制或重定向输出,则默认情况下会将其发送到主机(控制台),并根据PowerShell 丰富且可自定义的显示输出格式化系统进行呈现。请参阅此答案以获得简洁的概述。

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

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

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

  • 操作系统级别的 IPC(进程间通信)只知道两个输出流:stdout(标准输出)和stderr(标准错误),这迫使 PowerShell 将其 6 个输出流映射到这两个输出流,以便将流式输出返回到外部呼叫者。

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

  • 另请注意,自版本 7.2 起,PowerShell 仅通过文本(字符串)外部调用者以及从 PowerShell 会话内部调用的外部程序进行通信,这意味着可能会出现字符编码问题- 请参阅此答案以获取更多信息。


[1] 请注意,PowerShell 本身没有输入流的概念,因此也不支持其他 shell 所熟悉的stdin 重定向运算符<。相反,命令(仅)通过 pipeline接收流输入。为了通过 PowerShell CLI的 stdin 流接收来自外界的数据,必须使用自动变量- 请参阅此答案$input

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


归档时间:

查看次数:

2176 次

最近记录:

2 年,5 月 前