Bash Process Substitution

Use process substitution to pass command output as file arguments, enabling powerful shell pipelines.

Process Substitution Basics

# <(cmd) โ€” treats command output as a file (read)
diff <(sort file1.txt) <(sort file2.txt)
comm <(sort a.txt) <(sort b.txt)
wc -l <(find . -name "*.go")

# >(cmd) โ€” treats command input as a file (write)
tee >(gzip > out.gz) >(wc -l) > /dev/null

# Real use: compare remote and local files
diff <(ssh host cat /etc/hosts) /etc/hosts

# Multiple substitutions
join <(sort file1) <(sort file2)

Here-String & Here-Doc

# Here-string: <<< "string"
grep 'pattern' <<< "search in this string"
base64 -d <<< "aGVsbG8="
read -r a b c <<< "one two three"

# Here-doc: << DELIM
cat << EOF
Line 1
Line 2: $variable (expanded)
EOF

# Indented here-doc (<<-)
if true; then
  cat <<- EOF
    Indented content (leading tabs stripped)
  EOF
fi

# Quoted delimiter โ€” no expansion
cat << 'EOF'
$NOT_EXPANDED  \n literal
EOF

Named Pipes (FIFOs)

# Create named pipe
mkfifo /tmp/mypipe

# Terminal 1: writer
echo "hello" > /tmp/mypipe

# Terminal 2: reader
cat < /tmp/mypipe

# Persistent logging pipeline
mkfifo /tmp/log_pipe
tee /tmp/log_pipe | logger -t myapp &
your_program > /tmp/log_pipe

# Cleanup
rm /tmp/mypipe

Command Grouping

# ( ) โ€” subshell grouping (changes don't affect parent)
(cd /tmp && ls)              # pwd unchanged after
(export VAR=val; ./script)   # VAR not set in parent

# { } โ€” current shell grouping
{ echo "a"; echo "b"; } | grep a
{ cd /tmp && ls; }           # cd affects current shell (note: space and ; required)

# Redirect group output
{ cmd1; cmd2; cmd3; } > output.txt 2>&1

# Pipe group into while
{ echo "line1"; echo "line2"; } | while read line; do
  echo "Got: $line"
done

Practical Pipelines

# Log to multiple destinations simultaneously
./app 2>&1 | tee >(grep ERROR > errors.log) >(grep WARN > warns.log) | cat

# Parallel processing with process substitution
paste <(cut -f1 file) <(cut -f2 file | tr 'a-z' 'A-Z')

# Check if two commands produce same output
if diff <(cmd1) <(cmd2) > /dev/null; then
  echo "Same output"
fi

# Capture stderr separately
exec 3>&1
stderr=$(./script 2>&1 1>&3)
exec 3>&-

# Pipeline with error handling
set -o pipefail   # return non-zero if any pipe cmd fails
cmd1 | cmd2 | cmd3