Linux文件:重定向底层实现原理(输入重定向、输出重定向、追加重定向)
- 前言
- 一、文件描述符fd的分配规则
- 二、输出重定向(>)
- 三、输出重定向底层实现原理
- 四、追加重定向(>>)
- 五、输入重定向(<)
- 六、系统调用dup2
- 七、标准错误stderr存在意义
前言
在Linux中,操作系统会为每一个文件创建对应的描述结构体对象struct file
。该结构体中一定存在3个部分:打开文件的所有属性、文件的操作集、文件缓冲区(内存)。其中由于冯诺依曼体系决定了,无论对文件进行读操作还是写操作,都需要先将数据加载到文件缓存区!
我们在应用层进行对数据读写的操作本质上是用户缓冲区和内核数据缓冲区之间的相互拷贝!!!
一、文件描述符fd的分配规则
- 默认情况下,进程会默认打开3个文件:标准输入、标准输出、标准错误。
- 文件描述符的分配规制是:从上往下遍历查找最小的、未被使用的分别个新的文件!
二、输出重定向(>)
下面我们将标准输出文件关闭,然后创建新的文件;并向新的文件中输出一些信息。
int main()
{close(1); //将标准输出文件显示器关闭int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);//新创建文件,fd分配为1 if(fd < 0) { perror("open"); return 1; } printf("what will happen\n"); return 0;
}
- 我们将
stdout
关闭后,根据文件描述符的分配规则,新创建的文件fd为1。 - 我们观察结果发现,原本应该向显示器打印的消息直接向新创建的文件中写入。我们将这种现象称为输出重定向
>
!!
三、输出重定向底层实现原理
在C中,printf
函数只能向标准输出文件中输出消息,更准确的说:printf
只认文件描述符为1对应的文件。
当我们关闭标准输出后,新创建的文件所分别的文件描述符fd为1。此时文件描述符表中,下标为1的数组内容由标准输出替换为新打开的文件log.txt
。此时我们调用printf
输出消息时,变为向log.txt
文件中写入!!
所有重定向的本质就是修改特定文件fd的下标内容!!上层fd不变,下层fd指向的内容发生改变!
四、追加重定向(>>)
追加重定向和输出重定向基本相同,主要在于新创建文件的打开方式从w
改为a
。在底层上和输出重定向一样,都是将fd为1的下标内容替换为新文件即可!!!
int main()
{close(1); //将标准输出文件显示器关闭int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);//新创建文件,fd分配为1 if(fd < 0) { perror("open"); return 1; } printf("what will happen\n"); return 0;
}
五、输入重定向(<)
输入重定向<
本质就是将待重定的文件内容替换到fd为0的下标位置。此时我们熟悉的scanf、fputs
等函数读取方式会从键盘转变为新文件。即直接读取新文件中的内容
int main()
{close(0); //将显示器关闭 int fd = open("log.txt", O_RDONLY); //新创建文件fd为0if(fd < 0) { perror("open"); return 1; } char buffer[1024]; fread(buffer, 1, 1024, stdin);//从文件log.txt中读写信息 printf("stdin->fd:%d, %s\n", stdin->_fileno, buffer); return 0;
}
- 在上述代码执行过程中,没有出现光标等待我们从键盘上输入信息,而是直接将
log.txt
中的信息输出,进一步说明了fd为0
的下标内容被替换。
六、系统调用dup2
上述进行重定向时时进行的fd指向的内容替换,都需要我们手动先将对应的1、2、3
号文件关闭,非常麻烦!为此,系统中提供了相关的系统调用接口:dup2
。
#include <unistd.h>
int dup2(int oldfd, int newfd);//These system calls create a copy of the file descriptor oldfd
【实例】:
int main()
{int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);dup2(fd, 1);printf("this is log.txt test\n"); return 0;
}
七、标准错误stderr存在意义
标准输出和标准错误都是向显示器上打印信息。既然都是向同一个硬件进行写入,为什么还需要标准错误的存在?
在项目实际运行过程中,bug是不可避免的。所以在日志中会记录很多正确信息和错误信息。而stderr存在的意义是将两者信息分开,将所有的错误信息全部写入标准错误文件中,从而降低排除的成本!!
【实例】:
- 下面我们通过输出
hello stdin
和hello Stderr
来模拟日志中的正确信息和错误错误信息!
int main()
{fprintf(stdout, "hello stdin\n"); fprintf(stderr, "hello Stderr\n");}
我们情况下,两者都在显示器上直接输出
我们也可以通过替换fd=1
和fd=2
的下标对应文件,来将两者信息分开,分别向两个文件中写入
- 我们也可以将两种信息全部打印到同一个文件中:
1>log.txt 2>&1
。 这段代码的意思是先用log.txt文件fd中的内容替换fd=1
下标中的内容,然后&1
获得下标fd=1
中保存的数据(即指向log.txt
),最后将fd=2
对应的内容用fd=1
中的内容替换!!即fd=1
和fd=2
都指向了log.txt
文件。 - 前面讲述的输入重定向、输出重定向、追加重定向都是简写的,原始版本应该和上面重定向一样,加上对应的fd。例如输出重定向
./myfile > log.txt
是./myfile 1 > log.txt
简写。