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.)
A year later, here's an old bash script for logging anything. For example,
teelog make ...
logs to a generated log name (and see the trick for logging nestedmake
s too.)Use "tee" to redirect to a file and the screen. Depending on the shell you use, you first have to redirect stderr to stdout using
or
In csh, there is a built-in command called "script" that will capture everything that goes to the screen to a file. You start it by typing "script", then doing whatever it is you want to capture, then hit control-D to close the script file. I don't know of an equivalent for sh/bash/ksh.
Also, since you have indicated that these are your own sh scripts that you can modify, you can do the redirection internally by surrounding the whole script with braces or brackets, like
I created a script called "RunScript.sh". The contents of this script is:
I call it like this:
This works, but it requires the application's scripts to be run via an external script. It's a bit kludgy.
EDIT: I see I got derailed and ended up answering a different question from the one asked. The answer to the real question is at the bottom of Paul Tomblin's answer. (If you want to enhance that solution to redirect stdout and stderr separately for some reason, you could use the technique I describe here.)
I've been wanting an answer that preserves the distinction between stdout and stderr. Unfortunately all of the answers given so far that preserve that distinction are race-prone: they risk programs seeing incomplete input, as I pointed out in comments.
I think I finally found an answer that preserves the distinction, is not race prone, and isn't terribly fiddly either.
First building block: to swap stdout and stderr:
Second building block: if we wanted to filter (e.g. tee) only stderr, we could accomplish that by swapping stdout&stderr, filtering, and then swapping back:
Now the rest is easy: we can add a stdout filter, either at the beginning:
or at the end:
To convince myself that both of the above commands work, I used the following:
Output is:
and my prompt comes back immediately after the "
teed stderr: to stderr
", as expected.Footnote about zsh:
The above solution works in bash (and maybe some other shells, I'm not sure), but it doesn't work in zsh. There are two reasons it fails in zsh:
2>&3-
isn't understood by zsh; that has to be rewritten as2>&3 3>&-
So, for example, my second solution has to be rewritten for zsh as
{my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter
(which works in bash too, but is awfully verbose).On the other hand, you can take advantage of zsh's mysterious built-in implicit teeing to get a much shorter solution for zsh, which doesn't run tee at all:
(I wouldn't have guessed from the docs I found that the
>&1
and2>&2
are the thing that trigger zsh's implicit teeing; I found that out by trial-and-error.)Approaching half a decade later...
I believe this is the "perfect solution" sought by the OP.
Here's a one liner you can add to the top of your Bash script:
Here's a small script demonstrating its use:
(Note: This only works with Bash. It will not work with /bin/sh.)
Adapted from here; the original did not, from what I can tell, catch STDERR in the logfile. Fixed with a note from here.
Use the
script
command in your script (man 1 script)Create a wrapper shellscript (2 lines) that sets up script() and then calls exit.
Part 1: wrap.sh
Part 2: realscript.sh
Result: