Linux文件描述符

avatar
作者
猴君
阅读量:0

前言

我们以前就听过"Linux下一切皆文件",但是说实话我们只是记住了这句话,实质是不理解的!本期我们就会解释!

本期内容介绍

• 回顾C语言文件操作

• 系统I/O操作接口

• 文件描述符fd

• 理解Linux下一切皆文件

• 重定向

• 缓冲区

• stderr

回顾C语言文件操作

我们在C语言的时候就对文件操作进行过介绍,这里是只是稍微回顾一下,详细见C语言专栏的文件操作:

C语言打开文件: fopen(打开文件的方式有:读 r、写 w、追加 a)

C语言写入fwritefprintffputs

C语言读取freadfscanffgets

C语言会默认给我们打开三个流,即标准输入:stdin标准输出:stdout标准错误:stderr

注意:这里他们的返回值类型都是FILE*我们当时介绍说他是文件指针类型

OK,举个例子

#include <stdio.h> #include <string.h>   int main() {     //打开文件,以写的方式打开     FILE* fp = fopen("log.txt", "w");     if(fp == NULL)     {         perror("open error\n");         return 1;     }      //写入     const char* msg = "Hello World\n";     int cnt = 5;     while (cnt--)     {         fwrite(msg, strlen(msg), 1, fp);                                                                                               }      //关闭     fclose(fp);      return 0; }

OK, 看看效果:

w的特性是文件存在清空原始数据,从头开始写不存在,在当前进程的工作目录下创建,然后写入!这个和我们在指令那一期介绍过的输出重定向(>)的作用非常像!其实,输出重定向本质就是文件操作!

#include <stdio.h> #include <string.h>   int main() {     //打开文件,以追加的方式打开     FILE* fp = fopen("log.txt", "a");     if(fp == NULL)     {         perror("open error\n");         return 1;     }      //写入     const char* msg = "Hello World\n";     int cnt = 5;     while (cnt--)     {         fwrite(msg, strlen(msg), 1, fp);                                                                                               }      //关闭     fclose(fp);      return 0; }

a的特性是,文件存在,追加内容;文件不存在,在当前进程的工作路径下新建,在追加写入!这个有换个我们指令那里的追加重定向(>>)的性质一样!其实追加重定向本质也是文件操作

系统文I/O操作接口

我们以前就介绍过:

文件 = 内容 + 属性 

文件在没有被打开之前是存在磁盘地上

打开文件的本质是进程打开文件

我们知道,文件存在磁盘,而磁盘是硬件,所以向文件的写入本是上就是对硬件的写入,而我们普通用户是没有权限直接操作硬件的(OS不相信任何人),但是OS必须要给上层提供操作文件的服务,所以他就提供了系统调用接口!下面我们就来学习一下系统的调用接口:

open

作用:打开文件

参数:pathname是要打开/要创建按的文件名称, flag是标记位传参(作用是传递多个标记位),mode是设置新建文件权限的

返回值:如果打开成功,返回一个大于0的整数即文件描述符;失败,返回-1

注意:返回值后面会在介绍!

close

作用:关闭文件

这里你一定最奇怪的是标记位传参,什么是标记位传参?

标记位传参是一种位图的思想,目的是为了传递多个标记!

什么意思呢?举个例子:如果今天叫你给一个函数传递两个标记位,你大概率就是int flag1, int flag2了,而人家写OS的大佬使用的是标记位传参。先看如下代码,看完你就懂什么是标记位传参了:

#include <stdio.h> #include <string.h> #include <unistd.h>   #define ONE 1           // 1  0000 0001 #define TWO (1 << 1)    // 2  0000 0010 #define THREE (1 << 2)  // 4  0000 0100 #define FOUR (1 << 3)   // 8  0000 1000  void print(int flag) {     if(flag & ONE)         printf("one\n"); //这些都可以完成其他功能     if(flag & TWO)         printf("two\n");     if(flag & THREE)         printf("three\n");     if(flag & FOUR)         printf("four\n"); }  int main() {     print(ONE);     printf("\n");      print(ONE | TWO);     printf("\n");      print(ONE | THREE | FOUR);     printf("\n");      print(ONE | TWO | THREE | FOUR);     printf("\n");      return 0; }

看效果:

