多次调用exec日志重定向后如何重置避免旧日志重复写入
问题描述
在同一Bash脚本中多次执行exec > >(tee -a "${logFile}") 2>&1并指定不同日志文件时,较早的日志文件会被重复追加内容。示例脚本执行后:
test1.log包含三次输出:hello 1、hello 2、hello 3test2.log包含后两次输出:hello 2、hello 3test3.log仅包含最后一次输出:hello 3
如何在调用该exec命令前重置exec,让每个日志文件只记录对应阶段的输出?
示例脚本及执行结果:
$ cat sample.bash #!/bin/bash -e function redirectOutputToLogFile() { local -r logFile="${1}" mkdir -p "$(dirname "${logFile}")" exec > >(tee -a "${logFile}") 2>&1 } function main() { redirectOutputToLogFile 'test1.log' echo 'hello 1' redirectOutputToLogFile 'test2.log' echo 'hello 2' redirectOutputToLogFile 'test3.log' echo 'hello 3' } main "${@}"
执行结果:
$ ./sample.bash hello 1 hello 2 hello 3 $ cat test1.log hello 1 hello 2 hello 3 $ cat test2.log hello 2 hello 3 $ cat test3.log hello 3
原因分析
每次执行exec > >(tee ...)时,是在当前标准输出的基础上新增重定向链路,而非替换原有链路。第一次执行后,stdout被指向test1.log的tee进程;第二次执行时,新的tee进程会接管stdout,但旧的test1.log的tee进程并未关闭,仍会持续接收后续输出,导致所有之前打开的日志文件都会被写入后续内容。
解决方案
核心思路是:在每次重定向前,先将stdout和stderr恢复到脚本初始的终端输出状态,再重新绑定到新的日志文件。具体通过保存原始文件描述符实现:
修改后的脚本如下:
#!/bin/bash -e # 保存原始stdout(1)到描述符3,原始stderr(2)到描述符4 exec 3>&1 4>&2 function redirectOutputToLogFile() { local -r logFile="${1}" # 先恢复stdout和stderr到原始状态 exec >&3 2>&4 mkdir -p "$(dirname "${logFile}")" # 重新定向到新日志文件 exec > >(tee -a "${logFile}") 2>&1 } function main() { redirectOutputToLogFile 'test1.log' echo 'hello 1' redirectOutputToLogFile 'test2.log' echo 'hello 2' redirectOutputToLogFile 'test3.log' echo 'hello 3' # 脚本结束前恢复原始输出并关闭保存的描述符(可选) exec >&3 2>&4 exec 3>&- 4>&- } main "${@}"
验证结果
修改后执行脚本,日志文件内容符合预期:
test1.log仅包含:hello 1test2.log仅包含:hello 2test3.log仅包含:hello 3
原理说明
exec 3>&1 4>&2:将脚本启动时的终端输出通道(stdout/stderr)复制到备用描述符3和4,留存原始输出路径。exec >&3 2>&4:将当前stdout/stderr恢复为备用描述符指向的原始通道,此时之前的tee进程会因管道关闭而退出,停止接收后续输出。- 每次重定向前执行恢复操作,确保新的
exec > >(tee ...)仅将输出绑定到当前日志文件,不会影响历史日志。
内容的提问来源于stack exchange,提问作者Nam Nguyen
相关产品推荐
相关产品推荐

