I know how to use tee
to write the output (STDOUT
) of aaa.sh
to bbb.out
, while still displaying it in the terminal:
I'm assuming you want to still see STDERR and STDOUT on the terminal. You could go for Josh Kelley's answer, but I find keeping a tail
around in the background which outputs your log file very hackish and cludgy. Notice how you need to keep an exra FD and do cleanup afterward by killing it and technically should be doing that in a trap '...' EXIT
.
There is a better way to do this, and you've already discovered it: tee
.
Only, instead of just using it for your stdout, have a tee for stdout and one for stderr. How will you accomplish this? Process substitution and file redirection:
command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)
Let's split it up and explain:
> >(..)
>(...)
(process substitution) creates a FIFO and lets tee
listen on it. Then, it uses >
(file redirection) to redirect the STDOUT of command
to the FIFO that your first tee
is listening on.
Same thing for the second:
2> >(tee -a stderr.log >&2)
We use process substitution again to make a tee
process that reads from STDIN and dumps it into stderr.log
. tee
outputs its input back on STDOUT, but since its input is our STDERR, we want to redirect tee
's STDOUT to our STDERR again. Then we use file redirection to redirect command
's STDERR to the FIFO's input (tee
's STDIN).
See http://mywiki.wooledge.org/BashGuide/InputAndOutput
Process substitution is one of those really lovely things you get as a bonus of choosing bash
as your shell as opposed to sh
(POSIX or Bourne).
In sh
, you'd have to do things manually:
out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$"
mkfifo "$out" "$err"
trap 'rm "$out" "$err"' EXIT
tee -a stdout.log < "$out" &
tee -a stderr.log < "$err" >&2 &
command >"$out" 2>"$err"
To redirect stderr to a file, display stdout to screen, and also save stdout to a file:
./aaa.sh 2>ccc.out | tee ./bbb.out
EDIT: To display both stderr and stdout to screen and also save both to a file, you can use bash's I/O redirection:
#!/bin/bash
# Create a new file descriptor 4, pointed at the file
# which will receive stderr.
exec 4<>ccc.out
# Also print the contents of this file to screen.
tail -f ccc.out &
# Run the command; tee stdout as normal, and send stderr
# to our file descriptor 4.
./aaa.sh 2>&4 | tee bbb.out
# Clean up: Close file descriptor 4 and kill tail -f.
exec 4>&-
kill %1
In my case, a script was running command while redirecting both stdout and stderr to a file, something like:
cmd > log 2>&1
I needed to update it such that when there is a failure, take some actions based on the error messages. I could of course remove the dup 2>&1
and capture the stderr from the script, but then the error messages won't go into the log file for reference. While the accepted answer from @lhunath is supposed to do the same, it redirects stdout
and stderr
to different files, which is not what I want, but it helped me to come up with the exact solution that I need:
(cmd 2> >(tee /dev/stderr)) > log
With the above, log will have a copy of both stdout
and stderr
and I can capture stderr
from my script without having to worry about stdout
.
If you're using zsh, you can use multiple redirections, so you don't even need tee
:
./cmd 1>&1 2>&2 1>out_file 2>err_file
Here you're simply redirecting each stream to itself and the target file.
Full example
% (echo "out"; echo "err">/dev/stderr) 1>&1 2>&2 1>/tmp/out_file 2>/tmp/err_file
out
err
% cat /tmp/out_file
out
% cat /tmp/err_file
err
Note that this requires the MULTIOS
option to be set (which is the default).
MULTIOS
Perform implicit
tee
s orcat
s when multiple redirections are attempted (see Redirection).