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

bkkx9g8r  于 2023-03-23  发布在  Shell
关注(0)|答案(1)|浏览(152)

我试图弄清楚是什么决定了一个值是否从PowerShell函数返回,我遇到了一些奇怪的事情。about_return文档说:
在PowerShell中,每个语句的结果都作为输出返回,即使没有包含Return关键字的语句。
但这似乎忽略了细节。如果我运行这个:

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

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

1
True
False
0
2

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

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

退货:

1
2
123
123

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

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

退货:

1
1
2

现在, Package 赋值语句会导致它被返回,而 Package $n++会导致它不被返回。
总之,我只想知道一种简单的方法,可以查看函数中的一行代码,并确定它是否会导致返回值。

bkhjykvo

bkhjykvo1#

本节讨论示例函数中的特定语句。
背景信息见底部。

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

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

iRon表示感谢。

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

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

primary 输出流是success 输出流**(编号为1),用于传输 data*,only it 默认情况下通过pipeline发送,因此默认情况下只有 it 被捕获到变量中,被抑制或重定向到文件。

写入成功输出流有两种方式,即产生 data output

显式地,使用*Write-Output调用-尽管很少需要**。
**通常 * 隐式 ,即不捕获、抑制或重定向表达式、命令或语言语句产生的输出

  • 换句话说:*任何 command(如Get-ChildItem *.txt)或 expression(如1 + 2(42).ToString('x')),甚至 language statement作为一个整体 (如foreach ($i in 0..2) { 1 + $i })的输出,默认都会发送到success输出流

  • 与传统的编程语言不同,return * 不需要 * 产生输出-事实上,它的主要目的是 * 独立地 * 退出封闭作用域的任何输出,尽管作为一种*语法便利你可以 * 合并 * 这两个方面:

  • return <command-or-expression>实际上与下面的 two 语句相同,第一个语句(可能)产生输出,第二个语句退出作用域:<command-or-expression>; return

  • 这种 * 隐式输出行为 * 很方便,并且允许简洁,富有表现力的代码,但也可能是一个陷阱很容易 * 意外地 * 产生输出-通常来自不需要返回值的.NET方法(参见this question的示例)。

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

  • This answer显示了可用于当前可用功能的故障排除技术
    对于assignments-例如$n = 1; $n += 1; ++$n; $n--

  • 默认情况下,它们不 * 产生输出

  • hybrid case是多重赋值的链式形式,例如$a = $b = 1,它将1赋值给 both 变量:statement-internally 传递赋值,但语句 * 作为一个整体 * 没有输出。

  • 但是,作为 * 选择加入 *,您可以让它们通过 * 通过(...)(分组操作符)传递所分配的值;例如($n = 1)1赋值给变量$n 输出1,这允许它参与更大的表达式,例如($n = 1) -gt 0

  • 请注意,相关的$(...)(子表达式运算符)和@(...)(数组子表达式运算符)没有这种效果-它们 Package 了一个或多个完整的语句,而不会影响所包含语句的内在输出行为;例如,$($n = 1)不产生输出,因为$n = 1本身不产生输出;但是,$(($n = 1)) * 会 *,因为($n = 1)本身会。
    *至于输出 * 枚举行为

  • 默认情况下,PowerShell * 会枚举正在输出的 * collections,以 streaming 输出的精神:也就是说,它将集合的 * 元素 * 发送到管道, 一个接一个**。

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

  • , $collection(sic;使用辅助的单元素 Package 器数组)

  • 更明确,但效率较低:Write-Output -NoEnumerate $collection

  • 有关详细信息,请参见this answer
    对于输出$null
    $null * 是 * 到管道的输出,但默认情况下 * 不显示

  • $null本身不产生 * 可见 * 输出,

  • 但是下面的代码返回$true,说明值 was sent:

$null | ForEach-Object { $null -eq $_ } # -> $true
  • 请注意,PowerShell还有一个“array-valued $null“值,用于表示命令的 lack of output,技术上表示为[System.Management.Automation.Internal.AutomationNull]::Value singleton。expression 上下文中,此值与$null相同,但是pipeline 中,它的行为就像一个 enumerable without elements,因此通过pipeline不发送任何东西**-有关更多信息,请参阅this answer
    至于 * 抑制 (丢弃)不需要的输出/ * 重定向到文件
    *抑制语句的 success 输出的最佳通用方法是assign to $null$null = ...);例如:
# .Add() outputs a value, which is usually unwanted.
 $null = ($list = [System.Collections.ArrayList]::new()).Add('hi')
  • 参见this answer以获得讨论和替代方案。
  • 注意:下面讨论了输出 suppression,通过$null作为重定向目标,但类似地适用于将输出重定向到 file,通过指定文件名或路径作为目标。
  • 要**选择性地抑制 * 不同的 * 输出流,请在>$null前面加上其number;例如3>$null**抑制警告流输出。
  • 抑制 * 所有 * 流的输出(在 * 外部程序 * 的情况下,这包括stdout和stderr),请使用重定向***>$null**。
    至于 * 合并 * 输出流
    *只有 success 输出流(流号1)才能合并到
  • 您可以选择性地 * 合并一个或多个输出流(例如**2>&1和/或3>&1),或者合并 * 所有 (其他)*>&1
  • 在合并后的成功输出流中,通过检查对象的类型,您仍然可以识别给定对象来自哪个(不成功)流;例如,error 流对象(流2)是[System.Management.Automation.ErrorRecord]示例-有关更多信息,请参见this answer
    关于 * 绕过 * PowerShell的流系统:
    Out-Host[Console]::WriteLine()调用 * 绕过 * PowerShell的输出流,直接写入主机/控制台(终端)。( 主机 * 是任何托管PowerShell引擎的环境,* 通常 *,但不一定是控制台(终端);其他主机的示例是PowerShell SDK和PowerShell中使用的主机remoting)。
  • 因此,无法从PowerShell内部捕获、抑制或重定向它们的输出
    ***Write-Host***以前 * 无条件绕过PowerShell的输出流,默认情况下 * 仍然会转到主机 *,但是-自PowerShell版本5以来-通过 * 信息流 *(流编号6)路由其输出,其中它 * 可以 * 按需捕获/重定向-请参阅this answer了解更多信息。
    *输出 * 格式
  • 如果输出未被捕获、抑制或重定向,则默认情况下会将其发送到主机(控制台),并在主机(控制台)中根据PowerShell丰富且可自定义的 * 显示输出格式系统*进行渲染。有关简要概述,请参阅this answer
  • 请注意,结果表示是为 * 人类 * 观察者而设计的,而不是为 * 编程处理 * 设计的。虽然PowerShell在实际 * 数据 * 和 * 其表示*之间保持了明确的分离,但警告是,在以下情况下,您最终只会得到用于显示的字符串表示:
  • 使用Out-File或其有效别名时,重定向运算符>>>
  • 当你(隐式地)将输出发送到 * 外部世界 * 时(外部调用者的stdout流-见下文)。
  • 在这两种情况下,如果需要后续的编程处理,则必须将数据转换为 * 结构化 * 文本格式,例如CSV(Export-CsvConvertTo-Csv)或JSON(使用ConvertTo-Json)。
    至于 * 外界 * 如何看待PowerShell的输出流:
  • 操作系统级别的IPC(进程间通信)只知道 * 两个 * 输出流:stdout(标准输出)和 stderr(标准错误),强制PowerShell将其6个输出流Map到这两个流上,以便将流输出返回给外部调用者。
  • 虽然将PowerShell的success输出流Map到stdout并将 * 所有其他流 * Map到stderr是有意义的,不幸的是, 所有 * 流都是通过 stdout 报告的,默认情况下,PowerShell 7.2*-尽管在调用过程中有选择地重定向stderr(通常使用2>)* 确实 * 将PowerShell的 * 错误流 *(仅)发送到该重定向目标。有关详细信息,请参阅this answer的底部部分。
  • 另外请注意,PowerShell从7.2版开始仅通过 text(字符串)外部调用者以及从PowerShell会话内部调用的外部程序进行通信,这意味着可能会出现字符编码问题-有关详细信息,请参阅this answer

[1]请注意,PowerShell没有 input 流的概念,因此也 * 不 * 支持与其他shell相似的stdin重定向操作符<。(仅)* 通过管道 *.为了接收 * 来自外界 * 的数据,通过PowerShell CLI的stdin流,必须使用自动变量$input-参见this answer
[2]使用>(或>>)重定向到文件有效地在幕后使用Out-File cmdlet,因此 * 它的 * 默认字符编码为“Unicode”(UTF-16 LE),以及 PowerShell中无BOM的UTF-8(Core)7+。但是,在PowerShell版本5.1及更高版本中,您可以通过$PSDefaultParameterValues首选项变量控制此编码-请参阅this answer

相关问题