一、 系统调用 vs 库函数
我们常用 C 语言的 fopen、printf,这属于 C 标准库函数(用户态)。而 Linux 系统底层真正干活的是 系统调用接口:open、read、write、close。
库函数本质上是对系统调用的封装,底层还是通过文件描述符(fd)来操作。
二、 深入理解文件描述符(fd)
使用 open 打开文件后,返回值是一个整数,这就是 fd。它是怎么产生的?
在操作系统的进程控制块(PCB)中,有一个指针指向 files_struct 结构体,里面包含一个指针数组。这个数组的下标就是 fd!
-
0:标准输入(键盘)
-
1:标准输出(显示器)
-
2:标准错误(显示器)
分配规则:系统分配 fd 时,永远找当前未被使用的最小下标。所以我们新打开的文件默认是 3;如果关掉 0,新打开的文件就会变成 0。
三、 重定向的本质
我们平时用的 >, >> 符号,本质上是改变了 fd 的指向。
如果标准输出(1号)原本指向显示器,我们通过代码让 1 号指向一个磁盘文件,那么 printf 输出的内容就会跑到文件里。
实现这个魔法的核心系统调用是 dup2(oldfd, newfd)。它会将 oldfd 复制给 newfd,让 newfd 也指向同一个文件。
四、 实战代码:体验重定向
int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
// 此时 fd 是 3,指向 log.txt
// 让 1 号(标准输出)也指向 log.txt
dup2(fd, 1);
// 接下来 printf 的内容全都会写入 log.txt,不再显示在屏幕上
printf("这句话被重定向到文件了!\\n");
close(fd);
总结
“一切皆文件”是 Linux 的核心哲学。通过文件描述符,Linux 把硬件(键盘、显示器)和磁盘文件统一成了同一套操作接口。掌握 dup2,就能理解各种重定向和管道底层的运作机制。
自我实战操作
cd ~/linux_study
mkdir io_test
cd io_test
实战 1:体验系统调用 I/O
vim sysio.c
(输入以下代码)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
// 打开文件,如果没有则创建,权限设为 0644
int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open");
return 1;
}
printf("成功打开文件,得到的 fd 是: %d\\n", fd); // 大概率是 3
const char *msg = "Hello System IO!\\n";
// 用系统调用 write 写入
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
编译运行:
gcc sysio.c -o sysio
./sysio
cat test.txt

👉 你会发现 fd 是 3。因为 0、1、2 被系统占用了。
实战 2:验证 fd 的分配规则
vim fd_rule.c
(输入以下代码)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
close(0); // 关掉标准输入!
int fd = open("test.txt", O_RDONLY);
printf("关掉 0 号后,新打开的 fd 是: %d\\n", fd); // 你会发现变成了 0!
close(fd);
return 0;
}

(现象:系统永远找最小的空位。这证明了 fd 就是数组下标。)
实战 3:用 dup2 完成输出重定向
vim redir.c
(输入以下代码)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
// 1. 打开一个文件(此时 fd 是 3)
int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
// 2. 将 fd(3)复制给 1(标准输出)
// 原本 1 指向显示器,现在 1 和 3 指向同一个文件!
dup2(fd, 1);
// 3. 此时用 printf 输出,原本该上屏幕的字,全进 log.txt 了!
printf("这句话会被写进文件里!\\n");
printf("标准输出已经被重定向啦!\\n");
close(fd);
return 0;
}
编译运行:
gcc redir.c -o redir
./redir
# 屏幕上什么都不会显示!
cat log.txt # 打开文件看,字全在里面。

实战 4:进阶——给之前写的微型 Shell 加上重定向(选做)
第一步:创建新的 Shell 文件
进入目录,新建一个文件:
cd ~/linux_study/io_test
vim myshell_redir.c
第二步:复制并保存以下代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAX_ARGS 10
// 定义重定向类型
#define NoneRedir 0
#define OutputRedir 1 // >
#define AppRedir 2 // >>
int main() {
char buf[1024];
char *argv[MAX_ARGS];
int redir = NoneRedir;
char *filename = NULL;
while (1) {
// 每次循环重置状态
memset(argv, 0, sizeof(argv));
redir = NoneRedir;
filename = NULL;
printf("myshell> ");
fflush(stdout);
// 1. 获取用户输入
if (fgets(buf, sizeof(buf), stdin) == NULL) continue;
buf[strcspn(buf, "\\n")] = 0; // 去除换行符
if (strcmp(buf, "exit") == 0) break;
if (strlen(buf) == 0) continue;
// 2. 解析重定向(非常简单粗暴的解析:找 > 或 >>)
char *ptr = strstr(buf, ">>");
if (ptr != NULL) {
redir = AppRedir;
*ptr = '\\0'; // 把 >> 变成字符串结束符,切断前面命令
filename = ptr + 2; // 文件名在 >> 的后面
while (*filename == ' ') filename++; // 跳过空格
} else {
ptr = strchr(buf, '>');
if (ptr != NULL) {
redir = OutputRedir;
*ptr = '\\0'; // 把 > 变成字符串结束符
filename = ptr + 1; // 文件名在 > 的后面
while (*filename == ' ') filename++; // 跳过空格
}
}
// 3. 解析命令行(按空格切分,注意:此时 buf 里只剩下命令部分了)
int argc = 0;
argv[argc] = strtok(buf, " ");
while (argv[argc] != NULL) {
argc++;
argv[argc] = strtok(NULL, " ");
}
if (argc == 0) continue; // 用户只敲了回车或重定向符号
// 4. Fork 创建子进程执行
pid_t pid = fork();
if (pid == 0) {
// — 子进程内部 —
// 核心:处理重定向!
if (redir != NoneRedir && filename != NULL) {
int fd = -1;
if (redir == OutputRedir) {
// 以写方式打开,不存在则创建,存在则清空
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
} else if (redir == AppRedir) {
// 以追加方式打开,不存在则创建
fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644);
}
if (fd < 0) {
perror("open error");
exit(1);
}
// 将标准输出(1)指向我们打开的文件
dup2(fd, 1);
close(fd); // 复制完就可以关掉原来的 fd 了
}
// 执行命令
execvp(argv[0], argv);
perror("execvp error"); // 走到这说明命令没找到
exit(1);
} else if (pid > 0) {
// 父进程等待回收
wait(NULL);
}
}
return 0;
}
第三步:编译并运行
gcc myshell_redir.c -o myshell_redir
./myshell_redir
第四步:见证奇迹的时刻
在 myshell> 提示符下,依次输入以下命令体验:
测试普通命令:
myshell> ls -l
(应该正常在屏幕上打印出当前目录的文件列表)
测试输出重定向 >:
myshell> ls -l > out.txt
(屏幕上什么都不打印,退回到 myshell> 提示符。此时你输入 cat out.txt 或退出 shell 后看,内容全在里面)
测试追加重定向 >>:
myshell> echo "hello" >> out.txt
myshell> echo "world" >> out.txt
(再用 cat out.txt 查看,你会发现 hello 和 world 都追加进去了,没有被覆盖)
测试错误命令:
myshell> abcdefg
(会提示 execvp error: No such file or directory,因为系统找不到这个命令)
网硕互联帮助中心






评论前必须登录!
注册