在 Bash 中同时捕获标准输出和标准错误 [重复]

Posted

技术标签:

【中文标题】在 Bash 中同时捕获标准输出和标准错误 [重复]【英文标题】:Capture both stdout and stderr in Bash [duplicate] 【发布时间】:2012-11-28 04:50:00 【问题描述】:

我知道这个语法

var=`myscript.sh`

var=$(myscript.sh)

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

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

【问题讨论】:

啊,没有临时文件就无法同时捕获两者,请参阅我的答案,其中显示了如何获取 stderr 并将 stdout 传递到屏幕(在对话框的情况下):***.com/a/13427218/815386 这里是附加信息mywiki.wooledge.org/BashFAQ/002What you cannot do is capture stdout in one variable, and stderr in another, using only FD redirections. You must use a temporary file (or a named pipe) to achieve that one. 您不想使用临时文件有什么具体原因吗?在 bash 编程环境中使用临时文件非常习惯 相关(并且有一个非常简单的解决方案):Bash script - store stderr in variable @eicto 是的,有办法,阅读here。 【参考方案1】:

有一种非常丑陋的方法可以在没有临时文件的两个单独变量中捕获stderrstdout(如果您喜欢管道),适当地使用process substitution、sourcedeclare。我会打电话给你的命令banana。您可以使用函数模拟这样的命令:

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

我假设您希望变量bout 中的banana 标准输出和变量berrbanana 的标准错误。这是实现这一目标的魔法(仅限 Bash≥4):

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

那么,这里发生了什么?

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

bout=$(banana)

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

然后:

 bout=$(banana);  2>&1

仍将banana的stdout分配给bout,但banana的stderr通过stdout显示在终端上(感谢重定向2&gt;&amp;1

然后:

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

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

然后:

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

banana的stderr分配给berr,并用declare显示berr的内容。

此时,您的终端屏幕上将显示:

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

用线

declare -- bout="banana to stdout"

通过标准错误显示。

最终重定向:

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

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

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


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

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

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

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

您也可以通过使用 eval 来实现无需采购和流程替换(它也适用于 Bash

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

这一切都是安全的,因为我们sourceing 或evaling 的唯一内容是从declare -p 获得的,并且总是会被正确转义。


当然,如果您希望以数组形式输出(例如,使用 mapfile,如果您使用 Bash≥4,否则将 mapfile 替换为 whileread 循环),适配很简单。

例如:

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)

【讨论】:

真棒答案 - 这太强大了! @gniourf_gniourf 我可以使用您的eval 示例并复制相同的banana 输出。接下来,我尝试通过变量传递命令并让它与ls "foo" 一起工作,但ls "foo bar" 出现问题——注意引号中的空格。在后一种情况下,我看到在 berr 中捕获了以下错误:ls: cannot access "foo: No such file or directory ls: cannot access bar": No such file or directory 这使我相信文件路径周围的引用不足以逃避空间。你能想出一个解决方案吗?顺便说一句,设置 IFS='' 不起作用。 @gniourf_gniourf 文章Bash: Preserving Whitespace Using set and eval 让我想知道使用 eval 保留空格是否是根本问题。我不确定我目前对问题的理解是否足够好,无法确定如何解决它。 @JohnMarkMitchell 你在这里使用了反模式! Don't put commands into variables!. 所以你基本上是动态生成源代码的! ingenious!谢谢你!【参考方案2】:

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

您可以将标准错误捕获到变量并将标准输出传递给用户屏幕(示例来自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 重定向。您必须使用临时文件(或命名管道)来实现该文件。

【讨论】:

谢谢 - 接受这个答案,因为使用文件描述符 3 及更高版本可以避免使用临时文件(即使在技术上,字面上是临时文件)所指的“临时文件”。 谢谢。基于这个答案,我使用 fd 3 在两个脚本之间发送额外信息,并使用 output=$(command 3&gt;&amp;1 1&gt;&amp;4-) ; 4&gt;&amp;1 从调用脚本中捕获它 我反对!有一种方法可以同时捕获标准输出和标准错误。 You can capture stdout into one and stderr into another variable with only FD redirections. No need to use some temporary file or named pipe。唯一的缺点就是有点丑。【参考方案3】:

你可以这样做:

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

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

【讨论】:

对不起,我忘了指定没有临时文件。【参考方案4】:

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

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

【讨论】:

【参考方案5】:

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

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

...然后我检查退出状态“$?”,并根据 $result 中的数据进行适当的操作。

【讨论】:

【参考方案6】:
# 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")

【讨论】:

这是错误的:赋值result=$(command) 在子shell 中运行:error=$( ... ) 中括号内的所有内容都在子shell 中;因此result 将永远不会被看到。你可以自己试试:c() echo &gt;&amp;2 'to stderr'; echo 'to stdout'; ; error=$( result=$(c); 2&gt;&amp;1); echo "result: $result"; echo "error: $error"。你会看到result 是空的。请参阅我的答案以了解实际有效的方法。

以上是关于在 Bash 中同时捕获标准输出和标准错误 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

在 Bash 中重定向标准错误和标准输出 [重复]

将所有输出重定向到 Bash 中的文件 [重复]

如何使用 googletest 捕获标准输出/标准错误?

Shell编程Shell中Bash基本功能

Shell编程Shell中Bash基本功能

以最佳方式从 system() 命令捕获标准输出 [重复]