I experiment with exec'ing the bash itself only to redirect the output. If I use redirection like
exec >bla.log
ls
exec 1>&2
it works as expected: the ls
output ends up in bla.log
and after the second exec
things are back to normal, mainly because handle 2 is still bound to the terminal.
Now I thought to send the output through a pipe instead of into a file, a trivial example being exec | cat >bla.log
. However, the command immediately returns. To figure out what is going on, I did this:
exec | bash -c 'echo $$; ls -l /proc/$$/fd /proc/23084/fd'
where 23084 is the bash currently running and got this:
24002
/proc/23084/fd:
total 0
lrwx------ 1 harald harald 64 Aug 14 20:17 0 -> /dev/pts/1
lrwx------ 1 harald harald 64 Aug 14 20:17 1 -> /dev/pts/1
lrwx------ 1 harald harald 64 Aug 14 20:17 2 -> /dev/pts/1
lrwx------ 1 harald harald 64 Aug 14 20:17 255 -> /dev/pts/1
/proc/24002/fd:
total 0
lr-x------ 1 harald harald 64 Aug 14 21:56 0 -> pipe:[58814]
lrwx------ 1 harald harald 64 Aug 14 21:56 1 -> /dev/pts/1
lrwx------ 1 harald harald 64 Aug 14 21:56 2 -> /dev/pts/1
As we can see, the sub-process 24002 is indeed listening to a pipe. But it certainly is not the parent process, 23084, which has this pipe open.
Any ideas what is going on here?
The proper way to implement something that might otherwise be written
exec | cat >bla.log
is
#!/bin/bash
# ^^^^ - IMPORTANT: not /bin/sh
exec > >(cat >bla.log)
This is because >()
is a process substitution; it's replaced with a filename (of the /dev/fd/NN
form if possible, or a temporary FIFO otherwise) which, when written to, will deliver to the stdin of the enclosed process. (<()
is similar, but in the other direction: being substituted with the name of a file-like object which will, when read, return the given process's stdout).
Thus, exec > >(cat >bla.log)
is roughly equivalent to the following (on an operating system that doesn't provide /dev/fd
, /proc/self/fds
, or similar):
mkfifo "tempfifo.$$" # implicit: FIFO creation
cat >bla.log <"tempfifo.$$" & # ...start the desired process within it...
exec >"tempfifo.$$" # explicit: redirect to the FIFO
rm "tempfifo.$$" # ...and can unlink it immediately.