I dug through probably 20 questions across SO looking for a solution to this problem with no luck. It seems like a common task.
I have the following requirements for a bash script:
stdout
to log.out
, NOT to consolestderr
to log.out
AND to consolelog.out
?)Currently I am attempting the following to achieve this:
#!/bin/bash
> log.out
exec 3>&1 4>&2
trap 'exec 2>&4 1>&3' 0 1 2 3
exec 1>>log.out 2>&1
# Test
echo "status goes to console" >&3
echo "stderr goes to file + console" >&2
echo "stdout goes to file"
My understanding of this code is roughly...
This works perfectly except that errors are not displayed to the console.
So, I thought, I'll just cat /dev/stderr
to &3
in a separate bg process, and added this line under the 2nd exec
:
cat /dev/stderr >&3 &
I don't understand why, but, this also sends stdout to &3, so my console reads:
echoes goes to console
stderr goes to file + console
stdout goes to file
I've tried probably 50 combinations without success. After much reading I am leaning towards needing a (custom?) C program or similar to achieve this, which seems kind of crazy to me.
Any help is greatly appreciated.
Hey thanks to @charles-duffy's comments and answers I was able to make a very small modification to my existing script that achieved the general idea of what I'm looking for:
exec 3>&1 4>&2
trap 'exec 2>&4 1>&3' 0 1 2 3
exec 1>log.out 2> >(tee /dev/tty >&1)
The downside is that messages do appear out-of-order in the log file.
Assuming you have bash 4.1 or later:
# make copies of orig stdout, /dev/tty, and our log file FDs
exec {orig_stdout_fd}>&1
exec {tty_fd}>/dev/tty
exec {file_fd}>log.out
# ...and set them up however you wish, using ''tee'' for anything that goes two places
exec >&$file_fd # redirect stdout only to the log file
exec 2> >(tee /dev/fd/"$tty_fd" >&$file_fd) # redirect stderr to both sinks
Note that writes to stderr will take longer than writes to stdout (since they're going through a FIFO), so ordering can be lost.