Linux 文件描述符与重定向:2>&1 到底把什么接到哪里

举报
yd_252769024 发表于 2026/11/13 06:28:21 2026/11/13
【摘要】 nohup job.sh 2>&1 > app.log 为什么错误还是打在屏幕上?本文从 0/1/2 三个文件描述符讲清重定向与 2>&1 的复制语义、顺序陷阱、管道配合,并给出 /proc/$PID/fd 与 strace 的排障命令。

从一个常见的日志坑说起

在服务器上跑后台任务时,很多人写过这样的命令:

nohup ./job.sh 2>&1 > app.log &

结果标准错误还是打到了终端,app.log 里只有标准输出。问题不在 nohup,而在 2>&1 的位置。要把这件事讲清楚,得先回到文件描述符。

三个默认描述符

Linux 给每个进程都维护一张打开文件表,下标就是文件描述符(file descriptor,fd)。进程启动时内核会预置三个:

fd 名称 默认指向
0 stdin 键盘 / 管道输入
1 stdout 终端
2 stderr 终端

关键点:stdout 和 stderr 是两条独立的通道,只是默认都连到同一个终端,所以看起来混在一起。这也是为什么 cmd > out.log 只能收走 stdout,报错信息依旧刷屏。

重定向本质上只是「把某个 fd 重新指向一个文件」。> file 等价于 1> file,2> file 改的是 fd 2,< file 改的是 fd 0。

2>&1 是复制,不是别名

2>&1 的含义是:把 fd 2 指向 fd 1 当前指向的那个对象。它是一次性的复制动作,不是建立永久绑定。所以顺序至关重要:

cmd > app.log 2>&1     # 正确:先把 1 指向文件,再把 2 复制到同一处
cmd 2>&1 > app.log     # 错误:2 先复制了当时的 1(终端),随后 1 才改到文件

第二种写法里,fd 2 复制的是「复制那一刻」fd 1 的指向,之后 fd 1 再改去哪里都与 fd 2 无关。这就是开头那个坑的根因。

Bash 提供了更不容易写错的简写 &> app.log,等价于 > app.log 2>&1,推荐在脚本里直接用。如果只想丢弃错误,用 2>/dev/null;只想保留错误,用 1>/dev/null。

管道场景下的一点差异

管道只接 stdout,所以 cmd | grep x 默认会把 stderr 漏在屏幕上。想连错误一起过滤,必须先把 2 合并进 1,再交给管道:

cmd 2>&1 | grep -i error

注意 cmd | grep x 2>&1 改的是 grep 的 fd 2,不是 cmd 的,方向完全不同。另外一个高频组合是 set -o pipefail:管道默认只返回最后一条命令的退出码,加上它之后,中间任何一环失败都会让整条管道返回非零,配合 set -e 才能真正让脚本在出错时停下来。

三个实用排障命令

不确定某个进程把输出写到哪儿了,直接看它的 fd 表最靠谱:

ls -l /proc/$PID/fd        # 0/1/2 各自指向的文件或管道
readlink /proc/$PID/fd/1   # 单独看 stdout 的去向

想确认程序到底往哪个 fd 写了内容,可以用 strace -e trace=write -p $PID,输出里的 write(1, ...) 是 stdout,write(2, ...) 是 stderr。另外,echo $? 只看退出码,别把它和输出重定向混为一谈——重定向影响的是字节流,退出码是另一套机制。

小结

记住三句话就够用了:fd 是进程级的下标,> 改的是 fd 1;2>&1 是把 fd 2 复制成 fd 1 当时的指向,所以必须写在 > file 之后;管道只吃 stdout,要连 stderr 一起处理得先合并。把这些规则理顺,后台任务丢日志、CI 里看不到报错、脚本明明失败却返回成功这几类问题,基本都能一次定位。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。