Redirecting file descriptors to more than one file in bash

Posted on June 13, 2016

TL;DR make writing to file descriptor three write to standard out and to standard error simultaneously:

exec 3> >(tee >(cat >&2))

What the hell was that?

Bash file descriptors are a neat feature. You can open files for reading, writing, and appending just like in other programming languages. In bash, these custom open files are given the numeric names 3 through 9.

Here’s how to do it:

Even though in regular redirections such as some-command >some-file we can include whitespace after the >, we can’t do that when opening files via exec. Think about it: exec 3 > path/to/file would simply be running the exec command with the single argument 3, redirecting standard output to path/to/file. It makes sense, but it’s nonetheless another one of those bash gotchas.

As for the file we opened with exec, >(tee >(cat >&2)) doesn’t exactly look like a file, now does it? That’s because it’s a process substitution.

There are several kinds of substitutions in bash:

There are some other substitutions, such as arithmetic substitution, but it’s no use discussing all of them here.

Back to our command. The outermost process substitution will create a FIFO, say /dev/fd/12 connected to the standard input of a tee command. tee writes its standard input unchanged to all given files as well as to its standard output. We give one argument to tee in the form of another process substitution creating another FIFO, say /dev/fd/13, this time connected to cat. cat concatenates its standard input with all given files, writing the result to its standard output. We redirect the standard output of cat to its standard error stream, which has been inherited from its parent processes, meaning that it is the shell’s standard error stream.

So what happens when we write to file descriptor three, e.g. with echo 'hi' >&3? Bash will write the standard output of the echo command to /dev/fd/12 and hence to the standard input of tee. tee will forward its input to /dev/fd/13 and to its standard output (which is the shell’s standard output). /dev/fd/13 is connected to the standard input of cat, which will write its input unchanged to its standard output; cat’s standard output has been redirected to its standard error, which is the shell’s standard error.

Of course, we didn’t need to open file descriptor three. If we want to write to more than one file as a one-off, we can do the process substitution on the spot as follows.

echo 'hello world' > >(tee >(cat >&2))

However, it can nonetheless be convenient to have a handy shortcut in the form of >&3 to mean “write to standard output and to standard error”.