按时间顺序捕获STDOUT和STDERR时输出顺序不符问题排查
问题分析与解决:捕获STDOUT/STDERR时保留输出顺序
我来帮你拆解这个问题——你猜的方向完全正确,输出顺序乱掉的核心原因就是两个后台tee进程的无同步调度,再加上操作系统进程调度的随机性。
为什么顺序会乱?
你的脚本里启动了两个独立的tee后台进程,分别处理stdout和stderr对应的FIFO:
- 当
test.pl输出stdout1到outFIFO时,第一个tee被唤醒准备写入文件;紧接着程序输出stderr1到errFIFO,第二个tee也被唤醒。 - 这两个
tee是完全独立的进程,操作系统会根据自身的调度算法决定先运行哪一个。如果stderr对应的tee先拿到CPU时间片,它就会先把内容写入目标文件,哪怕stdout的输出更早产生。 - 虽然
tee -a用了追加模式,内核会自动处理文件偏移避免内容覆盖,但它无法保证不同进程的写入顺序和程序输出的时序一致。
解决方法
要保留程序输出的原始顺序,关键是让stdout和stderr的内容通过同一个处理路径,或者用同步机制确保写入顺序和输出时序匹配。下面提供几种可行方案:
方案1:用单个进程统一处理(POSIX兼容)
我们给stdout和stderr的每行内容添加标记,再用awk统一处理,既保证写入文件的顺序,又能在终端上正确区分两个流:
#!/bin/sh stdout_and_stderr=$1 shift pipe="${TMPDIR:-/tmp}/combined.$$" mkfifo "$pipe" trap 'rm "$pipe"' EXIT # 后台进程:读取标记后的输出,写入文件并分流到终端 awk -v outfile="$stdout_and_stderr" ' /^STDOUT:/ { sub(/^STDOUT:/, ""); print >> outfile; print; next; } /^STDERR:/ { sub(/^STDERR:/, ""); print >> outfile; print > "/dev/stderr"; next; } ' < "$pipe" & # 给stdout/stderr加标记后送入管道 "$@" 2> >(sed 's/^/STDERR:/' > "$pipe") > >(sed 's/^/STDOUT:/' > "$pipe") wait
方案2:用script命令(简单高效)
script是专门捕获终端会话输出的工具,它会模拟终端环境,完美保留stdout和stderr的输出顺序,代码极简:
#!/bin/sh stdout_and_stderr=$1 shift # -q 静默模式,不显示script的启动/结束提示 # 如果需要同时在终端显示输出,可追加 | tee -a "$stdout_and_stderr" script -q -c "$*" "$stdout_and_stderr"
方案3:改进原FIFO方案(Bash兼容)
如果你想保留原脚本的FIFO思路,可以用bash的进程替换让一个cat进程统一读取两个FIFO,保证顺序:
#!/bin/bash stdout_and_stderr=$1 shift out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$" mkfifo "$out" "$err" trap 'rm "$out" "$err"' EXIT # 统一读取两个FIFO,按收到顺序写入文件并分流到终端 cat <(tee -a "$stdout_and_stderr" < "$out") <(tee -a "$stdout_and_stderr" < "$err" >&2) & "$@" >"$out" 2>"$err" wait
验证结果
用你的test.pl测试任意方案,/tmp/xxx里的输出都会和终端显示一致:
1: stdout1 2: stderr1 3: stdout2
内容的提问来源于stack exchange,提问作者Andrew Sohn
相关产品推荐
相关产品推荐

