Process Substitution — <() and >()
Process substitution lets you use command output as a file argument, and feed a file to a command without a temp file. Essential for diff, while-read loops, and parallel processing.
Process substitution is one of Bash's most powerful features. It runs a command in the background and gives you a file path representing its input or output stream — without needing a temp file. The two forms are <(cmd) (output) and >(cmd) (input).
<(cmd) — Use Command Output as a File
<(cmd) runs cmd and provides a file path (like /dev/fd/63) that you can pass to any command expecting a file argument. The command reads from that path and gets cmd's output.
# diff two sorted versions of files — no temp files
diff <(sort file1.txt) <(sort file2.txt)
# Compare command output to a file
diff <(ls /tmp) <(cat saved_tmp_listing.txt)
# comm requires sorted input
comm -12 <(sort list1.txt) <(sort list2.txt) # common lines
comm -23 <(sort list1.txt) <(sort list2.txt) # only in list1
# Read command output in a while loop (without a subshell!)
while IFS= read -r line; do
echo "Processing: $line"
done < <(find /var/log -name "*.log" -mtime -1)
# ↑ The space before < is important!while < <() vs pipe
while read; done < <(cmd) keeps the loop in the current shell — variables set inside persist.
cmd | while read; done runs the loop in a subshell — variables set inside are lost.
This is why < <() is preferred for loops that accumulate results.
>(cmd) — Feed a File Argument to a Command
# tee to split output into multiple processors
# Without process substitution:
tee /tmp/a | process1
# But this loses stderr and requires temp files
# With >(cmd) — send output to multiple commands simultaneously
command | tee >(grep "ERROR" > errors.log) >(wc -l > count.txt) > output.log
# Log to two places at once
script_output | tee >(logger -t myscript) >> /var/log/myscript.logPractical Patterns
#!/usr/bin/env bash
# 1. Find files in A but not in B
comm -23 <(find /backup -type f | sort) <(find /source -type f | sort)
# 2. Process each line from a query without a temp file
while IFS=, read -r user email; do
echo "Sending mail to $user <$email>"
done < <(psql -t -c "SELECT name, email FROM users" "$DATABASE_URL")
# 3. Merge-sort two pre-sorted log files by timestamp
sort -m <(grep "2026-01" app1.log) <(grep "2026-01" app2.log)
# 4. Count output from two different commands
wc -l <(ls /etc) <(ls /usr)Comparison with Pipes
Process substitution vs pipes
| Feature | Pipe | | Process sub <() |
|---|---|---|
| Passes data as | stdin stream | file path |
| Commands connected | in sequence | in parallel |
| Loop variables persist | ❌ subshell | ✅ current shell |
| Multiple inputs to one cmd | ❌ No | ✅ Yes |
| Works with commands that need files | ❌ No | ✅ Yes |
What is the key advantage of `while read; done < <(cmd)` over `cmd | while read; done`?
Use process substitution to:
- List all files in
/etcand/usr/sharethat are larger than 100KB - Sort both lists, then use
commto find files that appear in BOTH locations (same filename, regardless of directory) - Print the count of common filenames