Skip to content
Shell & TerminalDeep Dive July 12, 2026 2 min readViews unavailable

Command Substitution vs Process Substitution

$(command) captures text, while <(command) exposes a stream through a path-like argument. Their data flow, buffering, portability, and failure behavior are fundamentally different.

Command substitution and process substitution look similar, but they solve different interface problems. One turns a command’s output into shell text. The other connects a command’s output or input to a filename-shaped endpoint.

Command substitution produces a word

kernel=$(uname -r)
printf 'kernel=%s\n' "$kernel"

The shell runs uname -r, captures its standard output, removes trailing newline characters, and substitutes the remaining text. Quoting "$kernel" prevents later word splitting and glob expansion.

Because the result is stored as text, command substitution is a poor fit for arbitrary binary data: shell variables cannot represent embedded NUL bytes. Capturing very large output also means retaining it before the consumer can use it.

Process substitution produces an endpoint

diff <(sort old.txt) <(sort new.txt)

Bash, Zsh, and Ksh can replace each <(...) expression with a path that diff opens. Behind that path is commonly a pipe exposed through /dev/fd, or sometimes a named pipe. The producer and consumer can run concurrently, and the data is streamed instead of becoming one shell variable.

The reverse form supplies a writable destination:

tee >(gzip >output.gz) >(sha256sum >output.sha256) >/dev/null

Each >(...) becomes an endpoint to which tee writes; the enclosed command reads that data on standard input.

The practical decision rule

Use command substitution when the output is small textual metadata that belongs in an argument or variable: a version, path, identifier, or date. Use process substitution when a command insists on filenames but the data naturally comes from or goes to another process.

Process substitution is not specified by POSIX sh, so a script whose shebang is #!/bin/sh must not depend on it. A temporary file or explicit FIFO is the portable alternative. Even in Bash, failures inside process substitutions can be less visible than ordinary foreground-command failures; capture statuses explicitly when correctness depends on every branch succeeding.

Finally, neither form is a quoting exemption. Quote command-substitution results, and quote ordinary variables used inside either substitution. The syntax controls data transport, not the safety of later expansion.

Sources: