打印命令输出而不是在 Powershell 中捕获

Command output printed instead of captured in Powershell

一般来说,我知道如何在 Powershell 中将命令的输出捕获到变量中:

> $output = python3 --version
> Write-Host $output
Python 3.7.0

Python 3 打印它的版本,但它被捕获到变量 $output 中,控制台上没有显示任何内容。不出所料:

> $output = python3 --version
> Write-Host $output
Python 3.7.0

但是在Python2的情况下,就不行了。 Python 2 打印其版本,但它显示在控制台上而不是捕获,并且变量 $output 为空。

> $output = python2 --version
Python 2.7.15
> Write-Host $output

>

我尝试了一些替代语法,但到目前为止没有任何帮助:

> $output = (python2 --version)  # use a subcommand
Python 2.7.15
> $output = "$(python2 --version)"  # enclose the subcommand into a string
Python 2.7.15

如果我在 Write-Command 中使用它,输出如下所示:

> Write-Host "Python version: $(python2 --version)"
Python 2.7.15
Python version:
>

Python 2 如何打印其未捕获到变量中的版本,是否有可能捕获它?

这是由 Python 2 造成的,它将版本打印到 stderr 而不是 stdout。程序调用输出的分配仅捕获 stdout,在本例中为空。另一方面,默认情况下 stderr 会打印到控制台,这就是 $output 变量为空并且版本会打印到控制台的原因。详情见下文。

tl;博士:

# Redirect stderr to the success output stream and convert to a string.
$output = (python --version 2>&1).ToString()
# $? is now $True if python could be invoked, and $False otherwise

对于可能 return 多个 stderr 行 的命令,使用:

  • PSv4+,使用.ForEach() 方法:

    $output = (python --version 2>&1).ForEach('ToString')
    
  • PSv3-,使用 ForEach-Object cmdlet(其内置别名是 %):

    # Note: The (...) around the python call are required for
    #       $? to have the expected value.
    $output = (python --version 2>&1) | % { $_.ToString() }
    
    # Shorter PSv3+ equivalent, using an operation statement
    $output = (python --version 2>&1) | % ToString
    

  • 正如 Mathias R. Jessen 在对问题的评论中指出的那样,您自己在版本方面进行了澄清,python 2.x - 令人惊讶的是 - 将其版本信息输出到 stderr 而不是标准输出,不像 3.x.

  • 您无法使用 $output = ... 捕获输出的原因是 分配了一个 外部程序 调用的输出默认情况下,变量仅捕获其 stdout 输出

    • 使用 PowerShell 本地命令,它捕获命令的成功输出流 - 参见about_redirection
  • 主要修复是使用重定向 2>&1 合并 PowerShell 的错误流(PowerShell 对 stderr 的模拟,stderr 输出映射到 if redirected) 到 PowerShell 的成功输出流(stdout 模拟),然后在变量中捕获。
    但是,这有两个副作用:

    • 由于 2>&1 重定向现在涉及 PowerShell 的错误流(默认情况下,stderr 输出 传递到控制台 ), $? 总是设置为 $False,因为向错误流写入任何内容都会这样做(即使错误流最终被发送到其他地方,如本例所示) .

      • 请注意 $? 而不是 设置为 $False 没有 重定向 (除了在 ISE 中,它是 an unfortunate discrepancy),因为 stderr 输出永远不会 "touches" PowerShell 的错误输出流。
        在 PowerShell 控制台中,在没有 2> 重定向的情况下调用外部程序时,$? 设置为 $False 只有 是如果它的退出代码非零$LASTEXITCODE -ne 0)。

      • 因此,使用 2>&1 解决了一个问题(无法捕获输出),同时引入了另一个问题($? 错误地设置为 $False)- 请参阅下面的补救措施。

    • 写入成功输出流的不是字符串,而是[System.Management.Automation.ErrorRecord]实例,因为 PowerShell 将每个 stderr 输出行包装在一个中。

    • 如果您仅在 string 上下文中使用这些实例 - 例如,"Version: $output",您可能不会注意到或关心,但是。

  • .ToString()(对于单个输出行)和 .ForEach('ToString') / (...) | % ToString(对于可能的多个输出行)撤消两个副作用:

    • 他们将 [System.Management.Automation.ErrorRecord] 实例转换回 strings

    • 他们将$?重置为$True,因为.ToString()/.ForEach()调用/使用(...)命令是表达式,它们本身被认为是成功的,即使它们包含命令,它们本身将$?设置为$False

注:

  • 如果无法找到可执行文件 python,PowerShell 本身将抛出语句终止错误,这意味着对 $output 的赋值将被 跳过(它的值,如果存在的话,不会改变),默认情况下你会得到嘈杂的错误输出并且 $? 将被设置为 $False.

  • 如果python 可以被调用并且报告一个错误(这不太可能与--version),如非零 退出代码 所示,您可以通过自动 $LASTEXITCODE 变量检查该退出代码(其值直到下一次调用外部程序).

  • 像在命令周围放置 (...) 这样简单的东西使得 $? 总是 return $True (除非语句或脚本 -发生终止错误)在上述方法中被利用,但通常是可能被认为有问题的模糊行为 - 参见 this discussion on GitHub.