57

我知道这种语法

var=`myscript.sh`

或者

var=$(myscript.sh)

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

我的用例是如果返回码不为零,我想回显stderr并抑制其他情况。可能有其他方法可以做到这一点,但这种方法似乎会奏效,如果它真的可能的话。

4

6 回答 6

57

有一种非常丑陋的方法可以在没有临时文件(如果你喜欢管道)的情况下捕获两个单独的变量,使用stderr进程替换, , 和适当的。我会打电话给你的命令。您可以使用函数模拟这样的命令:stdoutsourcedeclarebanana

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

我假设您想要 in variable 的标准输出和in bananavariablebout的标准错误。这是实现这一目标的魔法(仅限 Bash≥4):bananaberr

. <({ 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.

然后:

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

会像上面那样做,但也会在终端上(通过stderr)显示内置的内容boutdeclare这将很快被重用。

然后:

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

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

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

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

用线

declare -- bout="banana to stdout"

通过 stderr 显示。

最终重定向:

{ 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)

您也可以不使用采购和流程替换eval(它也适用于 Bash<4):

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

所有这一切都是安全的,因为我们正在sourceing 或evaling 的唯一东西是从中获得的,declare -p并且总是会被适当地逃脱。


当然,如果您想要一个数组中的输出(例如,mapfile如果您使用 Bash≥4,则使用 ,否则替换mapfilewhile–<code>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)

并带有返回码:

. <({ berr=$({ mapfile -t bout< <(banana; bret=$?; declare -p bret >&3); } 3>&2 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
于 2014-11-09T11:00:12.653 回答
40

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

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

但是没有办法同时捕获标准输出和标准错误:

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

于 2012-12-10T18:12:38.460 回答
21

你可以做:

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

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

于 2012-12-10T18:12:16.990 回答
8

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

TMP=$(mktemp)
var=$(myscript.sh 2> "$TMP")
err=$(cat "$TMP")
rm "$TMP"
于 2012-12-10T18:15:33.600 回答
5

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

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

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

于 2014-01-09T20:09:54.950 回答
-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")
}
于 2015-02-15T10:11:50.520 回答