| name | process-substitution-fifos |
| description | Process substitution, named pipes (FIFOs), and advanced IPC patterns for efficient bash data streaming (2025) |
CRITICAL GUIDELINES
Windows File Path Requirements
MANDATORY: Always Use Backslashes on Windows for File Paths
When using Edit or Write tools on Windows, you MUST use backslashes (\) in file paths, NOT forward slashes (/).
Process Substitution & FIFOs (2025)
Overview
Master advanced inter-process communication patterns in bash using process substitution, named pipes (FIFOs), and efficient data streaming techniques. These patterns enable powerful data pipelines without temporary files.
Process Substitution Basics
Input Process Substitution <(command)
#!/usr/bin/env bash
set -euo pipefail
diff <(sort file1.txt) <(sort file2.txt)
diff <(ssh server 'cat /etc/config') /etc/config
sort -m <(sort file1.txt) <(sort file2.txt) <(sort file3.txt)
paste <(cut -f1 data.tsv) <(cut -f3 data.tsv)
wc -l <(grep "error" *.log)
jq '.items[]' <(curl -s "https://api.example.com/data")
source <(aws configure export-credentials --format env)
while IFS= read -r line; do
((count++))
process "$line"
done < <(find . -name "*.txt")
echo "Processed $count files"
Output Process Substitution >(command)
#!/usr/bin/env bash
set -euo pipefail
echo "Log message" | tee >(logger -t myapp) >(mail -s "Alert" admin@example.com)
tar cf - /data | tee >(gzip > backup.tar.gz) >(sha256sum > backup.sha256)
generate_data | tee >(processor1 > result1.txt) >(processor2 > result2.txt) > /dev/null
./build.sh 2>&1 | tee >(grep -i error > errors.log) >(grep -i warning > warnings.log)
tail -f /var/log/syslog | tee \
>(grep --line-buffered "ERROR" >> errors.log) \
>(grep --line-buffered "WARNING" >> warnings.log) \
>(grep --line-buffered "CRITICAL" | mail -s "Critical Alert" admin@example.com)
Combining Input and Output Substitution
#!/usr/bin/env bash
set -euo pipefail
diff <(sort input.txt | uniq) <(sort reference.txt | uniq)
cat data.csv | tee \
>(awk -F, '{print $1}' > column1.txt) \
>(awk -F, '{print $2}' > column2.txt) \
| wc -l
process_data() {
local input="$1"
while IFS= read -r line; do
echo "$line" | tee \
>(echo "LOG: $line" >> "$log_file") \
>(process_line "$line" >> results.txt)
done < <(cat "$input" | filter_input)
}
Named Pipes (FIFOs)
Creating and Using FIFOs
#!/usr/bin/env bash
set -euo pipefail
mkfifo my_pipe
trap 'rm -f my_pipe' EXIT
echo "Hello from writer" > my_pipe &
cat < my_pipe
if read -t 5 line < my_pipe; then
echo "Received: $line"
else
echo "Timeout waiting for data"
fi
Bidirectional Communication
#!/usr/bin/env bash
set -euo pipefail
REQUEST_PIPE="/tmp/request_$$"
RESPONSE_PIPE="/tmp/response_$$"
mkfifo "$REQUEST_PIPE" "$RESPONSE_PIPE"
trap 'rm -f "$REQUEST_PIPE" "$RESPONSE_PIPE"' EXIT
server() {
while true; do
if read -r request < "$REQUEST_PIPE"; then
case "$request" in
"QUIT")
echo "BYE" > "$RESPONSE_PIPE"
break
;;
"TIME")
date > "$RESPONSE_PIPE"
;;
"UPTIME")
uptime > "$RESPONSE_PIPE"
;;
*)
echo "UNKNOWN: $request" > "$RESPONSE_PIPE"
;;
esac
fi
done
}
() {
request=
>
<
}
server &
SERVER_PID=$!
send_request
send_request
send_request
Producer-Consumer Pattern
#!/usr/bin/env bash
set -euo pipefail
WORK_QUEUE="/tmp/work_queue_$$"
mkfifo "$WORK_QUEUE"
trap 'rm -f "$WORK_QUEUE"' EXIT
producer() {
local item
for item in {1..100}; do
echo "TASK:$item"
done
echo "DONE"
}
consumer() {
local id="$1"
while read -r item; do
[[ "$item" == "DONE" ]] && break
echo "Consumer $id processing: $item"
sleep 0.1
done
}
consumer 1 < "$WORK_QUEUE" &
consumer 2 < "$WORK_QUEUE" &
consumer 3 < "$WORK_QUEUE" &
producer > "$WORK_QUEUE"
wait
FIFO with File Descriptors
#!/usr/bin/env bash
set -euo pipefail
FIFO="/tmp/fd_fifo_$$"
mkfifo "$FIFO"
trap 'rm -f "$FIFO"' EXIT
exec 3<>"$FIFO"
echo "Message 1" >&3
echo "Message 2" >&3
read -r msg1 <&3
read -r msg2 <&3
echo "Got: $msg1, $msg2"
exec 3>&-
Coprocess (Bash 4+)
Basic Coprocess Usage
#!/usr/bin/env bash
set -euo pipefail
coproc BC { bc -l; }
echo "scale=10; 355/113" >&"${BC[1]}"
read -r result <&"${BC[0]}"
echo "Pi approximation: $result"
echo "sqrt(2)" >&"${BC[1]}"
read -r sqrt2 <&"${BC[0]}"
echo "Square root of 2: $sqrt2"
exec {BC[1]}>&-
wait "$BC_PID"
Named Coprocess
#!/usr/bin/env bash
set -euo pipefail
coproc PYTHON { python3 -u -c "
import sys
for line in sys.stdin:
exec(line.strip())
"; }
echo "print('Hello from Python')" >&"${PYTHON[1]}"
read -r output <&"${PYTHON[0]}"
echo "Python said: $output"
echo "print(2**100)" >&"${PYTHON[1]}"
read -r big_num <&"${PYTHON[0]}"
echo "2^100 = $big_num"
exec {PYTHON[1]}>&-
wait "$PYTHON_PID" 2>/dev/null || true
Coprocess Pool Pattern
#!/usr/bin/env bash
set -euo pipefail
declare -A WORKERS
declare -A WORKER_PIDS
start_workers() {
local count="$1"
local i
for ((i=0; i<count; i++)); do
coproc "WORKER_$i" {
while IFS= read -r task; do
[[ "$task" == "QUIT" ]] && exit 0
sleep 0.1
echo "DONE:$task"
done
}
local -n write_fd="WORKER_${i}[1]"
local -n read_fd="WORKER_${i}[0]"
local -n pid="WORKER_${i}_PID"
WORKERS["$i,in"]="$write_fd"
WORKERS["$i,out"]="$read_fd"
WORKER_PIDS["$i"]=
}
Advanced Patterns
Progress Monitoring with FIFO
#!/usr/bin/env bash
set -euo pipefail
PROGRESS_PIPE="/tmp/progress_$$"
mkfifo "$PROGRESS_PIPE"
trap 'rm -f "$PROGRESS_PIPE"' EXIT
monitor_progress() {
local total="$1"
local current=0
while read -r update; do
((current++))
local pct=$((current * 100 / total))
printf "\rProgress: [%-50s] %d%%" \
"$(printf '#%.0s' $(seq 1 $((pct/2))))" "$pct"
done < "$PROGRESS_PIPE"
echo
}
do_work() {
local items=("$@")
local item
for item in "${items[@]}"; do
process_item "$item"
echo "done" > "$PROGRESS_PIPE"
done
}
items=(item1 item2 item3 ... item100)
monitor_progress "${#items[@]}" &
MONITOR_PID=$!
do_work
3>
3>&-
Log Aggregator with Multiple FIFOs
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="/tmp/logs_$$"
mkdir -p "$LOG_DIR"
for level in DEBUG INFO WARN ERROR; do
mkfifo "$LOG_DIR/$level"
done
trap 'rm -rf "$LOG_DIR"' EXIT
aggregate_logs() {
local output_file="$1"
exec 3<"$LOG_DIR/DEBUG"
exec 4<"$LOG_DIR/INFO"
exec 5<"$LOG_DIR/WARN"
exec 6<"$LOG_DIR/ERROR"
while true; do
read -t 0.1 -r msg <&3 && echo "[DEBUG] $(date '+%H:%M:%S') $msg" >> "$output_file"
read -t 0.1 -r msg <&4 && echo "[INFO] $(date '+%H:%M:%S') $msg" >> ""
-t 0.1 -r msg <&5 && >>
-t 0.1 -r msg <&6 && >>
}
() { > ; }
() { > ; }
() { > ; }
() { > ; }
aggregate_logs &
AGGREGATOR_PID=$!
log_info
log_debug
log_warn
log_error
2>/dev/null
Data Pipeline with Buffering
#!/usr/bin/env bash
set -euo pipefail
buffered_stage() {
local name="$1"
local buffer_size="${2:-100}"
local buffer=()
while IFS= read -r line || [[ ${#buffer[@]} -gt 0 ]]; do
if [[ -n "$line" ]]; then
buffer+=("$line")
fi
if [[ ${#buffer[@]} -ge $buffer_size ]] || [[ -z "$line" && ${#buffer[@]} -gt 0 ]]; then
printf '%s\n' "${buffer[@]}" | process_batch
buffer=()
fi
done
}
run_parallel_pipeline() {
local input="$1"
cat "$input" | \
tee >(filter_a | transform_a > output_a.txt) \
>(filter_b | transform_b > output_b.txt) \
>(filter_c | transform_c > output_c.txt) \
> /dev/null
}
Streaming JSON Processing
#!/usr/bin/env bash
set -euo pipefail
stream_json_array() {
local url="$1"
curl -s "$url" | jq -c '.items[]' | while IFS= read -r item; do
process_json_item "$item"
done
}
parallel_json_process() {
local input="$1"
local workers=4
jq -c '.[]' "$input" | \
parallel --pipe -N100 --jobs "$workers" '
while IFS= read -r item; do
echo "$item" | jq ".processed = true"
done
' | jq -s '.'
}
transform_json_stream() {
jq -c '.' | while IFS= read -r obj; do
local id
id=$(echo "$obj" | jq -r '.id')
| jq --arg ts
}
Bash 5.3 In-Shell Substitution
No-Fork Command Substitution
#!/usr/bin/env bash
set -euo pipefail
result=$(echo "hello")
result=${ echo "hello"; }
counter=0
result=$(counter=$((counter + 1)); echo "$counter")
echo "Counter: $counter"
result=${ counter=$((counter + 1)); echo "$counter"; }
echo "Counter: $counter"
${ REPLY="computed value"; }
echo "$REPLY"
${| REPLY=$(expensive_computation); }
echo "Result: $REPLY"
Performance-Critical Pipelines
#!/usr/bin/env bash
set -euo pipefail
build_path() {
local parts=("$@")
local result=""
for part in "${parts[@]}"; do
result=${ printf '%s/%s' "$result" "$part"; }
done
echo "${result#/}"
}
accumulate() {
local -n arr="$1"
local sum=0
for val in "${arr[@]}"; do
sum=${ echo $((sum + val)); }
done
echo "$sum"
}
Error Handling in Pipelines
Pipeline Error Detection
#!/usr/bin/env bash
set -euo pipefail
run_pipeline() {
local result
if ! result=$(stage1 | stage2 | stage3); then
echo "Pipeline failed" >&2
return 1
fi
echo "$result"
}
run_with_status() {
cmd1 | cmd2 | cmd3
local -a status=("${PIPESTATUS[@]}")
for i in "${!status[@]}"; do
if [[ "${status[$i]}" -ne 0 ]]; then
echo "Stage $i failed with status ${status[$i]}" >&2
fi
done
local max=0
for s in "${status[@]}"; do
((s > max)) && max="$s"
done
return "$max"
}
Cleanup on Pipeline Failure
#!/usr/bin/env bash
set -euo pipefail
declare -a CLEANUP_PIDS=()
declare -a CLEANUP_FILES=()
cleanup() {
local pid file
for pid in "${CLEANUP_PIDS[@]}"; do
kill "$pid" 2>/dev/null || true
done
for file in "${CLEANUP_FILES[@]}"; do
rm -f "$file" 2>/dev/null || true
done
}
trap cleanup EXIT
register_pid() { CLEANUP_PIDS+=("$1"); }
register_file() { CLEANUP_FILES+=("$1"); }
run_safe_pipeline() {
local fifo="/tmp/pipeline_$$"
mkfifo "$fifo"
register_file "$fifo"
producer > "$fifo" &
register_pid "$!"
consumer < "$fifo" &
register_pid
}
Best Practices
FIFO Naming Convention
#!/usr/bin/env bash
set -euo pipefail
create_fifo() {
local name="$1"
local fifo="/tmp/${name}_$$_$(date +%s)"
mkfifo -m 600 "$fifo"
echo "$fifo"
}
create_secure_fifo() {
local name="$1"
local tmpdir
tmpdir=$(mktemp -d)
local fifo="$tmpdir/$name"
mkfifo -m 600 "$fifo"
echo "$fifo"
}
Preventing Deadlocks
#!/usr/bin/env bash
set -euo pipefail
mkfifo pipe
trap 'rm -f pipe' EXIT
echo "data" > pipe &
cat < pipe
exec 3<>pipe
echo "data" >&3
read -r data <&3
exec 3>&-
exec 3<pipe &
exec 4>pipe
echo "data" >&4
read -r data <&3
Timeout Patterns
#!/usr/bin/env bash
set -euo pipefail
read_with_timeout() {
local fifo="$1"
local timeout="$2"
local result
if read -t "$timeout" -r result < "$fifo"; then
echo "$result"
return 0
else
echo "Timeout after ${timeout}s" >&2
return 1
fi
}
write_with_timeout() {
local fifo="$1"
local timeout="$2"
local data="$3"
if timeout "$timeout" bash -c "echo '$data' > '$fifo'"; then
return 0
else
echo "Write timeout after s" >&2
1
}
Resources
Master process substitution and FIFOs for efficient inter-process communication without temporary files.