在Bash中捕获stdout和stderr

dje*_*lin 41 bash stdout pipe stream stderr

我知道这个语法

var=`myscript.sh`
Run Code Online (Sandbox Code Playgroud)

要么

var=$(myscript.sh)
Run Code Online (Sandbox Code Playgroud)

将捕获结果(stdout)myscript.sh转化为var.我可以重定向stderrstdout,如果我想同时捕获.如何将它们中的每一个保存为单独的变量?

我在这里的用例是如果返回代码非零,我想要回显stderr并禁止其他方式.可能有其他方法可以做到这一点,但这种方法似乎可行,如果它实际上是可能的.

gni*_*urf 44

有一个非常丑陋的方式来捕捉stderrstdout在没有临时文件两个单独的参数(如果你喜欢管道),使用过程中替换,sourcedeclare适当的.我会打电话给你banana.您可以使用函数模仿这样的命令:

banana() {
    echo "banana to stdout"
    echo >&2 "banana to stderr"
}
Run Code Online (Sandbox Code Playgroud)

我假设你想要的标准输出banana变量bout的标准误差banana的变量berr.这是实现这一目标的魔力(仅限Bash≥4):

. <({ berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
Run Code Online (Sandbox Code Playgroud)

那么,这里发生了什么?

让我们从最内层的术语开始:

bout=$(banana)
Run Code Online (Sandbox Code Playgroud)

这只是分配给bout标准输出的标准方法banana,即终端上显示的标准错误.

然后:

{ bout=$(banana); } 2>&1
Run Code Online (Sandbox Code Playgroud)

仍将分配给boutstdout banana,但stderr banana通过stdout显示在终端上(由于重定向2>&1.

然后:

{ bout=$(banana); } 2>&1; declare -p bout >&2
Run Code Online (Sandbox Code Playgroud)

将做如上述,但也将在终端显示(通过stderr)上的内容boutdeclare内置:这将很快重新使用.

然后:

berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr
Run Code Online (Sandbox Code Playgroud)

将分配给berrstderr banana并显示berrwith 的内容declare.

此时,您将在终端屏幕上看到:

declare -- bout="banana to stdout"
declare -- berr="banana to stderr"
Run Code Online (Sandbox Code Playgroud)

与线

declare -- bout="banana to stdout"
Run Code Online (Sandbox Code Playgroud)

通过stderr显示.

最后的重定向:

{ berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1
Run Code Online (Sandbox Code Playgroud)

将通过stdout显示之前的内容.

最后,我们使用一个进程替换这些行的内容.


你也提到了命令的返回码.更改banana到:

banana() {
    echo "banana to stdout"
    echo >&2 "banana to stderr"
    return 42
}
Run Code Online (Sandbox Code Playgroud)

我们还将banana在变量中bret包含返回代码,如下所示:

. <({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1)
Run Code Online (Sandbox Code Playgroud)

您也可以不使用源代码和使用进程替换eval(并且它也适用于Bash <4):

eval "$({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1)"
Run Code Online (Sandbox Code Playgroud)

而这一切是安全的,因为只有东西我们source荷兰国际集团和eval荷兰国际集团从获得declare -p而且将永远是正确转义.


当然,如果你想要一个数组中的输出(例如,mapfile如果你使用Bash≥4-否则mapfilewhile- read循环替换),适应是直截了当的.

例如:

banana() {
    printf 'banana to stdout %d\n' {1..10}
    echo >&2 'banana to stderr'
    return 42
}

. <({ berr=$({ mapfile -t bout < <(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
Run Code Online (Sandbox Code Playgroud)

并带有返回码:

. <({ berr=$({ mapfile -t bout< <(banana; bret=$?; declare -p bret >&3); } 3>&2 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
Run Code Online (Sandbox Code Playgroud)

  • 很棒的答案 - 这是如此强大! (4认同)
  • 所以你基本上动态生成源代码![巧妙](https://gist.github.com/AquariusPower/ee2b7fc44e674296a324)!thx vm! (2认同)

zb'*_*zb' 29

没有临时文件就无法捕获它们.

您可以将stderr捕获到变量并将stdout传递给用户屏幕(此处的示例):

exec 3>&1                    # Save the place that stdout (1) points to.
output=$(command 2>&1 1>&3)  # Run command.  stderr is captured.
exec 3>&-                    # Close FD #3.

# Or this alternative, which captures stderr, letting stdout through:
{ output=$(command 2>&1 1>&3-) ;} 3>&1
Run Code Online (Sandbox Code Playgroud)

但是没有办法捕获stdout和stderr:

你不能做的是在一个变量中捕获stdout,在另一个变量中捕获stderr,仅使用FD重定向.您必须使用临时文件(或命名管道)来实现该文件.

  • 我反对!有一种方法可以同时捕获 stdout 和 stderr。[您可以仅通过 FD 重定向将 stdout 捕获到一个变量中,并将 stderr 捕获到另一个变量中。无需使用一些临时文件或命名管道](/sf/answers/2874874691/)。不过唯一的缺点就是有点丑。 (2认同)

anu*_*ava 15

你可以做:

OUT=$(myscript.sh 2> errFile)
ERR=$(<errFile)
Run Code Online (Sandbox Code Playgroud)

现在$OUT将具有脚本的标准输出并且$ERR具有脚本的错误输出.


jof*_*fel 7

一种简单但不优雅的方法:将stderr重定向到临时文件,然后将其读回:

TMP=$(mktemp)
var=$(myscript.sh 2> "$TMP")
err=$(cat "$TMP")
rm "$TMP"
Run Code Online (Sandbox Code Playgroud)


小智 7

虽然我还没有找到一种方法来捕获 stderr 和 stdout 以在 bash 中分隔变量,但我将两者发送到同一个变量 \xe2\x80\xa6

\n\n
result=$( { grep "JUNK" ./junk.txt; } 2>&1 )\n
Run Code Online (Sandbox Code Playgroud)\n\n

\xe2\x80\xa6 然后我检查退出状态 \xe2\x80\x9c$?\xe2\x80\x9d,并对 $result 中的数据采取适当的操作。

\n