Handling Multi-Input Commands with Bash Process Substitution
Stop creating temporary files just to compare command outputs. Learn how Bash process substitution lets you treat command output as a file for tools like diff and comm.
11 Mar 2026, 05:33 UTC

The Pipe Limitation
Standard Unix pipes (|) are designed for linear data flow: the output of one process becomes the standard input (stdin) of the next. However, many essential engineering tools—like diff, comm, and join—require two or more distinct file paths as arguments. They cannot read multiple streams from a single stdin.
When you need to compare the output of two different commands, the traditional workaround is to save both outputs to temporary files, run the comparison, and then delete the files. This adds boilerplate code and creates disk I/O overhead. Process substitution solves this by making the output of a command appear to the system as a file.
How Process Substitution Works
Process substitution uses the <(command) syntax. When Bash encounters this, it doesn't execute the command immediately in the main shell. Instead, it creates a temporary named pipe (FIFO) or a file descriptor entry in /dev/fd/ and passes that path to the calling program.
To the receiving program, the input looks like a standard file on disk. In reality, the program is reading directly from the output stream of the substituted command. This allows you to feed multiple dynamic streams into a single command simultaneously.
Practical Example: Comparing Live Directory States
Consider a scenario where you need to find which files exist in folder_a but not in folder_b without creating intermediate text files. The comm command is ideal for this, but it requires two sorted files.
Run the following in a Bash terminal (ensure you have read permissions for the target directories):
# Compare two directory listings and show lines unique to the first directory
comm -23 <(ls folder_a | sort) <(ls folder_b | sort)
Execution Breakdown
ls folder_a | sort: This is the first substituted process. Bash creates a file descriptor (e.g.,/dev/fd/63) that streams this output.ls folder_b | sort: This is the second substituted process, creating another descriptor (e.g.,/dev/fd/62).comm -23: This command receives two file paths as arguments and compares them as if they were static files on the filesystem.
Trade-offs and Limitations
While powerful, process substitution is not a universal replacement for pipes or temporary files. Keep these constraints in mind:
Portability
Process substitution is a Bash extension. It is not POSIX-compliant. If your script starts with #!/bin/sh, it may be executing in dash or another shell where <() will trigger a syntax error. Always use #!/bin/bash for scripts utilizing this feature.
Resource Exhaustion
Each substitution opens a new file descriptor. If you use process substitution inside a large loop without caution, you risk hitting the system's open file limit (ulimit -n), leading to "Too many open files" errors. For massive iterations, traditional temporary files or a more robust language like Python may be safer.
Read-Once Nature
Because the output is streamed via a pipe, you cannot "seek" backward in the file. If the consuming tool tries to read the same argument twice or jump to a specific byte offset, the operation will fail because the data is consumed as it is read.
Verification
To verify your environment supports this and to see the mechanism in action, you can use the type command and a slow-running process:
- Run
type -a bashto confirm you are using the Bash shell. - Run
ls -l /dev/fdwhile a long-running process substitution is active (e.g., using asleepcommand inside the substitution) to see the temporary descriptors being allocated.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.