Redirect all subsequent commands' stderr using exec

You can use exec like this at the top of your script:

exec > >(tee "$HOME/somefile.log") 2>&1

For example:

#!/bin/bash -

exec > >(tee "$HOME/somefile.log") 2>&1

echo "$HOME"
echo hi
date
date +%F
echo bye 1>&2

Gives me output to the file $HOME/somefile.log and to the terminal like this:

/home/saml
hi
Sun Jan 20 13:54:17 EST 2013
2013-01-20
bye

As for a solution to redirect lots of command at once:

#!/bin/bash
{
    somecommand 
    somecommand2
    somecommand3
} 2>&1 | tee -a $DEBUGLOG

Why your original solution does not work: exec 2>&1 will redirect the standard error output to the standard output of your shell, which, if you run your script from the console, will be your console. the pipe redirection on commands will only redirect the standart output of the command.

On the point of view of somecommand, its standard output goes into a pipe connected to tee and the standard error goes into the same file/pseudofile as the standard error of the shell, which you redirect to the standard output of the shell, which will be the console if you run your program from the console.

The one true way to explain it is to see what really happens:

Your shell's original environment might look like this if you run it from the terminal:

stdin -> /dev/pts/42
stdout -> /dev/pts/42
stderr -> /dev/pts/42

After you redirect standard error into standard output (exec 2>&1), you ... basically change nothing. But if you redirect the script's standart output to a file, you would end up with an environment like this:

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /dev/pts/42

Then redirecting the shell standard error into standard output would end up like this :

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /your/file

Running a command will inherit this environment. If you run a command and pipe it to tee, the command's environment would be :

stdin -> /dev/pts/42
stdout -> pipe:[4242]
stderr -> /your/file

So your command's standard error still goes into what the shell uses as its standard error.

You can actually see the environment of a command by looking in /proc/[pid]/fd: use ls -l to also list the symbolic link's content. The 0 file here is standard input, 1 is standard output and 2 is standard error. If the command opens more files (and most programs do), you will also see them. A program can also choose to redirect or close its standard input/output and reuse 0, 1 and 2.


Write stderr and stdout to a file, display stderr on screen (on stdout)

exec 2> >(tee -a -i "$HOME/somefile.log")
exec >> "$HOME/somefile.log"

Useful for crons, so you can receive errors (and only errors) by mail