目录
1.重定向
1.1引入
1.2dup2
1.3原理
1.4重定向的实现
1.5重定向与进程替换
2.2重定向示例
2.文件描述符1与2
3.一切皆文件的理解
1.重定向
1.1引入
首先使用close接口关闭文件描述符为1的文件(起初是标准输出文件)
然后open函数打开一个文件,根据最小未使用文件描述符策略,该文件的文件描述符为1
接着向文件描述符为1的文件写入5条信息
最后再关闭open函数打开的文件
这样就实现了输出重定向的非常搓的版本(先关闭再打开):原本向标准输出文件写入的内容变为了写入filename文件当中
1.2dup2
实现输入输出追加重定向,调用dup2函数即可,这样就不用先关闭再打开了
| 类别 | 详情 |
|---|---|
| 函数原型 | #include <unistd.h>int dup2(int oldfd, int newfd); |
| 功能描述 | 将文件描述符oldfd复制到newfd,使两者指向同一文件表项。若newfd已打开,则先关闭它。 |
| 参数说明 | -oldfd:源文件描述符(必须有效且已打开)。- newfd:目标文件描述符(可指定,范围通常为[0, OPEN_MAX-1])。 |
| 返回值 | - 成功:返回newfd。- 失败:返回 -1,并设置errno(如EBADF、EMFILE)。 |
(1)输出重定向
(2)追加重定向
(3)输入重定向
1.3原理
文件描述符实质上是进程文件描述符表fd_array[]的数组下标,表中存储的是指向内核文件对象(struct file)的指针。dup2()的功能表面上是指定文件描述符的复制,实质上是将一个文件描述符表项中的指针复制到另一个表项中。重定向的原理就是:进程通过修改文件描述符表项中的指针指向,使得对特定文件描述符的操作实际作用于另一个文件。
1.4重定向的实现
实现重定向的思路是:在执行需要重定向的操作之前,通过系统调用(如dup2())让内核修改进程的文件描述符表,使得特定的文件描述符指向不同的文件对象,从而永久改变后续所有通过该文件描述符进行的I/O操作的目标
void normalExecute(char* argv[]) { //创建子进程执行普通命令 pid_t id = fork(); if(id == 0) { //打开重定向文件,修改文件描述符的对应内容 //文件描述符 int fd = 0; //输入重定向 if(rdir == IN_RDIR) { fd = open(rdirfilename, O_RDONLY); dup2(fd, 0); } //输出重定向 else if(rdir == OUT_RDIR) { fd = open(rdirfilename, O_CREAT|O_WRONLY|O_TRUNC, 0666); dup2(fd, 1); } //追加重定向 else if(rdir == APPEND_RDIR) { fd = open(rdirfilename, O_CREAT|O_WRONLY|O_APPEND, 0666); dup2(fd, 1); } //子进程执行普通命令 //参数1程序名,参数2命令行参数数组 execvp(argv[0], argv); exit(EXIT_FAILURE);//替换失败返回错误码 } else if(id > 0) { //父进程阻塞等待 int status = 0; pid_t rid = waitpid(id, &status, 0); if(rid > 0)//等待成功 { } else//等待失败 { } } else { perror("fork fail\n"); exit(EXIT_FAILURE);//创建子进程败 } }1.5重定向与进程替换
结论:程序替换不会影响文件访问(包括重定向)
程序替换(exec)不会改变进程已打开的文件状态,包括重定向设置。这是因为Linux内核将进程管理与文件管理解耦,文件描述符表作为进程的一部分在exec时被保留。通过系统调用(如dup2)修改的文件描述符关联关系会被新程序继承,从而实现重定向的持久效果
2.2重定向示例
仅重定向标准输出
./mytest > log.txt 等价于 ./mytest 1> log.txt 效果:fd=1(标准输出) → log.txt文件- 分别重定向
./mytest 1>normal.log 2>err.log 效果: fd=1 → normal.log(正常输出) fd=2 → err.log(错误输出)- 合并重定向
方式1: ./mytest >all.log 2>&1 等价于 ./mytest 1>all.log 2>&1 执行顺序: (1) >all.log 或 1>all.log → fd=1指向all.log (2) 2>&1 → fd=2也指向fd=1当前指向的文件(all.log) 方式2: ./mytest &>all.log 等价于 >all.log 2>&1 错误的方式: ./mytest 1>all.log 2>all.log //error 竞争 !!两个描述符独立打开文件,会产生竞争条件,内容可能交错混乱!! ./mytest 2>&1 >all.log //error 顺序错误 2>&1 → fd=2指向fd=1当前指向的文件(此时fd=1仍指向显示器) >all.log → fd=1改为指向all.log 结果: fd=2指向显示器,fd=1指向all.log- > 默认重定向fd=1,2> 重定向fd=2
- 2>&1 不是重定向到文件,而是让fd=2指向fd=1的当前位置
- 顺序很重要:先重定向目标,再复制描述符
- &> 是bash的简写,等价于>file 2>&1
2.文件描述符1与2
文件描述符1(fd=1):标准输出(stdout) - 正常输出信息
文件描述符2(fd=2):标准错误(stderr) - 错误信息输出
默认情况下,程序刚启动时,文件描述符1和2指向不同的文件对象,但这两个文件对象最终都连接到同一个终端设备
3.一切皆文件的理解
自底向上,Linux内核为统一管理各类硬件设备和系统资源,为每种设备类型定义一组操作方法(如读、写、控制等),封装在file_operations结构体中。当设备被访问时,内核创建struct file对象来描述这次打开操作,其中的f_op指针指向该设备对应的操作方法集。进程通过文件描述符访问文件时,内核查找对应的struct file对象,并通过其f_op指针调用具体的设备操作方法。这种抽象层在用户空间表现为统一的文件接口,从而实现了"一切皆文件"的设计哲学