这样你就可以用一个整形传递多个标记位了, 我们在传递时以按位或的方式就可以传递多个了!!那我们open系统调用的标记位传参有哪些?OK,我们先来介绍一下,然后再使用:

O_RDONLY : 只读打开

O_WRONLY :只写打开

O_RDWR:读写打开

注意:前三个在使用open时必须指定一个,且只能指定一个!

O_CREAT :若文件不存在, 创建它,此时需要第三个参数设置新文件的访问权限

O_APPEND : 追加写

OK,我们下面来使用以下open,我们打开一个已经存在的文件,写权限打开,如果有内容直接清空

#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>  int main() {     //以写的方式打开,如果文件存在,清空内容;不存在,创建     int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC);      if(fd == -1)     {         perror("open falied\n");         return 1;     }      //....      //关闭文件     close(fd);      return 0; }

OK,没有问题!我们再来打开一个不存在文件,也是和上面的同样:

#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>  int main() {     //以写的方式打开,如果文件存在,清空内容;不存在,创建     int fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC);      if(fd == -1)     {         perror("open falied\n");         return 1;     }      //....      //关闭文件     close(fd);      return 0; }

果然创建出来了,但是我们发现他的权限是有问题的!这里他的权限是乱码!这和我的预期不符呀!如何解决呢?OK,这就是我们要介绍的第三个参数:mode

我们上面介绍open时说了,mode是创建不存在文件时,指定新文件访问权限的参数!那我们在创建文件时指定为666(八进制):

#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>  int main() {     //以写的方式打开,如果文件存在,清空内容;不存在,创建     int fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//创建权限为666      if(fd == -1)     {         perror("open falied\n");         return 1;     }      //....      //关闭文件     close(fd);      return 0; }

这次不是乱码了,但是我们发现我们设置的权限时666,这里怎么变成了664了?其实这个问题我们以前介绍过,权限掩码的问题!我们是普通用户所以umask是0002(八进制),他会拿着我们的初始权限0666和~umask按位与一下,然后的是最终的权限,如何避免呢,我就是想666呢?我们可以调用系统提供的umask接口来设置当前进程创建文件的umask:

#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>  int main() {     umask(0000);//将权限掩码设置为000     //以写的方式打开,如果文件存在,清空内容;不存在,创建     int fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//创建权限为666      if(fd == -1)     {         perror("open falied\n");         return 1;     }      //....      //关闭文件     close(fd);      return 0; }

注意:程序的权限掩码和系统中的权限掩码优先使用,最接近的!这里程序中的近,所以就使用了程序中的!

write

作用:向文件中写入

参数:fd文件描述符(可以理解为向哪个文件写) buf表示写入的文件内容 (注意没有\0) count表示写入内容的长度/字符个数;

返回值:成功,返回写入字节数;失败,返回-1

OK,我们和open配合使用一下,上面我们演示的是以不存在,创建;存在,先清空,再写入!

这里,我们使用一下,追加的方式打开,不存在就创建:

#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>  int main() {     //以追加的方式打开,如果文件存在,追加;不存在,创建     int fd = open("file.txt", O_WRONLY | O_CREAT | O_APPEND , 0666);//创建权限为666      if(fd == -1)     {         perror("open falied\n");         return 1;     }      const char* msg = "hello write\n";     //写入     int cnt = 3;     while (cnt--)//追加3次     {         write(fd, msg, strlen(msg));     }      //关闭文件     close(fd);      return 0; }

OK, 没有问题!write很简单的~!

read

ok,举个简单的例子:

#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>  int main() {     //以只读的方式打开     int fd = open("file.txt", O_RDONLY , 0666);//创建权限为666      if(fd == -1)     {         perror("open falied\n");         return 1;     }      char buff[1024];//简单的模拟一下缓冲区     //读取     ssize_t n = read(fd, buff, sizeof(buff));     if(n > 0)         buff[n] = '\0';          //打印出来     for(int i = 0; i < n; i++)     {         printf("%c ", buff[i]);     }      //关闭文件     close(fd);      return 0; }

OK,介绍了这么多,我们其实发现这些系统调用和我们以前在C语言学过的很像!其实,我们C语言的库函数都是封装的这些系统调用!

FILE* fopen("f.txt", "r")就是对int open("f.txt", O_RDONLY);的封装

