在Bash中捕获stdout和stderr

时间:2012-12-10 18:08:22

标签: bash stream pipe stdout stderr

我知道这个语法

var=`myscript.sh`

var=$(myscript.sh)

stdout的结果(myscript.sh)捕获到var。如果我想捕获两者,我可以将stderr重定向到stdout。如何将每个保存为单独的变量?

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

6 个答案:

答案 0 :(得分:37)

使用process substitutionstderr和{有两种单独的变量捕获stdoutsource而没有临时文件(如果您喜欢管道),这是一种非常难看的方法{1}}恰当。我会叫你的命令declare。您可以使用函数模仿这样的命令:

banana

我假设您希望变量banana() { echo "banana to stdout" echo >&2 "banana to stderr" } 中的banana标准输出和变量bout中的标准错误banana。这是实现这一目标的魔力(仅限Bash≥4):

berr

那么,这里发生了什么?

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

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

这只是分配给bout=$(banana) bout标准输出的标准方式,即终端上显示的标准错误。

然后:

banana

仍会将{ bout=$(banana); } 2>&1 的标准分配给bout,但banana的标准显示在终端上通过标准输出显示(感谢重定向banana

然后:

2>&1

将如上所述,但也将在终端上(通过stderr)显示内置{ bout=$(banana); } 2>&1; declare -p bout >&2 bout的内容:这将很快重复使用。

然后:

declare

berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr 分配给berr stderr,并banana显示berr的内容。

此时,您将进入终端屏幕:

declare

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

通过stderr显示。

最终重定向:

declare -- bout="banana to stdout"

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

最后,我们使用process substitution 来源这些行的内容。


您也提到了命令的返回码。将{ berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1 更改为:

banana

我们还会在变量banana() { echo "banana to stdout" echo >&2 "banana to stderr" return 42 } 中返回banana的返回码,如下所示:

bret

您也可以不使用. <({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1) 进行采购和流程替换(并且它也适用于Bash&lt; 4):

eval

所有这些都是安全的,因为我们eval "$({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1)" source的唯一内容是从eval获得的,并且始终会被正确转义。


当然,如果你想要一个数组中的输出(例如,使用declare -p,如果你使用Bash≥4 - 否则将mapfile替换为mapfile - {{ 1}}循环),适应很简单。

例如:

while

并带有返回码:

read

答案 1 :(得分:26)

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

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

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

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

  

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

答案 2 :(得分:15)

你可以这样做:

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

现在$OUT将拥有您脚本的标准输出,而$ERR会出现您脚本的错误输出。

答案 3 :(得分:7)

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

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

答案 4 :(得分:3)

虽然我还没有找到一种方法来捕获stderr和stdout来分隔bash中的变量,但我将两者都发送到同一个变量...

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

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

答案 5 :(得分:-1)

# 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=$1
    shift

    # Name of shell variable to capture the stderr of command.
    error=$1
    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] = $0
        }
        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")
}