在 Bash 中捕获 stdout 和 stderr

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13806626/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 23:05:44  来源:igfitidea点击:

Capture both stdout and stderr in Bash

bashstreampipestdoutstderr

提问by djechlin

I know this syntax

我知道这个语法

var=`myscript.sh`

or

或者

var=$(myscript.sh)

Will capture the result (stdout) of myscript.shinto var. I could redirect stderrinto stdoutif I wanted to capture both. How to save each of them to separate variables?

将捕获into的结果 ( stdout) 。如果我想同时捕获两者,我可以重定向到。如何将它们中的每一个保存到单独的变量中?myscript.shvarstderrstdout

My use case here is if the return code is nonzero I want to echo stderrand suppress otherwise. There may be other ways to do this but this approach seems it will work, if it's actually possible.

我的用例是,如果返回码非零,我想回显stderr和抑制否则。可能还有其他方法可以做到这一点,但如果确实可能,这种方法似乎会奏效。

采纳答案by zb'

There is no way to capture both without temp file.

如果没有临时文件,则无法同时捕获两者。

You can capture stderr to variable and pass stdout to user screen (sample from here):

您可以将 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

But there is no way to capture both stdout and stderr:

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

What you cannot do is capture stdout in one variable, and stderr in another, using only FD redirections. You mustuse a temporary file (or a named pipe) to achieve that one.

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

回答by gniourf_gniourf

There's a really ugly way to capture stderrand stdoutin two separate variables without temporary files (if you like plumbing), using process substitution, source, and declareappropriately. I'll call your command banana. You can mimic such a command with a function:

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

banana() {
    echo "banana to stdout"
    echo >&2 "banana to stderr"
}

I'll assume you want standard output of bananain variable boutand standard error of bananain variable berr. Here's the magic that'll achieve that (Bash≥4 only):

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

. <({ berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)


So, what's happening here?

那么,这里发生了什么?

Let's start from the innermost term:

让我们从最里面的术语开始:

bout=$(banana)

This is just the standard way to assign to boutthe standard output of banana, the standard error being displayed on your terminal.

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

Then:

然后:

{ bout=$(banana); } 2>&1

will still assign to boutthe stdout of banana, but the stderr of bananais displayed on terminal via stdout (thanks to the redirection 2>&1.

仍将分配给bout的标准输出banana,但标准错误banana是通过标准输出显示在终端上(感谢重定向2>&1.

Then:

然后:

{ bout=$(banana); } 2>&1; declare -p bout >&2

will do as above, but will also display on the terminal (via stderr) the content of boutwith the declarebuiltin: this will be reused soon.

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

Then:

然后:

berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr

will assign to berrthe stderr of bananaand display the content of berrwith declare.

将分配给berrstderrbanana并显示berrwith的内容declare

At this point, you'll have on your terminal screen:

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

declare -- bout="banana to stdout"
declare -- berr="banana to stderr"

with the line

与线

declare -- bout="banana to stdout"

being displayed via stderr.

通过 stderr 显示。

A final redirection:

最后重定向:

{ berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1

will have the previous displayed via stdout.

将通过标准输出显示前一个。

Finally, we use a process substitutionto sourcethe content of these lines.

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



You mentioned the return code of the command too. Change bananato:

您也提到了命令的返回码。更改banana为:

banana() {
    echo "banana to stdout"
    echo >&2 "banana to stderr"
    return 42
}

We'll also have the return code of bananain the variable bretlike so:

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

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


You can do without sourcing and a process substitution by using evaltoo (and it works with Bash<4 too):

您可以使用evaltoo(它也适用于 Bash<4)而无需采购和流程替换:

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

And all this is safe, because the only stuff we're sourceing or evaling are obtained from declare -pand will always be properly escaped.

所有这一切都是安全的,因为我们正在source或正在执行的唯一内容eval是从中获得的,declare -p并且总是会被正确地转义。



Of course, if you want the output in an array (e.g., with mapfile, if you're using Bash≥4—otherwise replace mapfilewith a whilereadloop), the adaptation is straightforward.

当然,如果您想要数组中的输出(例如,使用mapfile,如果您使用 Bash≥4——否则替换mapfilewhileread循环),调整很简单。

For example:

例如:

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)

and with return code:

并带有返回代码:

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

回答by anubhava

You can do:

你可以做:

OUT=$(myscript.sh 2> errFile)
ERR=$(<errFile)

Now $OUTwill have standard output of your script and $ERRhas error output of your script.

现在$OUT将有您的脚本的标准输出和您的脚本的$ERR错误输出。

回答by jofel

An easy, but not elegant way: Redirect stderr to a temporary file and then read it back:

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

TMP=$(mktemp)
var=$(myscript.sh 2> "$TMP")
err=$(cat "$TMP")
rm "$TMP"

回答by Hyman

While I have not found a way to capture stderr and stdout to separate variables in bash, I send both to the same variable with…

虽然我还没有找到一种方法来捕获 stderr 和 stdout 以在 bash 中分离变量,但我将它们发送到同一个变量...

result=$( { grep "JUNK" ./junk.txt; } 2>&1 )

… then I check the exit status “$?”, and act appropriately on the data in $result.

……然后我检查退出状态“$?”,并对 $result 中的数据采取适当的行动。

回答by Tom Michaelis

# NAME
#   capture - capture the stdout and stderr output of a command
# SYNOPSIS
#   capture <result> <error> <command>
# DESCRIPTION
#   This shell function captures the stdout and stderr output of <command> in
#   the shell variables <result> and <error>.
# ARGUMENTS
#   <result>  - the name of the shell variable to capture stdout
#   <error>   - the name of the shell variable to capture stderr
#   <command> - the command to execute
# ENVIRONMENT
#   The following variables are mdified in the caller's context:
#    - <result>
#    - <error>
# RESULT
#   Retuns the exit code of <command>.
# SOURCE
capture ()
{
    # Name of shell variable to capture the stdout of command.
    result=
    shift

    # Name of shell variable to capture the stderr of command.
    error=
    shift

    # Local AWK program to extract the error, the result, and the exit code
    # parts of the captured output of command.
    local evaloutput='
        {
            output [NR] = ##代码##
        }
        END \
        {
            firstresultline = NR - output [NR - 1] - 1
            if (Var == "error") \
            {
                for (i = 1; i < firstresultline; ++ i)
                {
                    printf ("%s\n", output [i])
                }
            }
            else if (Var == "result") \
            {
                for (i = firstresultline; i < NR - 1; ++ i)
                {
                    printf ("%s\n", output [i])
                }
            }
            else \
            {
                printf ("%d", output [NR])
            }
        }'

    # Capture the stderr and stdout output of command, as well as its exit code.
    local output="$(
    {
        local stdout
        stdout="$($*)"
        local exitcode=$?
        printf "\n%s\n%d\n%d\n" \
               "$stdout" "$(echo "$stdout" | wc -l)" "$exitcode"
    } 2>&1)"

    # extract the stderr, the stdout, and the exit code parts of the captured
    # output of command.
    printf -v $error "%s" \
                     "$(echo "$output" | gawk -v Var="error" "$evaloutput")"
    printf -v $result "%s" \
                      "$(echo "$output" | gawk -v Var="result" "$evaloutput")"
    return $(echo "$output" | gawk "$evaloutput")
}