FILE*fopen("f.txt", "w")就是对int open("f.txt", O_WRONLY | O_CREAO_TRUNC, 0666);的封装

FILE* fopen("f.txt", "r")就是对int open("f.txt", O_WRONLY | O_CREAT | O_APPEND , 0666);的封装

以及fwrite和fread等同理!

OK,上面说的理解了,就是不同的语言有不同的文件操作库函数,虽然他们的用法有差别,但是底层都是调用了这些系统调用;但是上面C语言和系统调用他俩的返回值有些不好理解,C语言的返回的是文件指针,倒还好理解,向文件指针指向的文件写嘛,但是系统调用的返回一个数字咋理解?怎么就可以往一个数字里面写呢??它两有啥关系呢?虽然我们目前还没有介绍文件描述符,但是根据上面的介绍有一点我们一定可以确定:FILE*一定是封装了文件描述符的!为什么要封装呢?文件描述符介绍了在解释!

文件描述符

上面我们只说了open的返回值是一个整数fd,叫做文件描述符,他究竟是个啥呢?下面我们隆重的介绍一下文件描述符!

文件描述符的本质

文件描述符的本质是什么呢?直接先说结论,再解释原理!

文件描述符的本质是进程和文件映射关系数组fd_array的下标!

既然是数组的下标那必然是>=0的,这也符合我们前面对open返回值的介绍;OK,我们来写个代码验证一下:

#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>  int main() {     umask(0000);     //打开文件     int fd1 = open("loga.txt.txt", O_WRONLY | O_CREAT, 0666);     printf("fd1 : %d\n", fd1);      int fd2 = open("logb.txt.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);       printf("fd2 : %d\n", fd2);        int fd3 = open("logc.txt.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);        printf("fd3 : %d\n", fd3);      //关闭文件     close(fd1);     close(fd2);     close(fd3);      return 0; } 

的确是大于等于0的,但是他怎么是从3开始依次增长的呢?0、1、2去哪了呢?上面我们介绍了,我们有一个进程启动的时候OS会给我们默认打开三个流:标准输入stdin、标准输出stdout、标准错误stderr;其实他们分别占用的就是0、1、2下标;

简单验证一下:既然1是标准输出,那我们往1里面写文件不就直接写到显示器了!

#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>   int main() {        const char* msg = "hello world\n";          int cnt = 3;     while (cnt--)     {         write(1, msg, strlen(msg));         sleep(1);     }      close(1);      return 0; }

OK,说了这么多,目前我就记住了fd是进程和文件的映射数组的下标~!但是说实话我还是不太理解,下面我们来介绍一下open打开一个文件的过程

我们知道打开和操作文件的本质是进程在打开和操作,每一个进程打开一个文件,OS会为其创建一个struct file的结构体对象,而一个进程可以有很多打开的文件,且OS中进程是很多的,所以OS需要对这些struct file的结构体对象管理起来!如何管理?先描述,在组织!OS会对每一个文件的struct file对象以双链表的形式管理起来(并为每一个struct file 对象开辟一个内核寄级文件缓冲区,用来存储该文件的操作内容),但是每个进程在这么多的struct file对象中,如何知道自己打开的是哪一个呢?其实在进程和struct file之间还会一张记录对应进程打开文件的表,叫文件描述符表files_struct,在文件描述符表里面有一个进程文件映射关系的数组fd_array,他是一个struct file*的指针数组,数组中的每个元素指向该进程打开的文件!所以当上层调用open打开文件时,先会在OS中创建struct file的对象和开辟对应的文件缓冲区,之后加载数据(可能会延后),然后查看对应进程的文件描述符表,将该struct file对象的指针放到fd_array的合适位置,最后给上层返回对应的fd_array的下标,然后上层就可以拿着open的返回值fd进行其他操作了,例如当上层在拿着fd去write的时候,因为write是系统调用所以他就知道fd是fd_array的下标,就可以找到相对应的struct file对象,然后将上层要写入的内容(用缓冲区中的内容)拷贝到fd下标位置中的struct file对象指向的file文件的内核级文件缓冲区,然后在由OS定期向磁盘刷新!

上面的内容对用的就是这张图:

OK这就是我们对文件描述符的介绍,上面也介绍了C语言的文件操作是对系统调用的封装!那既然是封装一定有fd等相关信息喽!OK,查看一下:

