I have a script which will be run interactively by non-technical users. The script writes status updates to STDOUT so that the user can be sure that the script is running OK.
I want both STDOUT and STDERR redirected to the terminal (so that the user can see that the script is working as well as see if there was a problem). I also want both streams redirected to a log file.
I've seen a bunch of solutions on the net. Some don't work and others are horribly complicated. I've developed a workable solution (which I'll enter as an answer), but it's kludgy.
The perfect solution would be a single line of code that could be incorporated into the beginning of any script that sends both streams to both the terminal and a log file.
EDIT: Redirecting STDERR to STDOUT and piping the result to tee works, but it depends on the users remembering to redirect and pipe the output. I want the logging to be fool-proof and automatic (which is why I'd like to be able to embed the solution into the script itself.)
The Pattern
This redirects both stdout and stderr separately, and it sends separate copies of stdout and stderr to the caller (which might be your terminal).
In zsh, it will not proceed to the next statement until the
tee
s have finished.In bash, you may find that the final few lines of output appear after whatever statement comes next.
In either case, the right bits go to the right places.
Explanation
Here's a script (stored in ./example):
Here's a session:
Here's how it works:
tee
processes are started, their stdins are assigned to file descriptors. Because they're enclosed in process substitutions, the paths to those file descriptors are substituted in the calling command, so now it looks something like this:the_cmd 1> /proc/self/fd/13 2> /proc/self/fd/14
the_cmd
runs, writing stdout to the first file descriptor, and stderr to the second one.In the bash case, once
the_cmd
finishes, the following statement happens immediately (if your terminal is the caller, then you will see your prompt appear).In the zsh case, once
the_cmd
finishes, the shell waits for both of thetee
processes to finish before moving on. More on this here.The first
tee
process, which is reading fromthe_cmd
's stdout, writes a copy of that stdout back to the caller because that's whattee
does. Its outputs are not redirected, so they make it back to the caller unchangedThe second
tee
process has it'sstdout
redirected to the caller'sstderr
(which is good, because it's stdin is reading fromthe_cmd
's stderr). So when it writes to its stdout, those bits go to the caller's stderr.This keeps stderr separate from stdout both in the files and in the command's output.
If the first tee writes any errors, they'll show up in both the stderr file and in the command's stderr, if the second tee writes any errors, they'll only show up only in the terminal's stderr.
Use the tee program and dup stderr to stdout.
the to redirect stderr to stdout append this at your command:
2>&1
For outputting to terminal and logging into file you should usetee
Both together would look like this:
EDIT: For embedding into your script you would do the same. So your script
would end up as