Linux Basic — Process Substitution
Process Substitution (<(cmd) / >(cmd))
Process substitution lets you use a command’s output (or input) where a file path is expected. Bash replaces <(cmd) with a path (a /dev/fd/N or named pipe) that the command treats as a regular file.
It’s the trick that turns “I need to compare two command outputs” from a 4-step temp-file dance into one line:
diff <(ls dir1) <(ls dir2)
Input: <(cmd)
The shell runs cmd in the background and exposes its stdout as a readable file.
# Show files in A but not B
comm -23 <(ls dir1 | sort) <(ls dir2 | sort)
# Diff two pipelines
diff <(grep ERROR app.log) <(grep ERROR app.log.old)
# Compare sorted CSV columns
diff <(cut -d, -f1 a.csv | sort) <(cut -d, -f1 b.csv | sort)
Output: >(cmd)
Bash exposes a writable file that pipes into cmd.
# Tee output to both a log file AND a compressor
some_command | tee >(gzip > out.log.gz) > out.log
# Split a stream into two analyzers
generate_data | tee >(sha256sum > data.sha) >(wc -l > data.count) > /dev/null
Why Not Just Pipes?
Pipes connect one stdout to one stdin. Process substitution lets you:
- Feed multiple inputs to a command that expects file arguments (
diff,comm,paste,join) - Feed multiple outputs to multiple consumers from a single producer
- Hand a command-output to a tool that doesn’t read stdin (
vim, some legacy binaries)
Real-World Examples
Live config drift check
diff <(ssh host1 cat /etc/nginx/nginx.conf) \
<(ssh host2 cat /etc/nginx/nginx.conf)
Apply a patch generated on the fly
patch original.txt <(diff -u original.txt new.txt)
paste two command outputs as columns
paste <(ls dir1) <(ls dir2)
Source an env file from a remote machine
source <(ssh prod-host 'cat /etc/myapp/env')
Caveats
| Caveat | Detail |
|---|---|
| Bash only | sh/dash don’t support <( ) — use #!/usr/bin/env bash |
| Not seekable | The substituted “file” is a pipe; commands that need to rewind (tar -x from random-access) may fail |
| Background process | The inner command runs concurrently; its exit status is not in $? of the outer command |
| Quoting | The whole construct is a single word — don’t quote it ("<(cmd)" is literal text) |
Capturing the Inner Exit Code
If you need the substituted command’s exit code, use PIPESTATUS with a pipeline or run it separately:
out=$(ls dir1 | sort) || die "ls failed"
diff <(printf '%s\n' "$out") <(ls dir2 | sort)
Next → Subshells & Grouping