揭秘Linux文件管理与I/O重定向核心
引言
在 Linux 的哲学中,“一切皆文件”是核心思想之一。键盘、显示器、硬盘、打印机、甚至进程间的通信通道,都可以被抽象为“文件”来进行操作。而 I/O 重定向 正是这一哲学最直观、最强大的体现之一。它允许我们灵活地改变程序的输入来源和输出目的地,就像给数据流重新铺设管道一样。本文将深入剖析 I/O 重定向的底层机制、使用方法及其实现原理。
第一部分:核心概念与文件描述符(File Descriptor)
要理解重定向,必须先理解 文件描述符(fd)。
1. 什么是文件描述符?
文件描述符是一个非负整数,它是操作系统为了管理已被打开的“文件”而创建的一个索引。内核会为每个进程维护一个文件描述符表,通过这个数字,进程就可以找到对应的文件信息(如位置、访问模式等)。
2. 标准流与默认文件描述符
每个进程在启动时,都会默认打开三个“文件”,它们被称为标准流:
文件描述符 | 名字 | 默认指向 | 宏 |
0 | 标准输入(stdin) | 键盘 | STDIN_FILENO |
1 | 标准输出(stdout) | 显示器(终端) | STDOUT_FILENO |
2 | 标准错误(stderr) | 显示器(终端) | STDERR_FILENO |
对于这些内容,如果看过我上篇博客的都不会陌生,这里只是详细地介绍了一下,在上篇博客中都有提到!如果不是很了解的可以看一下上一篇博客,这篇是接着上篇没有写完的内容写的,或者说是对上篇的一种补充与扩展!揭秘Linux文件管理与C语言IO操作-CSDN博客
https://blog.csdn.net/2501_91607282/article/details/151262663?spm=1001.2014.3001.5501
第二部分:Shell 中的重定向操作符
Shell 的重定向语法就是用来修改这些默认的文件描述符指向的。
1. 输出重定向(覆盖)
>
command > file
将命令command
的标准输出(fd 1)重定向到file
中。如果file
不存在则创建,存在则覆盖其内容。底层本质:
Shell 会先打开(或创建)文件file
,拿到一个新的文件描述符(例如3
),然后通过系统调用dup2(3, 1)
,将 fd 1(stdout)指向 fd 3 所指向的文件。最后,Shell 启动command
命令,这个新进程会继承这个被修改过的文件描述符表。2. 输出重定向(追加)
>>
command >> file
与>
类似,但如果file
已存在,则将输出追加到文件末尾,而不是覆盖。3. 输入重定向
<
command < file
将命令command
的标准输入(fd 0) 重定向为从文件file
中读取。
下面我将详细介绍一下重定向:
提到重定向,那么dup2一定是不可或缺的一份子。下面在先介绍重定向之前,先要了解与理解一下dup2函数。
#include<unistd.h>
int dup2(int oldfd,int newfd);
dup2函数复制描述表表项oldfd到描述表表项newfd,覆盖描述表表项newfd以前的内容,如果newfd已经打开了,dup2会在复制newfd之前把newfd关闭。
在这个函数调用前,描述符1对应文件A,描述符4对应文件B,且A和B的引用计数都为1,那么在调用dup2之后,两个描述符都指向文件B,文件A此时已经被关闭了,且它的文件表和v-node表表项已经被删除了,这两个我们并不陌生,因为在上篇博客中详细介绍与讲解了。文件B的引用计数会增加,因为此时描述符1也指向B,B文件被两个描述符所指向,它的引用计数为2也没有什么不好理解的,从此之后,任何写到标准输出的数据全部会被写入到文件B,因为描述符1默认指向标准输出。
接着来看下面两段代码:
声明,下面的内容是在vscode中进行的。
在这种情况下,会正常输出, 那么如果我关闭1呢?
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{close(1); // 关闭标准输出umask(0);int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open");return 1;}// 现在fd应该是1,因为1是当前最小的可用描述符printf("open:%d\n", fd); // 这行输出会写入test.txtfprintf(stdout, "open fd:%d\n", fd); // 同样写入test.txtfflush(stdout);close(fd);return 0;
}
从结果可知,写入的数据并没有显示出来,而是写入到了test.txt文件中。这就是重定向。
那么如果不关闭呢,如何实现仅仅借助dup2函数来实现我们的重定向。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{umask(0);int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//int fd = open("test.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd < 0){perror("open");return 1;}dup2(fd,1);printf("open:%d\n", fd);fprintf(stdout, "open fd:%d\n", fd);fflush(stdout);close(fd);return 0;
}
上面的代码中都使用了open这个函数,这里在继续讲解重定向其他的用法之前,先详细介绍与讲解一下open函数,提到open函数其实我们并不是很陌生,因为早在学习c语言的时候就或多或少地有所接触过。
open函数:
头文件有三个:
分别是#include<sys/types.h>,#include<sys/stat.h>,#include<fcntl.h>
int open(char*filename,int flags,mode_t mode);
open函数将filename转化为一个文件描述符,因为这个文件描述符是都是数字,那么返回值是为什么是int类型也就不攻自破了,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件。
flags参数:
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:可读可写
flags参数也可以是一个或更多位掩码的或,为写提供一些额外的提示:
O_CREAT:如果文件不存在,那么就创建一个截断的空文件。
O_TRUNC:如果文件已经存在,就截断它。
O_APPEND:在每次写操作前,设置文件位置到文件的结尾处,换句话讲就是我们所说的追加。
mode参数指定了新文件访问权限位,每个进程都有一个umask,文件权限通常被设置位mode&umask。
理解了上面的内容再往下看就会较为轻松了。
使用了dup2来实现重定向,我们直接看结果。
我代码里没有close1,所以当我使用dup2的时候,返回的描述符总是在进程中当前没有打开的最小描述符,这里就是5,那么我们此时cat test.txt,这个文件中对应的内容就会被显示出来。接着来看dup2函数是如何实现追加重定向的。
1、输入重定向:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{umask(0);//int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);int fd = open("test.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd < 0){perror("open");return 1;}char line[64];while(1){printf(">>");//因为在C++里,输入是cin>>,我这里就用>>来表示输入吧if(fgets(line,sizeof(line),stdin)==NULL) break;printf("%s",line);}// dup2(fd,1);// printf("open:%d\n", fd);// fprintf(stdout, "open fd:%d\n", fd);// fflush(stdout);//close(fd);return 0;
}
这样就轻松地读取到了输入的内容。也就是从stdin里读取内容。
接着来看下面的内容:
使用dup2就完成了输入重定向,那么再读取数据的时候读取的就不是输入的内容了,而是文件里面的内容,下面将用结果来证明这一说法。
这个是test.txt文件里面的内容
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{umask(0);//int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);int fd = open("test.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd < 0){perror("open");return 1;}dup2(fd,1);printf("open:%d\n", fd);fprintf(stdout, "open fd:%d\n", fd);fflush(stdout);close(fd);return 0;
}
来看这个代码的运行结果:
从结果可以看出,我们使用dup2完成了输入重定向。此时文件描述符0指向的就是文件test.txt了。
那么再读取数据就是从这个文件里读取内容了。
2、输出重定向:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{umask(0);// 保存原始的标准输出文件描述符int original_stdout = dup(STDOUT_FILENO);// 以写方式打开输出文件(如果不存在则创建,如果存在则清空)int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (fd < 0){perror("open output.txt failed");return 1;}printf("这条消息会显示在终端上 - 重定向前\n");// 输出重定向:将标准输出(1)重定向到文件if (dup2(fd, STDOUT_FILENO) == -1){perror("dup2 failed");close(fd);return 1;}// 现在所有标准输出都会写入到 output.txt 文件printf("这条消息会写入到 output.txt 文件中\n");fprintf(stdout, "这条消息也会写入到文件中\n");printf("当前进程ID: %d\n", getpid());// 刷新所有缓冲区,确保内容写入文件fflush(stdout);// 恢复原始的标准输出dup2(original_stdout, STDOUT_FILENO);close(original_stdout);// 现在输出会再次显示在终端上printf("这条消息会显示在终端上 - 重定向后\n");// 关闭文件描述符close(fd);// 验证:读取并显示文件内容printf("\n=== 验证输出重定向 ===\n");printf("output.txt 文件内容:\n");return 0;
}
那么如何来理解输出重定向呢?
简单来说,输出重定向就是将命令的输出结果发送到指定位置,而不是默认的显示在终端屏幕上。
下面将输出上面代码的运行结果来为大家证清晰地认识一下输出重定向,将抽象的知识具体化
3、追加重定向:
#include <stdio.h> #include <fcntl.h> #include <unistd.h> int main() {umask(0);//int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);int fd = open("test.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd < 0){perror("open");return 1;}dup2(fd,1);printf("open:%d\n", fd);fprintf(stdout, "open fd:%d\n", fd);fflush(stdout);close(fd);return 0; }
很简单,我们只需要把flags的参数改成APPEND,我们就能利用dup2函数来实现我们的重定向了。
接着来通过结果来进行验证一下。
现在的test.txt文件中只有上面的内容,那么我将运行上面的代码实现追加操作。
再从test.txt里面看一下就会看到内容变多了
这样仅仅通过修改一个flags参数就实现了追加操作。
总结如下:
重定向是 Linux/Unix 系统中强大且基础的功能,掌握重定向可以大大提高工作效率和脚本编写能力。从简单的文件重定向到复杂的进程间通信(后续的博客会详讲),重定向在各种场景中都发挥着重要作用。理解文件描述符、标准流和重定向操作符是成为 Linux 高级用户的重要一步。
通过本文的介绍,你应该对重定向有了全面的了解,包括基本概念、操作符使用、程序实现以及验证方法。在实际工作中,灵活运用这些知识可以帮助你更好地控制数据流,实现各种复杂的处理任务。
本文至此结束,如上述内容对您有所帮助可以点赞收藏,关注一下,如有错误可在评论区指出,本人会及时进行更正。
后续会持续更新linux相关的内容。