Bash 重定向顺序:2>&1 写在前面,为什么错误输出没有进入日志
两个符号都写了,日志仍然少一半
希望把正常输出和错误输出一起保存时,常见写法是命令后接 >log 2>&1。如果把顺序改成 2>&1 >log,看上去依旧同时提到了日志和两个输出通道,实际结果却不同。关键在于 Bash 从左到右处理重定向,复制的是处理到那一步时的目标,不会建立一个永远跟随的链接。
标准输出对应文件描述符一,标准错误对应文件描述符二。两者默认可能都显示在终端,但仍是不同通道。下面用 Bash 5.2.37 在 Linux 上验证,保存为 demo.sh,执行 bash demo.sh。代码只在新建临时目录里写测试日志,结束时自动清理,不会覆盖你的现有日志文件。
把外层输出也保存,避免靠屏幕猜测
emit 函数先向标准输出写 OUT,再向标准错误写 ERR。第一组先把标准输出接到 both.log,再让标准错误复制这个目标;第二组先让标准错误复制外层标准输出,再把标准输出改接到 stdout.log。两组外层分别写入 outer-a.log 和 outer-b.log,因此离开内层日志的内容也有可检查的落点。
AI概念示意图:相同两条输出流,因改接顺序不同,一组汇入同一日志,另一组保留不同目的地。图片不是终端截图。
#!/usr/bin/env bash
set -u
task_dir=$(mktemp -d) || exit 1
trap 'rm -rf -- "$task_dir"' EXIT
emit() {
printf 'OUT\n'
printf 'ERR\n' >&2
}
{ emit >"$task_dir/both.log" 2>&1; } >"$task_dir/outer-a.log"
{ emit 2>&1 >"$task_dir/stdout.log"; } >"$task_dir/outer-b.log"
mapfile -t both <"$task_dir/both.log"
mapfile -t only_out <"$task_dir/stdout.log"
mapfile -t outer_b <"$task_dir/outer-b.log"
[[ ${#both[@]} -eq 2 ]] || exit 1
[[ ${both[0]} == OUT && ${both[1]} == ERR ]] || exit 1
[[ ! -s "$task_dir/outer-a.log" ]] || exit 1
[[ ${#only_out[@]} -eq 1 && ${only_out[0]} == OUT ]] || exit 1
[[ ${#outer_b[@]} -eq 1 && ${outer_b[0]} == ERR ]] || exit 1
printf 'both.log: %s\n' "${both[*]}"
printf 'outer-a.log: empty\n'
printf 'stdout.log: %s\n' "${only_out[*]}"
printf 'outer-b.log: %s\n' "${outer_b[*]}"逐步追踪每个描述符的去向
输出确认 both.log 包含 OUT ERR,outer-a.log 为空;stdout.log 只有 OUT,outer-b.log 只有 ERR。第二组处理 2>&1 时,标准输出已经由外层指向 outer-b.log,于是标准错误也取得这个目标。随后 >stdout.log 只改动标准输出,不会回头把标准错误一起搬过去。
这也是为什么不能简单把 2>&1 理解成“以后都跟着一号通道走”。排查较长命令时,可以从最外层开始,写下当前一号和二号各指向哪里,再依次应用每个重定向,遇到复制操作就记录当时的目标。这样即使命令处在脚本、分组或管道里,也能说明日志为什么落在某个位置。
合并日志还不等于知道完整发生顺序
本例只有同一个 shell 函数顺序执行的两次 printf,所以断言可以精确比较行顺序。换成多个进程或使用缓冲输出的程序,即使两个通道都进入同一个文件,也不能据此保证日志行严格对应事件的真实先后。需要分析时间顺序时,应由应用记录时间和关联信息,而不是只凭两条流在文件里的相邻位置判断。
另外,2>1 会把标准错误写入名为一的文件;带上 & 才表示复制文件描述符。路径应按变量使用习惯加引号,避免含空格时被拆开。本文使用 > 新建或截断测试文件,正式脚本若需要保留旧日志,应另外决定是否采用追加,并检查写入路径与权限,不能只把重定向顺序修好就忽略文件生命周期。
示例中的 mapfile 只负责把测试文件逐行读回,比较结果保存在数组中。每个失败条件都显式退出,因此即使输出恰好看起来接近预期,脚本也会通过退出码报告不一致。把这种小函数放进临时实验,比直接拿会修改业务状态的真实命令试错更容易复现问题,也能为后续修改留下明确验收标准。


