为何Bash脚本中stderr被重定向至stdout?进程替换问题排查
为什么你的Bash脚本里stderr会被“混入”stdout的捕获输出?
先重现一下你的脚本和问题现象:
你的脚本代码:
exec > >( while read line; do echo " stdout: $line"; done ) exec 2> >( while read line; do echo " stderr: $line"; done ) echo "rolo" echo "cholo" >&2
执行后得到的输出:
stdout: rolo stdout: stderr: cholo
这问题的核心是进程替换的文件描述符继承逻辑,咱们一步步说清楚:
第一条
exec > >(...)的作用是把当前shell的**标准输出(stdout)**永久替换成了那个捕获stdout的进程替换管道。简单说,从这行之后,这个shell里所有默认输出到stdout的内容,都会被第一个while read循环捕获,加上stdout:前缀再输出。重点来了:第二条
exec 2> >(...)是把shell的stderr指向第二个进程替换,但这个第二个进程替换里的echo " stderr: $line",它自己的输出会继承当前shell的文件描述符——而此时当前shell的stdout已经被第一条exec改头换面了!
也就是说,当第二个循环捕获到cholo这条stderr内容,执行echo " stderr: cholo"时,这条echo的输出并没有直接到终端,而是被送到了第一个循环的管道里。然后第一个循环又给它加了一层stdout: 前缀,最终就变成了stdout: stderr: cholo。
怎么解决这个问题?
如果想让stderr的捕获输出直接到终端,你需要显式让第二个进程替换里的echo输出到原来的终端,有两种常见方式:
方式1:直接输出到/dev/tty
exec > >( while read line; do echo " stdout: $line"; done ) # 把echo的stdout定向到终端设备 exec 2> >( while read line; do echo " stderr: $line" > /dev/tty; done ) echo "rolo" echo "cholo" >&2
方式2:先保存原始stdout到临时文件描述符
这种方式更灵活,适合后续需要恢复的场景:
# 先把原始stdout保存到文件描述符3 exec 3>&1 # 让stdout的捕获逻辑输出到原始stdout exec > >( while read line; do echo " stdout: $line" >&3; done ) # 让stderr的捕获逻辑也输出到原始stdout exec 2> >( while read line; do echo " stderr: $line" >&3; done ) echo "rolo" echo "cholo" >&2 # 可选:用完后关闭临时文件描述符 exec 3>&-
这两种方式执行后,都会得到你预期的输出:
stdout: rolo stderr: cholo
内容的提问来源于stack exchange,提问作者user5047085
相关产品推荐
相关产品推荐

