This is due to how /dev/stderr
, or rather, /proc/$pid/fd/$num
works in Linux. There, opening /dev/stderr
does not duplicate file descriptor 2, but instead accesses the resource that fd is connected to directly.
So, since tee
normally truncates the file it writes to, so it does with tee /dev/stderr
. In your case, it's pretty much the same as doing tee log
.
See What's wrong with var=$(</dev/stdin) to read stdin into a variable? for further details.
That's only an issue on Linux. E.g. on macOS, it works as you'd assume it does. A somewhat compressed example:
linux$ bash -xc 'false; echo "truncated?" | tee /dev/stderr >/dev/null; true' 2>log
linux$ cat log
truncated?
+ true
vs.
mac$ bash -xc 'false; echo "truncated?" | tee /dev/stderr >/dev/null; true' 2>log
mac$ cat log
+ false
+ tee /dev/stderr
+ echo 'truncated?'
truncated?
+ true
Now, to make that work, you'd need to avoid using /dev/stderr
, and instead somehow use something like >&2
which tells the shell to duplicate file descriptor 2.
I think you could do that with tee >( cat >&2 )
. That's a bit convoluted, but you can't tell tee
to use an existing fd, you need to give it a filename, and due to the above issue, it can't be one that refers to the script's stderr directly.
However, it has the issue that the cat
runs in the background, which can make the output from it delayed, like here:
linux$ bash -xc 'false; echo "truncated?" | tee >(cat >&2) >/dev/null; true' 2>log
$ cat log
+ false
+ echo 'truncated?'
+ tee /dev/fd/63
++ cat
+ true
truncated?
Note that just tee -a /dev/stderr
doesn't help, since while that means anything tee
writes goes to the end of the file, anything written through the original stderr does not, but follows the write position of that file description. So you get things like this:
$ bash -xc 'false; echo "truncated?" | tee -a /dev/stderr >/dev/null; true' 2>log
$ cat log
+ false
+ echo 'truncated?'
+ tee -a /dev/stderr
+ true
ed?
where the last + true<nl>
(from the script's stderr) is written over the truncated?
(from tee
). You need to also make the original redirection an appending one, i.e. bash ... 2>>log
.
Or just make the original redirection go to a pipe instead of the file. With a pipe, /dev/stderr
works more like you'd think, since pipes don't have a write position, and can't be truncated.
Just the way to do that is a bit convoluted, you need something like 2> >(cat > log)
.
Or just make the original redirection go to a pipe instead of the file.
Could I instead do something inside the script file with e.g. exec to run stderr output through a pipe?
– Jakub Bochenski Oct 15 '23 at 16:27