如何让STDOUT、STDERR同时输出到终端与日志并保留二者区分
问题描述
这是关于「如何让STDOUT和STDERR同时输出到终端与日志文件」的后续提问,核心需求如下:
- 运行命令时,需将
STDOUT和STDERR存入同一个日志文件,严格保留输出的时序关联 - 必须确保
STDERR仍输出到原本的STDERR流(不能混到STDOUT中) - 要保留
STDOUT与STDERR的流区分,同时保留原命令的返回码
动机
部分工具会区分处理STDOUT和STDERR,比如给STDERR内容着色,或在命令返回非零值时专门展示STDERR内容,因此需要满足上述需求。期望能通过类似如下的命令调用实现:
log-output --file=command.log -c "make stuff-with-stderr"
现有方案的不足
尝试过两种方法,但均无法满足需求:
- 分文件存储的tee方案
the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )
- 缺点:
STDOUT和STDERR分别存入不同文件,丢失时序关联;且最终两者都只输出到STDOUT流,破坏了原流的区分属性。
- script命令方案
script -e -B build.log -c "the_cmd"
- 缺点:虽然将两者存入同一文件并保留时序,但所有输出都被重定向到
STDOUT,丢失了STDERR的原流属性。
请问是否有其他可行方案?
可行解决方案
方法一:自定义bash脚本实现log-output功能
可以写一个简单的bash脚本,完全实现你想要的log-output命令:
#!/bin/bash # 解析输入参数 while [[ $# -gt 0 ]]; do case "$1" in --file=*) LOG_FILE="${1#*=}" shift ;; -c) CMD="$2" shift 2 ;; *) echo "未知参数: $1" >&2 exit 1 ;; esac done # 保存原STDOUT和STDERR的文件描述符 exec 3>&1 4>&2 # 捕获命令输出,分流写入日志并保留原流属性 { bash -c "$CMD" 2>&1 1>&3 | tee -a "$LOG_FILE" >&4 } 3>&1 1>&2 | tee -a "$LOG_FILE" # 关闭临时文件描述符 exec 3>&- 4>&- # 保留原命令的返回码 EXIT_CODE=${PIPESTATUS[1]} exit $EXIT_CODE
将脚本保存为log-output,赋予执行权限(chmod +x log-output),即可按期望调用:
./log-output --file=command.log -c "make stuff-with-stderr"
- 核心逻辑:通过临时文件描述符保存原流,将
STDERR和STDOUT分别捕获后,用tee写入同一个日志文件,同时把各自内容送回原流,既保留时序又不破坏流区分,还能获取原命令的返回码。
方法二:单命令行实现(无需脚本)
如果不想单独写脚本,可以直接用一行命令实现等效效果:
cmd="make stuff-with-stderr"; { $cmd 2>&1 1>&3 | tee -a command.log >&2; } 3>&1 1>&2 | tee -a command.log; exit ${PIPESTATUS[1]}
- 说明:用文件描述符
3临时保存原STDOUT,先将STDERR转成STDOUT后写入日志并转回原STDERR流;再将原STDOUT内容写入日志,最后通过PIPESTATUS获取原命令的返回码。
验证要点
- 日志文件
command.log会包含STDOUT和STDERR的完整内容,时序与终端输出完全一致 - 终端上
STDERR内容仍输出到原STDERR流,支持工具的着色、特殊处理等逻辑 - 执行完命令后,用
echo $?可查看原命令的返回码
内容的提问来源于stack exchange,提问作者frans
相关产品推荐
相关产品推荐