#include <stdio.h>  int main() {     printf("stdin->fd : %d\n", stdin->_fileno);     printf("stdout->fd : %d\n", stdout->_fileno);     printf("stderr->fd : %d\n", stderr->_fileno);      FILE* fp1 = fopen("f1.txt", "w");     printf("fp1->fd: %d\n", fp1->_fileno);      FILE* fp2 = fopen("f2.txt", "w");     printf("fp2->fd: %d\n", fp2->_fileno);      FILE* fp3 = fopen("f3.txt", "w");     printf("fp3->fd: %d\n", fp3->_fileno);      fclose(fp1);     fclose(fp2);     fclose(fp3);          return 0; } 

文件描述符的分配规则

文件描述符的规则:查自己的文件描述符表,分配最小的没被使用的fd

ok,验证一下:

#include <stdio.h> #include <unistd.h> #include <sys/types.h>  #include <sys/stat.h> #include <fcntl.h>  int main() {     umask(0000);     //先关掉 0 -> stdin     close(0);      //打开文件     int fd = open("log.txt.txt", O_WRONLY | O_CREAT, 0666);      //看一下新的文件fd     printf("fd : %d\n", fd);      //关闭文件     close(fd);      return 0; } 

这里我们在打开前先把0给关了,然后当open打开的时候去给新的文件分配的时候查找文件fd——array然后发现0是没有给分配所以刚打开的fd就是0!你可能会问,原来打来的0号文件呢?答案是系统会自动回收!

C语言为什么要对底层的系统调用进行封装呢?

我们既然可以使用系统调用问什么C语言还要对系统调用接口进行性封装呢?OK,先说结论,在解释!

• 为了使代码具有跨平台性

• 为用户考虑,方便编码,提高了编码效率

首先解释第一点,不同系统的系统调用接口是不一样的,如果C语言不对OS调用接口封装的话那我们得直接调系统调用,假设你今天在Linux上写的代码想在mac os以及win上跑是不行的,因为不同的系统的OS调用接口不同!这就是使得代码的跨平台性差,历史也会最终淘汰掉!第二点,系统调用时你必须得对系统调用有一定的了解才可以,所以编码的成本变高,工作效率较低!而C语言对不同的系统调用封装后,我们上层用户只管开发,关于跨平台的原因C语言已经解决了,而库函数比系统调用使用的成本简单,开发效率就提升了!

理解Linux下一切皆文件

OK,通过上面的介绍,我们认识到访问文件时OS的内核只认文件描述符fd; 我们说过每个进程在启动的时候OS会默认打开:标准输入stdin(键盘)、标准输出stdout(显示器)和标准错误stderr(显示器),他们分别是fd_array的0、1、2下标,我们上面也演示了可以拿着他们三个去操作文件,但是他们说到底是硬件啊,怎么可以和文件一样操作呢?其实这就是我们要介绍的Linux一些皆文件!

键盘、显示器、鼠标、网卡、磁盘等本质是硬件,这些硬件也是要被管理起来的,如何管理?先描述,在组织!OS会通过驱动层拿到对应硬件的数据,然后通过结构体进行描述,对每个种硬件进行创建相应的对象;然后用某种数据结构(例如双链表管理起来),然后对硬件操作就变成了对数据结构的操作;而这些硬件本质上的功能都是一样的即I/O操作和文件差不多,所以OS就把他们抽象成文件看待,给他们创建struct file,里面一部分他们的属性,一部分是他们I/O方法的指针,指向他们的I/O方法,这些I/O方法通常是硬件的厂商提供的,所以站在OS以及以上的视角看到所有的外设不都是struct file文件吗?所有的外设操作不就是和操作文件一样吗?这就是Linux下一切皆文件!将抽象出来的struct file这一层称为VFS即虚拟文件系统!其实这种方法就是多态,或C语言实现类!

重定向

我们知道每一个进程在启动的时候,操作系统会默认的打开,stdin(键盘)、stdout(显示器)、stderr(显示器),他们分别对应的fd是0、1、2;那我们把1给关闭了会出现什么情况呢?

