Linux Basic — Process Substitution

1 minute read

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

Updated: