目录
📖一、再来理解重定向
什么是重定向?
重定向就是把原本要发送到一个地方的请求,转到另一个地方去
ls > output.txt
,ls
命令本来在屏幕显示当前目录文件列表,加了 >
后,结果就写入 file.txt
分析:ls
指令是显示当前目录下的文件,本质就是将当前目录下所有的文件名以字符串的形式写入到显示器文件。采用输出重定向 >
,将原本应该写入显示器文件的内容写入到了 log.txtx
文件中。
总结: 重定向原理->:把当前的文件的文件描述符把标准输入/标准输出替换下去
📖二、 重定向的原理
在讲解重定向原理前,我们需要明确文件描述符的分配规则,即从0下标开始,寻找最小的没有使用的数组位置,它的下标就是新打开文件的文件描述符。
这里没有使用的意思是该下标里面存的是 NULL
,即没有指向任何一个文件对象。下面通过一段代码来为大家展示重定向的原理。
// mytest.c
int main()
{
close(1);
int fd = open(FILE_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0666);
const char* str = "Hello Linux!\n";
int cnt = 5;
while(cnt--)
{
write(1, str, strlen(str));
}
return 0;
}
代码分析:上面这段代码就展示了重定向的原理。首先调用 close 系统调用将 1 号下标对应的文件关闭,1号也就是标准输出流(显示器文件),关闭的意思就是将 1 下标里的内容置为 NULL,原本 1 下标里面存储的内容是显示器文件对象的地址,也就是标准输出 stdout(显示器文件),紧接着调用 open 打开了一个文件,根据文件描述符的分配规则,新打开的这个文件的文件描述符就是 1,即文件描述符表(file*的数组)1 号下标里面存储的就是新打开的文件对象的地址。
接下来调用 write 接口,向 1 号文件描述符中进行写入,本来 1 号文件描述符对应的是显示器文件,原本向显示器文件中写入的内容,此时就被写入到新打开的文件中,没有向显示器文件中写入,因此屏幕上就不会出现字符串,至此整个重定向的过程就结束啦。
总结:重定向的本质是对数组下标里面的内容进行修改。
📖三、系统调用--dup2 (输出重定向)
dup2是一个更改文件描述符的函数,具体作用往下看
上面介绍了重定向的原理,下面介绍一下实现重定向的系统调用 dup2
。
#include <unistd.h>
int dup2(int oldfd, int newfd);
dup2 的具体实现并不是向上面代码中那样,先将一个文件描述符关闭,然后紧接着再打开一个文件。
dup2 的使用方法是,用户在调用 dup2 接口前,正常打开一个文件,不用将显示器文件关闭,此时新打开文件的文件描述符就是 3。接下来调用 dup2 ,将新打开文件的文件描述符作为 oldfd,将显示器文件的文件描述符也就是 1的位置,作为 newfd。
我们知道,文件描述符本质上就是数组下标,dup2 函数中执行的工作就是将 oldfd 下标里存储的文件对象地址拷贝到 newfd 下标里面,至此重定向工作就完成了。
小Tips:dup2 的函数形参有一个误导,我们可能会觉得新打开文件的描述符是 newfd
其实不然,这里的 newfd 是将要被覆盖的文件描述符,oldfd 是新打开文件的描述符。
小技巧: 你可以认为 oldfd 是活的久的意思
。
代码示例->:
int main()
{
// close(1);
int fd = open(FILE_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd < 0)
{
perror("open");
return errno;
}
dup2(fd, 1);
const char* str = "Hello Linux!\n";
int cnt = 5;
while(cnt--)
{
write(1, str, strlen(str));
}
return 0;
}
代码分析:上面就是输出重定向的实现原理,追加重定向只需要把 O_TRUNC
替换成 O_APPEND
📖四、输入重定向效果演示
QQ2025426-165528
分析:cat
指令本来是从键盘文件中获取输入然后写入显示器文件中,采用输入重定向 <
后,是从 log.txt
文件中获取输入然后写入显示器文件中。
📖五、 输入重定向代码实现
// 输入重定向
int main()
{
int fd = open(FILE_PATH, O_RDONLY);
if(fd < 0)
{
perror("open");
}
dup2(fd, 0);
char str[1024];
ssize_t ret = read(0, str, sizeof(str) - 1);
if(ret > 0)
{
str[ret] = '\0';
printf("echo: %s", str);
}
return 0;
}
代码分析->:使用 dup2
函数将 fd
复制到标准输入的文件描述符 0
,之后对标准输入的读取操作会从文件读取。
小Tips: 假设一个进程先打开了一个文件 test.txt
,并将标准输出重定向到这个文件,然后执行了一个程序替换,用新的程序替代当前程序。在新程序运行时,它依然可向 test.txt
文件写入内容,因为文件打开和重定向操作不受程序替换的影响。
📖六、理解标准输出和标准错误
int main()
{
fprintf(stdout, "Standard output messages\n");
fprintf(stdout, "Standard output messages\n");
fprintf(stdout, "Standard output messages\n");
fprintf(stderr, "Standard error messages\n");
fprintf(stderr, "Standard error messages\n");
fprintf(stderr, "Standard error messages\n");
return 0;
}
代码分析:>
是输出重定向,也就是对标准输出(1号文件描述符)进行重定向。也就是把1号文件描述符替换成了output.txt,那么stdout本身是1号文件描述符,虽然写的是fprintf英文,但其实底层是根据下标执行的,所以三行Standard output messages则是输入到了文件里面
标准错误对应的 2号文件描述符并没有进行重定向,因此标准错误消息仍然打印在了屏幕上。
声明:fprintf
函数的第一个参数代表输出流,也就是指定将格式化的字符串输出到哪里
2.1 同时对标准输出和标准错误进行重定向
./mytest 1>output.txt 2>error.txt
小Tips:这段代码就是 output.txt 文件的文件描述符替换掉1号文件描述符,error.txt 文件的文件描述符替换掉2号文件描述符。这样一来屏幕上就不会有任何输出。
📖七、理解一切皆文件
为什么说Linux中一切皆是文件?
所谓的一切皆文件,就是操作系统帮我们封装了一层文件对象,进程对各种外设的操作,全都变成了对文件的操作。
这是因为,每一种外设都有各自的读写方法,一种外设它的读写的方法又分很多种,这就导致种类很多。
而操作系统对其进行了一层封装,不管是什么硬件,它们的各种读写方法全部变为了一种,也就是说只剩下了读和写,因此进程在与这些设备或资源交互时,不需要关心具体设备的细节差异,只需要使用标准的文件操作函数即可。所以在进程看来,这些(如硬盘、键盘、鼠标、打印机等)外设皆文件
一切皆文件的意义:
1.方便用户操作
2.提高语言的可移植性!
📖 八、完结
创作不易,留下你的印记!为自己的努力点个赞吧!