#include <stdio.h> #include <unistd.h> #include <sys/types.h>  #include <sys/stat.h> #include <fcntl.h>  int main() {     umask(0000);     //先关掉 1 -> stdout     close(1);      //打开文件     int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);      if(fd < 0)     {         perror("open\n");         return 1;     }      printf("printf, fd %d\n", fd);     fprintf(stdout, "fprintf, fd %d\n", fd);     fflush(stdout);      //关闭文件     close(fd);      return 0; } 

这是什么原因呢?其实稍微一想,本来是默认向显示器写的内容写到了文件!这和我们以前介绍的输出重定向一样!如下:

OK,既然知道了,这是重定向了,下面我们就来介绍一下重定向的原理:

重定向的原理

我们知道printf/fprintfC语言,他们都是默认向显示器写入的,也就是写到FILE* stdout,FILE* stdout的值stdout->_fileno默认是等于1的,是不变的,也就是C语言(上层语言)的stdout是无论什么情况都是默认写到fd == 1位置指向的文件的!而上面我们将1号文件给关了,又打开了一个log.txt的文件,根据上面介绍的文件描述符规则,我们可以知道此时的log.txt的fd就是1,但是我们的C语言(上层语言)不知道此时1号位置指向的文件已经改变,当再次写入时依然写到了1号位置指向的文件!所以这就是为什么本应该写到显示器的内容跑到了文件的原因~!

根据上面的介绍,我么可以得出结论:

重定向的本质是:改变内核fd_array数组中特定下标的内容,与上层无关!

OK,那我们是不是以后重定向也得先关1号文件,然后在打开?是不是有点挫呀!再说了我们在使用ls 命令重定向的时候也没有关闭1啊,他们是怎么做到的呢?其实系统为了方便重定向,给我们提供了一个接口:dup2,下面我们就来学习使用一下这个接口!

dup2

dup2的作用就是将内核数据结构fd_arrary的数组的oldfd下标中的内容拷贝到newfd下标中

有了dup2接口我们就不在先关闭,在打开了!OK,我们写个代码看一下:

#include <stdio.h> #include <unistd.h> #include <sys/types.h>  #include <sys/stat.h> #include <fcntl.h>  int main() {     umask(0000);     int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);     if(fd < 0)     {         perror("open\n");         return 1;     }      //使用dup2重定向     dup2(fd, 1);      printf("printf, fd %d\n", fd);     fprintf(stdout, "fprintf, fd %d\n", fd);     fflush(stdout);      //关闭文件     close(fd);      return 0; }

追加重定向的话就是,再打开的时候将标记位O_TRUNC换成O_APPEND,然后就变成了追加重定向,OK上面的代码只需要换一下:

#include <stdio.h> #include <unistd.h> #include <sys/types.h>  #include <sys/stat.h> #include <fcntl.h>  int main() {     umask(0000);     int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);     if(fd < 0)     {         perror("open\n");         return 1;     }      //使用dup2重定向     dup2(fd, 1);      printf("printf, fd %d\n", fd);     fprintf(stdout, "fprintf, fd %d\n", fd);     fflush(stdout);      //关闭文件     close(fd);      return 0; }

缓冲区

在介绍重定向的时候,我们发现我们写的代码总是带一个fflush这是啥呢?可不可以去掉呢?OK,我们去掉试一试:

#include <stdio.h> #include <unistd.h> #include <sys/types.h>  #include <sys/stat.h> #include <fcntl.h>  int main() {     umask(0000);     //先关掉 1 -> stdout     close(1);      //打开文件     int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);      if(fd < 0)     {         perror("open\n");         return 1;     }      printf("printf, fd %d\n", fd);     fprintf(stdout, "fprintf, fd %d\n", fd);          //fflush(stdout);      //关闭文件     close(fd);      return 0; }

为啥不加个fflush就不显示呢?这是啥情况?其实不光我们的系统内部存在内核级的文件缓冲区,我们的语言层也是存在的!OK,我先画个图,解释一下,上面代码为什么没有写入到文件:

我们知道main函数中在return时代表进程结束,且会在结束前冲刷缓冲区!但是这里在冲刷之前先把文件给关了,等进程结束前刷新时发现找不到要写入的文件了,所以我们就看不到写入log.txt的数据了!那我们把clos给注释掉是不是就可以看到了呢?

果然我们看到了!这也符合我们的介绍!所以这就是为什么上面重定向的时候要到fflush的原因,是想在关闭文件之前将用户级别的缓冲区刷新到内核,然后再由OS刷到外设!OK,上面也只是验证了一下以前的一个代码!下面我们再来重新的介绍一下缓冲区!

