【Linux】基础IO(三):文件描述符 与 重定向
这是Linux基础IO系列的第三篇,我们重点来理解文件描述符(File Descriptor,简称fd)以及Shell中非常常用的重定向机制。
一、什么是文件描述符(fd)?
在Linux中,一切皆文件(文件、目录、管道、socket、设备……)。
内核为了统一管理这些“文件”,给每个进程维护了一张打开文件描述符表(本质是一个数组)。
每个打开的文件(或类文件资源)都会在这个表中占一个位置,下标就是一个非负整数,这个整数就叫文件描述符(fd)。
每个进程启动时,系统自动为它打开三个特殊的文件描述符:
| 文件描述符 | 名称 | 含义 | 默认指向 | 简称 |
|---|---|---|---|---|
| 0 | STDIN_FILENO | 标准输入 | 键盘 | stdin |
| 1 | STDOUT_FILENO | 标准输出 | 显示器(终端) | stdout |
| 2 | STDERR_FILENO | 标准错误输出 | 显示器(终端) | stderr |
这三个几乎所有程序都会用到,所以被约定俗成地固定下来。
后面的fd(3、4、5……)由进程自己打开文件时从最小可用的数字开始分配。
二、文件描述符表(进程视角示意图)
当一个进程刚被创建时,内核为它准备的文件描述符表大概长这样:
进程的文件描述符表
下标(fd) 指向的内容
0 ────► 键盘(/dev/tty)
1 ────► 显示器(/dev/tty)
2 ────► 显示器(/dev/tty)
3 ────► (未使用)
4 ────► (未使用)
...
当我们执行 ls > a.txt 时,Shell 会:
- 打开文件
a.txt→ 得到一个新的fd(通常是3) - 把原来的 1 号fd 指向改为指向
a.txt - 执行
ls,它向1号fd写数据 → 最终写入a.txt
三、Shell 中的重定向操作符
| 操作符 | 含义 | 示例 | 说明 |
|---|---|---|---|
> | 标准输出重定向(覆盖) | ls > list.txt | 清空文件 → 写入 |
>> | 标准输出重定向(追加) | date >> log.txt | 追加到文件末尾 |
< | 标准输入重定向 | wc -l < access.log | 从文件读取输入 |
2> | 标准错误重定向(覆盖) | gcc main.c 2> error.log | 只把错误信息写入文件 |
2>> | 标准错误重定向(追加) | make 2>> build.log | 错误追加到日志 |
&> 或 >& | 标准输出 + 标准错误 一起重定向 | command &> all.log | 旧写法,部分Shell支持 |
command > file 2>&1 | 最经典的“输出和错误都重定向”写法 | ./test.sh > out.log 2>&1 | 先重定向1,再让2指向1的位置 |
command 2>&1 > file | 错误写法!常见面试陷阱 | 错误信息仍然输出到屏幕 |
经典写法顺序问题(非常重要!)
# 正确:错误和正常输出都进文件
./script.sh > log.txt 2>&1
# 错误示范:只有正常输出进文件,错误仍然在屏幕
./script.sh 2>&1 > log.txt
为什么?因为重定向是从左到右顺序执行的:
- 先执行
2>&1→ 2 指向了当时的 1(屏幕) - 再执行
> log.txt→ 只改变了 1 指向文件,2 仍然指向屏幕
四、常见的重定向组合示例
- 只想要正常输出,丢弃错误
make > build.log 2> /dev/null
# 或
make > build.log 2>/dev/null
- 正常输出和错误都保存,但分开
./test.sh > stdout.log 2> stderr.log
- 正常和错误都保存到同一个文件(推荐写法)
./test.sh > all.log 2>&1
# 更现代的写法(bash 4+)
./test.sh &> all.log
- 同时输入和输出重定向
bc < calc.txt > result.txt
- Here Document(多行输入)
cat > config.yaml << 'EOF'
server:
port: 8080
host: 0.0.0.0
EOF
五、文件描述符复制与关闭(进阶)
Shell 支持显式操作任意fd:
# 把 3 号fd指向文件
exec 3> debug.log
# 向 3 号fd写东西
echo "debug info" >&3
# 关闭 3 号fd
exec 3>&-
经典用法:临时保存原来的stdin/stdout
exec 3<&0 # 备份原来的stdin到3
exec 0< data.txt # 把stdin重定向到文件
read line # 从文件读
exec 0<&3 # 恢复原来的stdin
exec 3<&- # 关闭备份的fd
六、总结:一句话理解重定向
Shell的重定向,本质上就是在子进程执行命令前,修改它的文件描述符表,让某个fd指向别的文件(或管道、设备)。
记住这个本质,你就能理解几乎所有重定向的写法和顺序问题。
下一讲我们会进入更底层的系统调用层面:open/read/write/close/dup/dup2,真正看清楚重定向在内核里是怎么实现的。
有任何疑问欢迎留言~