Sagar.BlogArticle
All posts
All posts
Bash

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.

February 1, 20266 min read
BashProcess SubstitutionAdvancedScripting

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.log

Practical 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 |

Quick Check

What is the key advantage of `while read; done < <(cmd)` over `cmd | while read; done`?

Exercise

Use process substitution to:

  1. List all files in /etc and /usr/share that are larger than 100KB
  2. Sort both lists, then use comm to find files that appear in BOTH locations (same filename, regardless of directory)
  3. Print the count of common filenames