36.7 Process Substitution: <() and >()
Right, so you’ve got pipes (|) down. You can send stdout from one command to stdin of another. It’s brilliant, it’s simple, it’s the Unix philosophy in a single character. But what if you need to send the output of a command to a program that expects a filename as an argument, not a stream? You can’t pipe into that. You’d have to save it to a temp file first, which is a hassle.
Enter process substitution. It’s the shell’s way of saying, “Hold my beer,” and creating a temporary named pipe (a FIFO) that makes the output of a command look like a file. It’s pure, distilled shell magic, and once you get it, you’ll wonder how you lived without it.
The syntax looks a bit alien at first, but it’s logical: <() for input, >() for output. The shell runs the command inside the parentheses, connects its output (or input) to a special file descriptor (like /dev/fd/63), and then passes that filename to the outer command.
The Input Magician: <()
This is the one you’ll use 95% of the time. It takes the stdout of a command and presents it as a file path.
Let’s say you want to compare the output of two commands with diff. diff wants two filenames. Without process substitution, you’re stuck doing this:
ls -1 > list1.txt
ls -l | awk '{print $9}' > list2.txt # This is a contrived, silly example
diff list1.txt list2.txt
rm list1.txt list2.txt
What a chore. With <(), it’s a one-liner:
diff <(ls -1) <(ls -l | awk '{print $9}')
Boom. The shell creates two temporary file descriptors (e.g., /dev/fd/63 and /dev/fd/64), ls -1 writes to the first, your ls -l | awk pipeline writes to the second, and diff happily reads from both as if they were regular files. The temporary descriptors are automatically cleaned up afterwards. It’s brilliantly efficient.
The Output Weirdo: >()
This one is less common and often bends people’s brains a little. It’s for when a command expects you to give it a filename to write to, but you want to pipe its output somewhere else instead.
A classic, if slightly niche, use is with tar. Imagine you want to create a tarball on a remote machine without writing a local file first. You’d stream it over SSH. But tar needs a -f output file. >() to the rescue:
tar -czf - /some/directory | ssh user@host "cat > backup.tar.gz"
That works, but see how we used -f - to write to stdout? Some stubborn commands don’t have a “stdout” option. This is where >() forces them to behave:
# Let's pretend 'some_command' requires an output file argument and won't use stdout
ssh user@host "some_command -f >(cat > output.file)"
Here, on the remote host, some_command is told to write to a special file (the process substitution). That “file” is actually connected to the stdin of cat, which then writes to a real file. It’s a bit of a Rube Goldberg machine, but it works when you’re in a pinch.
The Crucial Details and “Gotchas”
First, the order of operations matters. The commands inside the parentheses run in parallel with the outer command. The shell sets up the pipes and lets everything rip. This is generally what you want, but it means you can’t rely on the outer command’s exit status to tell you if the inner command succeeded. If the inner command fails, the outer command might just get an empty or broken pipe and fail in its own way.
Second, remember that it’s the path that gets substituted. This means it works anywhere a filename argument is expected. You can use it multiple times in a single command:
comm <(sort file1.txt | uniq) <(sort file2.txt | uniq)
Third, and this is the big one: process substitution is not POSIX. It’s a feature of advanced shells like Bash and Zsh. If you’re writing a script for a system that might only have dash (like Ubuntu’s /bin/sh), it will fail spectacularly. It’s for your interactive shell and your own personal scripts, not for maximum portability.
It’s one of those features that transforms the command line from a simple tool into a powerful programming environment. It feels like a hack because it is one—the good kind. The kind that becomes a foundational feature because it’s just so damn useful. Use it, abuse it, and impress your friends.