如何在/bin/sh中分别过滤STDOUT与STDERR且保留原输出流?
在/bin/sh中独立过滤STDOUT与STDERR的无临时文件实现方案
需求背景
需要实现对程序的STDOUT和STDERR分别进行过滤,且不合并两者的输出,流程如下:
STDOUT ── [ filter ] ────> STDOUT STDERR ── [ filter ] ────> STDERR
同时需满足以下要求:
- 保留原程序的
$?退出返回值 - 允许原程序(如
myprogram函数)修改当前shell的环境变量 - 不能使用临时文件或命名管道,仅通过
exec重定向实现 - 适配标准
/bin/sh(Bash特有的>(cmd)进程替换构造无法使用)
可行实现方案
可以通过文件描述符备份+子shell分流的纯POSIX兼容写法实现,完全无需磁盘存储,适用于只读文件系统。
具体代码示例
#!/bin/sh # 自定义过滤逻辑,这里以给每行添加前缀为例,可替换为实际需求 filter() { sed 's/^/[PROCESSED] /' } # 备份当前shell的标准输出、标准错误到文件描述符3、4 exec 3>&1 4>&2 # 执行目标程序并分别过滤输出 # 核心逻辑:通过子shell分流STDOUT和STDERR,各自过滤后回写到原终端流 ( myprogram 2>&1 >&3 | filter >&4 ) 3>&1 | filter # 捕获原程序的退出码 program_exit=$? # 释放备份的文件描述符 exec 3>&- 4>&- # 示例:使用原程序的退出码 echo "原程序退出状态: $program_exit"
原理说明
- 文件描述符备份:
exec 3>&1 4>&2将当前shell的STDOUT(fd1)和STDERR(fd2)复制到fd3和fd4,确保后续能将过滤后的内容输出回原终端。 - 分流与过滤:
- 内层子shell中,
myprogram 2>&1 >&3把原程序的STDERR重定向到自身STDOUT,同时将原程序的STDOUT直接写入备份的fd3(即原终端的STDOUT)。 - 内层子shell的STDOUT(包含原程序的STDERR)通过管道传给
filter,处理后写入备份的fd4(原终端的STDERR)。 - 外层管道接收来自fd3的原程序STDOUT内容,经过
filter处理后输出到原终端的STDOUT。
- 内层子shell中,
- 退出码保留:
myprogram执行完成后,其退出码可直接通过$?捕获,因为子shell的退出状态会传递到当前shell。 - 环境变量修改支持:如果
myprogram是当前shell定义的函数,或者是在当前shell环境中执行的命令,其对环境变量的修改会保留在当前shell中(外部命令本身无法修改父shell环境变量,这是shell的固有特性)。
注意事项
- 该方案完全符合POSIX标准,可在任何兼容
/bin/sh的环境中运行。 - 过滤函数
filter可以替换为任何能读取标准输入、输出到标准输出的命令或脚本,适配各种处理需求。
内容的提问来源于stack exchange,提问作者Ole Tange
相关产品推荐
相关产品推荐

