使用/dev/stdout和/dev/stderr交换stdout与stderr的问题排查及实现
Bash重定向异常的原因与解决办法
问题重现
给定以下Bash函数:
func() { ( echo STDOUT; echo STDERR >&2 ) 1>$1 2>$2 }
符合预期的执行结果
- 执行
func /dev/stdout /dev/stdout | wc -l,输出2,两行内容均被wc统计。 - 执行
func /dev/stdout /dev/stderr | wc -l,终端打印STDERR,同时输出1,仅STDOUT被wc统计。
不符合预期的执行结果
- 执行
func /dev/stderr /dev/stdout | wc -l,实际输出:
STDOUT STDERR 0
预期结果应为STDOUT打印到终端,STDERR被wc统计,输出1。
- 执行
func /dev/null /dev/stdout | wc -l,实际输出0,预期STDERR应被wc统计,输出1。
原因分析
问题出在重定向的执行顺序和**/dev/stdout//dev/stderr的本质**:
- 重定向从左到右依次执行:函数中
1>$1 2>$2的顺序会导致后续重定向依赖前面的修改结果。比如执行func /dev/stderr /dev/stdout时:- 第一步
1>/dev/stderr:将当前进程的stdout重定向到终端(当前的stderr目标)。 - 第二步
2>/dev/stdout:此时/dev/stdout已经指向终端(因为第一步修改了stdout),所以stderr也被重定向到终端。
最终子shell的stdout和stderr都指向终端,管道无内容,wc统计0行。
- 第一步
/dev/stdout//dev/stderr是当前进程fd的符号链接:它们不是“原始标准输出/错误”的固定别名,而是指向当前进程正在使用的fd对应的文件。重定向后,它们的指向会随之改变。
解决办法
方法一:调整重定向顺序
将重定向顺序改为2>$2 1>$1,先处理stderr的重定向,再处理stdout的重定向,避免后续重定向依赖已修改的fd:
func() { ( echo STDOUT; echo STDERR >&2 ) 2>$2 1>$1 }
测试验证:
func /dev/stderr /dev/stdout | wc -l:终端打印STDOUT,输出1,符合预期。func /dev/null /dev/stdout | wc -l:输出1,符合预期。
方法二:使用临时文件描述符(更可靠)
先打开目标文件到临时fd,再将子shell的输出重定向到这些fd,彻底避免重定向顺序的影响:
func() { # 打开目标文件到临时文件描述符 exec 3>"$1" 4>"$2" # 子shell输出重定向到临时fd ( echo STDOUT; echo STDERR >&2 ) 1>&3 2>&4 # 关闭临时fd exec 3>&- 4>&- }
这种方法适用于所有场景,无论参数是设备文件还是普通文件,都能保证重定向的准确性。
内容的提问来源于stack exchange,提问作者eepp
相关产品推荐
相关产品推荐

