Capture both stdout and stderr in Bash

This question already has an answer here:

  • Capture stdout and stderr into different variables 13 answers

  • 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):

    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:

    What 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.


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

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

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

    . <({ 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 bout the standard output of banana , the standard error being displayed on your terminal.

    Then:

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

    will still assign to bout the stdout of banana , but the stderr of banana is displayed on terminal via stdout (thanks to the redirection 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 bout with the declare builtin: this will be reused soon.

    Then:

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

    will assign to berr the stderr of banana and display the content of berr with 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.

    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 substitution to source the content of these lines.


    You mentioned the return code of the command too. Change banana to:

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

    We'll also have the return code of banana in the variable bret like so:

    . <({ 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 eval too (and it works with Bash<4 too):

    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 source ing or eval ing are obtained from declare -p and will always be properly escaped.


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

    For example:

    banana() {
        printf 'banana to stdout %dn' {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)
    

    You can do:

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

    Now $OUT will have standard output of your script and $ERR has error output of your script.

    链接地址: http://www.djcxy.com/p/25606.html

    上一篇: PHP:为什么不是exec()返回输出?

    下一篇: 在Bash中捕获stdout和stderr