关于 process-substitution-fifos
面向 Bash 用户的进程替换、命名管道、协程和流式处理技能,提供无需临时文件构建管道的示例,并介绍错误处理及避免 FIFO 死锁的方法。
主要功能
- 01输入与输出进程替换
- 02FIFO 与双向管道模式
- 03Bash 协程通信
- 04管道错误处理与清理
- 05避免死锁及超时模式
适用场景
- 01不使用临时文件比较命令输出
- 02将数据流式写入多个目标
- 03构建生产者与消费者管道
- 04与长期运行的子进程通信
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ josiahsiegel/ claude-plugin-marketplace --skill process-substitution-fifos安装后在 Claude Code 中输入/process-substitution-fifos确认已加载。
附带文件references/advanced-patterns.md
详细介绍
关键指南
Windows 文件路径要求
强制要求:在 Windows 上始终使用反斜杠表示文件路径
在 Windows 上使用 Edit 或 Write 工具时,必须在文件路径中使用反斜杠(\),而不是正斜杠(/)。
进程替换与 FIFO(2025)
概述
掌握在 bash 中使用进程替换、命名管道(FIFO)和高效数据流技术实现进程间通信的高级模式。这些模式无需临时文件即可构建强大的数据管道。
进程替换基础
输入进程替换 <(command)
#!/usr/bin/env bash
set -euo pipefail
# Compare two command outputs
diff <(sort file1.txt) <(sort file2.txt)
# Compare remote and local files
diff <(ssh server 'cat /etc/config') /etc/config
# Merge sorted files
sort -m <(sort file1.txt) <(sort file2.txt) <(sort file3.txt)
# Read from multiple sources simultaneously
paste <(cut -f1 data.tsv) <(cut -f3 data.tsv)
# Feed command output to programs expecting files
# Many programs require filename arguments, not stdin
wc -l <(grep "error" *.log)
# Process API response with tool expecting file
jq '.items[]' <(curl -s "https://api.example.com/data")
# Source environment from command output
source <(aws configure export-credentials --format env)
# Feed to while loop without subshell issues
while IFS= read -r line; do
((count++))
process "$line"
done < <(find . -name "*.txt")
echo "Processed $count files" # Variable survives!
输出进程替换 >(command)
#!/usr/bin/env bash
set -euo pipefail
# Write to multiple destinations simultaneously (tee alternative)
echo "Log message" | tee >(logger -t myapp) >(mail -s "Alert" [email protected])
# Compress and checksum in one pass
tar cf - /data | tee >(gzip > backup.tar.gz) >(sha256sum > backup.sha256)
# Send output to multiple processors
generate_data | tee >(processor1 > result1.txt) >(processor2 > result2.txt) > /dev/null
# Log and process simultaneously
./build.sh 2>&1 | tee >(grep -i error > errors.log) >(grep -i warning > warnings.log)
# Real-time filtering with multiple outputs
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" [email protected])
组合输入与输出替换
#!/usr/bin/env bash
set -euo pipefail
# Transform and compare
diff <(sort input.txt | uniq) <(sort reference.txt | uniq)
# Pipeline with multiple branches
cat data.csv | tee \
>(awk -F, '{print $1}' > column1.txt) \
>(awk -F, '{print $2}' > column2.txt) \
| wc -l
# Complex data flow
process_data() {
local input="$1"
# Read from process substitution, write to multiple outputs
while IFS= read -r line; do
echo "$line" | tee \
>(echo "LOG: $line" >> "$log_file") \
>(process_line "$line" >> results.txt)
done < <(cat "$input" | filter_input)
}
命名管道(FIFO)
创建和使用 FIFO
#!/usr/bin/env bash
set -euo pipefail
# Create FIFO
mkfifo my_pipe
# Clean up on exit
trap 'rm -f my_pipe' EXIT
# Writer (in background or separate terminal)
echo "Hello from writer" > my_pipe &
# Reader (blocks until data available)
cat < my_pipe
# With timeout (using read)
if read -t 5 line < my_pipe; then
echo "Received: $line"
else
echo "Timeout waiting for data"
fi
双向通信
#!/usr/bin/env bash
set -euo pipefail
# Create two FIFOs for bidirectional communication
REQUEST_PIPE="/tmp/request_$$"
RESPONSE_PIPE="/tmp/response_$$"
mkfifo "$REQUEST_PIPE" "$RESPONSE_PIPE"
trap 'rm -f "$REQUEST_PIPE" "$RESPONSE_PIPE"' EXIT
# Server process
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
}
# Client function
send_request() {
local request="$1"
echo "$request" > "$REQUEST_PIPE"
cat < "$RESPONSE_PIPE"
}
# Start server in background
server &
SERVER_PID=$!
# Send requests
send_request "TIME"
send_request "UPTIME"
send_request "QUIT"
wait "$SERVER_PID"
生产者-消费者模式
#!/usr/bin/env bash
set -euo pipefail
WORK_QUEUE="/tmp/work_queue_$$"
mkfifo "$WORK_QUEUE"
trap 'rm -f "$WORK_QUEUE"' EXIT
# Producer
producer() {
local item
for item in {1..100}; do
echo "TASK:$item"
done
echo "DONE"
}
# Consumer (can have multiple)
consumer() {
local id="$1"
while read -r item; do
[[ "$item" == "DONE" ]] && break
echo "Consumer $id processing: $item"
sleep 0.1 # Simulate work
done
}
# Start consumers (they'll block waiting for data)
consumer 1 < "$WORK_QUEUE" &
consumer 2 < "$WORK_QUEUE" &
consumer 3 < "$WORK_QUEUE" &
# Start producer
producer > "$WORK_QUEUE"
wait
echo "All work complete"
使用文件描述符操作 FIFO
#!/usr/bin/env bash
set -euo pipefail
FIFO="/tmp/fd_fifo_$$"
mkfifo "$FIFO"
trap 'rm -f "$FIFO"' EXIT
# Open FIFO for read/write on FD 3
# Opening for both prevents blocking on open
exec 3<>"$FIFO"
# Write to FIFO via FD
echo "Message 1" >&3
echo "Message 2" >&3
# Read from FIFO via FD
read -r msg1 <&3
read -r msg2 <&3
echo "Got: $msg1, $msg2"
# Close FD
exec 3>&-
协进程(Bash 4+)
协进程的基本用法
#!/usr/bin/env bash
set -euo pipefail
# Start coprocess (bidirectional pipe)
coproc BC { bc -l; }
# Send data to coprocess
echo "scale=10; 355/113" >&"${BC[1]}"
# Read result
read -r result <&"${BC[0]}"
echo "Pi approximation: $result"
# More calculations
echo "sqrt(2)" >&"${BC[1]}"
read -r sqrt2 <&"${BC[0]}"
echo "Square root of 2: $sqrt2"
# Close write end to signal EOF
exec {BC[1]}>&-
# Wait for coprocess to finish
wait "$BC_PID"
命名协进程
#!/usr/bin/env bash
set -euo pipefail
# Named coprocess for Python interpreter
coproc PYTHON { python3 -u -c "
import sys
for line in sys.stdin:
exec(line.strip())
"; }
# Send Python commands
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"
# Cleanup
exec {PYTHON[1]}>&-
wait "$PYTHON_PID" 2>/dev/null || true
协进程池模式
#!/usr/bin/env bash
set -euo pipefail
# Create pool of worker coprocesses
declare -A WORKERS
declare -A WORKER_PIDS
start_workers() {
local count="$1"
local i
for ((i=0; i<count; i++)); do
# Each worker runs a processing loop
coproc "WORKER_$i" {
while IFS= read -r task; do
[[ "$task" == "QUIT" ]] && exit 0
# Simulate work
sleep 0.1
echo "DONE:$task"
done
}
# Store FDs dynamically
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"]="$pid"
done
}
# Note: Coprocess pool management is complex
# Consider GNU Parallel for production workloads
高级进程替换 / FIFO 模式
多源比较、tee 式流拆分、由进程替换驱动的差异比较、流式转换、临时 FIFO,以及需要大量清理操作的管道组合等详细示例,见 references/advanced-patterns.md。如果基本的 <(...)、>(...)、命名管道或协进程示例无法满足需求,请加载该参考文档。
Bash 5.3 Shell 内替换
无派生进程的命令替换
#!/usr/bin/env bash
# Requires Bash 5.3+
set -euo pipefail
# Traditional: forks subshell
result=$(echo "hello")
# Bash 5.3: No fork, runs in current shell
result=${ echo "hello"; }
# Significant for variable modifications
counter=0
# Traditional - counter stays 0 (subshell)
result=$(counter=$((counter + 1)); echo "$counter")
echo "Counter: $counter" # Still 0
# Bash 5.3 - counter is modified (same shell)
result=${ counter=$((counter + 1)); echo "$counter"; }
echo "Counter: $counter" # Now 1
# REPLY variable syntax (even more concise)
${ REPLY="computed value"; }
echo "$REPLY"
# Or using ${| } syntax
${| REPLY=$(expensive_computation); }
echo "Result: $REPLY"
性能关键型管道
#!/usr/bin/env bash
# Requires Bash 5.3+
set -euo pipefail
# Build result without forks
build_path() {
local parts=("$@")
local result=""
for part in "${parts[@]}"; do
# No fork for each concatenation
result=${ printf '%s/%s' "$result" "$part"; }
done
echo "${result#/}"
}
# Accumulate values efficiently
accumulate() {
local -n arr="$1"
local sum=0
for val in "${arr[@]}"; do
# In-shell arithmetic capture
sum=${ echo $((sum + val)); }
done
echo "$sum"
}
管道中的错误处理
检测管道错误
#!/usr/bin/env bash
set -euo pipefail
# Check all pipeline stages
run_pipeline() {
local result
# pipefail ensures we catch errors in any stage
if ! result=$(stage1 | stage2 | stage3); then
echo "Pipeline failed" >&2
return 1
fi
echo "$result"
}
# PIPESTATUS for detailed error info
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
# Return highest exit status
local max=0
for s in "${status[@]}"; do
((s > max)) && max="$s"
done
return "$max"
}
管道失败时进行清理
#!/usr/bin/env bash
set -euo pipefail
# Track resources for cleanup
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 cleanup
register_pid() { CLEANUP_PIDS+=("$1"); }
register_file() { CLEANUP_FILES+=("$1"); }
# Example usage
run_safe_pipeline() {
local fifo="/tmp/pipeline_$$"
mkfifo "$fifo"
register_file "$fifo"
producer > "$fifo" &
register_pid "$!"
consumer < "$fifo" &
register_pid "$!"
wait
}
最佳实践
FIFO 命名约定
#!/usr/bin/env bash
set -euo pipefail
# Include PID and descriptive name
create_fifo() {
local name="$1"
local fifo="/tmp/${name}_$$_$(date +%s)"
mkfifo -m 600 "$fifo" # Restrictive permissions
echo "$fifo"
}
# Use tmpdir for security
create_secure_fifo() {
local name="$1"
local tmpdir
tmpdir=$(mktemp -d)
local fifo="$tmpdir/$name"
mkfifo -m 600 "$fifo"
echo "$fifo"
}
防止死锁
#!/usr/bin/env bash
set -euo pipefail
# ✗ DEADLOCK - writer blocks, reader never starts
# mkfifo pipe
# echo "data" > pipe # Blocks forever
# ✓ SAFE - open both ends or use background
mkfifo pipe
trap 'rm -f pipe' EXIT
# Option 1: Background writer
echo "data" > pipe &
cat < pipe
# Option 2: Open for read/write
exec 3<>pipe
echo "data" >&3
read -r data <&3
exec 3>&-
# Option 3: Non-blocking open (requires careful handling)
exec 3<pipe &
exec 4>pipe
echo "data" >&4
read -r data <&3
超时模式
#!/usr/bin/env bash
set -euo pipefail
# Read with timeout
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 (using timeout command)
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 ${timeout}s" >&2
return 1
fi
}
资源
掌握进程替换和 FIFO,无需临时文件即可实现高效的进程间通信。
评论
支持 Markdown