Bash script - store stderr in a variable

76,523

Solution 1

Try redirecting stderr to stdout and using $() to capture that. In other words:

VAR=$((your-command-including-redirect) 2>&1)

Since your command redirects stdout somewhere, it shouldn't interfere with stderr. There might be a cleaner way to write it, but that should work.

Edit:

This really does work. I've tested it:

#!/bin/bash                                                                                                                                                                         
BLAH=$((
(
echo out >&1
echo err >&2
) 1>log
) 2>&1)

echo "BLAH=$BLAH"

will print BLAH=err and the file log contains out.

Solution 2

For any generic command in Bash, you can do something like this:

{ error=$(command 2>&1 1>&$out); } {out}>&1

Regular output appears normally, anything to stderr is captured in $error (quote it as "$error" when using it to preserve newlines). To capture stdout to a file, just add a redirection at the end, for example:

{ error=$(ls /etc/passwd /etc/bad 2>&1 1>&$out); } {out}>&1 >output

Breaking it down, reading from the outside in, it:

  • creates a file description $out for the whole block, duplicating stdout
  • captures the stdout of the whole command in $error (but see below)
  • the command itself redirects stderr to stdout (which gets captured above) then stdout to the original stdout from outside the block, so only the stderr gets captured

Solution 3

You can save the stdout reference from before it is redirected in another file number (e.g. 3) and then redirect stderr to that:

result=$(mysqldump --user=$dbuser --password=$dbpswd  \
   --host=$host $mysqldb 3>&1 2>&3 | gzip > $filename)

So 3>&1 will redirect file number 3 to stdout (notice this is before stdout is redirected with the pipe). Then 2>&3 redirects stderr to file number 3, which now is the same as stdout. Finally stdout is redirected by being fed into a pipe, but this is not affecting file numbers 2 and 3 (notice that redirecting stdout from gzip is unrelated to the outputs from the mysqldump command).

Edit: Updated the command to redirect stderr from the mysqldump command and not gzip, I was too quick in my first answer.

Share:
76,523
thornate
Author by

thornate

Updated on July 08, 2022

Comments

  • thornate
    thornate almost 2 years

    I'm writing a script to backup a database. I have the following line:

    mysqldump --user=$dbuser --password=$dbpswd  \
       --host=$host $mysqldb | gzip > $filename
    

    I want to assign the stderr to a variable, so that it will send an email to myself letting me know what happened if something goes wrong. I've found solutions to redirect stderr to stdout, but I can't do that as the stdout is already being sent (via gzip) to a file. How can I separately store stderr in a variable $result ?

  • Stephen M. Harris
    Stephen M. Harris almost 11 years
    Doesn't work in bash v3.2: unexpected token `{out}'. Does this syntax require Bash 4?
  • osirisgothra
    osirisgothra about 10 years
    it worked fine in bash 4.2, and 4.1, i even tried the shopts: compat32, compat31, and it worked there fine too, maybe it is a bug in 3.2 since when bash is in compatibility mode it works, unless compatibility only encompasses items that would break not due to 4.2, not the features that would be a part of 4.2... (i think most people use 4.2 these days right?) Anyhow this is an old post but just to clear it out, it does work and was just what i needed to assign errors to variables while displaying normal text on screen...:)
  • koby meir
    koby meir almost 9 years
    @AdamCrume how would i change this script so that in log file I will have both stderr & stdout ( out \n err )
  • Adam Crume
    Adam Crume almost 9 years
    @kobymeir: The easiest thing would be to do something like foo > out.log 2> err.log and then cat out.log err.log > combined.log. To avoid clobbering files in the current directory, you want to use mktemp to create out.log and err.log in /tmp, and then delete them afterword.
  • koby meir
    koby meir almost 9 years
    @AdamCrume but using these technique i won't have the stderr and stdout won't be in the correct order that they appeared. if i have: echo err1 >&2 echo out >&1 echo err2 >&2 i want the log to be: err1 out err2 and the error variable will be: err1 err2
  • Adam Crume
    Adam Crume almost 9 years
    @kobymeir: I thought that's what you wanted, since you specified (out \n err). If you want them interleaved, it's even easier: myvar=$(foo 2>&1).
  • koby meir
    koby meir almost 9 years
    @AdamCrume ok, my point wasn't clear enough,On the one hand i want them interleaved, on the other hand i want to capture all the stderr into $error variable.
  • Adam Crume
    Adam Crume almost 9 years
    @kobymeir: I'm pretty sure that can't be done with just Bash. When you redirect a stream, it can only go one place (although the Bash man page confusingly calls it "duplication"), so stderr can't go into the log file and be captured by the $error variable. If you try something like error=$((foo >> log) |& tee -a log), then stdout and stderr won't be interleaved properly. I suggest opening a new question.
  • lfk
    lfk over 5 years
    What's the difference between `statement` and $(statement)?
  • Admin
    Admin about 5 years
    An excellent and elegant solution. Just one thing to add: you need to close the file descriptor afterwards: exec {out}>&-