Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture both stdout and stderr in Bash [duplicate]

People also ask

How do I redirect both stderr and stdout to a file?

Redirecting stdout and stderr to a file: The I/O streams can be redirected by putting the n> operator in use, where n is the file descriptor number. For redirecting stdout, we use “1>” and for stderr, “2>” is added as an operator. We have created a file named “sample.

How do I redirect stdout and stderr to the same location?

Discussion. &> or >& is a shortcut that simply sends both STDOUT and STDERR to the same place—exactly what we want to do.

How do I redirect stderr and stdout in bash?

Understanding the concept of redirections and file descriptors is very important when working on the command line. To redirect stderr and stdout , use the 2>&1 or &> constructs.


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


Of course, if you want the output in an array (e.g., 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 %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)

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.


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.


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

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