Bash 进程替换

使用进程替换将命令输出作为文件参数传递,构建强大的 shell 管道。

进程替换基础

# <(cmd) — 将命令输出视为文件(读取)
diff <(sort file1.txt) <(sort file2.txt)
comm <(sort a.txt) <(sort b.txt)
wc -l <(find . -name "*.go")

# >(cmd) — 将命令输入视为文件(写入)
tee >(gzip > out.gz) >(wc -l) > /dev/null

# 实际应用:比较远程和本地文件
diff <(ssh host cat /etc/hosts) /etc/hosts

# 多重替换
join <(sort file1) <(sort file2)

Here-String & Here-Doc

# Here-string: <<< "字符串"
grep 'pattern' <<< "在这个字符串中搜索"
base64 -d <<< "aGVsbG8="
read -r a b c <<< "one two three"

# Here-doc: << 分隔符
cat << EOF
第一行
第二行: $variable(会展开)
EOF

# 缩进 here-doc(<<-)
if true; then
  cat <<- EOF
    缩进内容(行首 tab 会被去除)
  EOF
fi

# 引号分隔符 — 不展开变量
cat << 'EOF'
$NOT_EXPANDED  \n 字面量
EOF

命名管道(FIFO)

# 创建命名管道
mkfifo /tmp/mypipe

# 终端 1:写入端
echo "hello" > /tmp/mypipe

# 终端 2:读取端
cat < /tmp/mypipe

# 持久日志管道
mkfifo /tmp/log_pipe
tee /tmp/log_pipe | logger -t myapp &
your_program > /tmp/log_pipe

# 清理
rm /tmp/mypipe

命令分组

# ( ) — 子 shell 分组(变更不影响父 shell)
(cd /tmp && ls)              # 执行后 pwd 不变
(export VAR=val; ./script)   # VAR 不影响父 shell

# { } — 当前 shell 分组
{ echo "a"; echo "b"; } | grep a
{ cd /tmp && ls; }           # cd 影响当前 shell(注意空格和分号)

# 重定向整组输出
{ cmd1; cmd2; cmd3; } > output.txt 2>&1

# 将分组结果管道给 while
{ echo "line1"; echo "line2"; } | while read line; do
  echo "Got: $line"
done

实用管道

# 同时记录到多个目标
./app 2>&1 | tee >(grep ERROR > errors.log) >(grep WARN > warns.log) | cat

# 进程替换并行处理
paste <(cut -f1 file) <(cut -f2 file | tr 'a-z' 'A-Z')

# 检查两个命令输出是否相同
if diff <(cmd1) <(cmd2) > /dev/null; then
  echo "输出相同"
fi

# 单独捕获 stderr
exec 3>&1
stderr=$(./script 2>&1 1>&3)
exec 3>&-

# 管道错误处理
set -o pipefail   # 任意管道命令失败则返回非零
cmd1 | cmd2 | cmd3