什么是缓冲区?

缓冲区就是一段内存空间!

缓冲区分为:用户级缓冲区和内核级缓冲区

为什么要有缓冲区?

上层减少对底层系统调用/硬件I\O接口的调用次数,给上层提供高效的I\O体验,间接提高整体的效率!

这里你可可能会想不管是内核的还是用户的缓冲区,我都是多了次拷贝呀!他怎么还效率高了呢?

举个例子:你买东西是到商场买,还是到工厂买?是不是商场啊!原因是商场的是现货,你到工厂区代价比较大,也不方便!商场不就是一个缓冲区吗?给用户的体验更好,假设没有商场你今天买一个键盘还得去工厂,明天买一个水杯又要去工厂是不是体验很差啊!而你直接去商城那现成的是不是效率高多了?此时虽然多了一次转手,但是比以前体验好了!所以有了缓存可以提高用户的使用效率!另外,假设你现在在北京,要给你云南的朋友送一个键盘,你会拿着键盘自己送过去吗?是不是不会呀,你只需要下楼给楼下的顺丰即可,然后又可以上楼开心的玩原始人了,等到时间他收到即可!顺丰快递会不会立即把你刚邮寄的包裹立刻送?显然不会,顺丰会等到比如100个包裹一起配送!此时顺风不就是一个缓冲区吗?你寄快递不就是调用语言层面的I\O接口吗?此时虽然多了一次转手(拷贝)但是用户的体验变好了,使用者的效率变高了!

缓冲区的刷新策略

立即刷新; 例如:用户级别的刷新:fflush(stdout); 内核级别的刷新:fsync(int fd);

行刷新;例如:显示器(照顾用户的使用习惯)

全缓冲:缓冲区满了才刷新,一般是普通文件;

特殊情况:进程退出,OS自动刷新!强制刷新:\n等

注意:这里说的是缓冲区的刷新策略,并没特质是用户还是内核!

stderr

在介绍stderr之前,我先来谈谈为什么会有stdin\stdout:

我们平时写的程序,本质都是对数据的处理(计算/存储等),这个过程需要知道数据从哪里来,以及数据去了哪里!比如你写个hello world数据是要从键盘获取的,它的执行结果是不是用户也需要看到啊!所以stdin(1)和stdout(1)是方便用户进行动态的与程序进行交互!但是我实在不理解的是为什么还要有stderr(2)呢,他不是对应的也是显示器吗?OK,我们来写一段代码看看:

#include <stdio.h>  int main() {     fprintf(stdout, "fprintf stdout\n");     fprintf(stderr, "fprintf stderr\n");     return 0; }

果然这也验证了我们前面的结论是正确的即1和2都是显示器文件!且是同一个显示器文件,不然上面也不可能同时打印!如下图:

为什么要有stderr?

那既然这样不是只有一个就够了吗,为什么还有stderr呢?OK,下面我们先来看看他们的区别:

我们发现我们本来是将他两都重定向到log.txt的但是为什么只有stdout写进去了呢?其实我们介绍的输出重定向叫做标准输出重定向,也就是他只会把1号fd对应的内容给更改了!但是stderr是2号,依然指向显示器的,所以stdout就写到了log.txt文件,stderr就写入了显示器!

这有什么用呢?其实这样可以将程序的正确信息和错误信息分开,可以更加容易的识别错误,对调试很有帮助!如如说,当前程序结束后有很多的输出,你想知道那些是错误的就可以直接用重定向分流!

假设这些都是输出,我们想找错误的就可以,重定向分流:

其实我们语言上学的perror以及cerr本质就是想stderr(2)中打印,而cout和printf是将stdout(1)中打印!

如果我现在也想让fd2和fd1卸载同一个文件中呢?

./test 1 > log.txt 2>&1

什么意思呢?先将./test fd1中的本应该输出到显示器的内容,从定向输出到log.txt中,然后再将fd2中的原本输出到显示器的内容重定向到stdout也就是fd1位置的文件,而fd1位置的文件已经被换成了log.txt所以stdout和stderr写入到了同一个文件!

OK,本期内容就分享到这里!好兄弟,我们下期再见!

结束语:未来可期!

    广告一刻

    为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!