目录
前言:
首先来回顾一下open函数,即在进程中同时打开多个文件:
Linux底层进程与文件的关系 :
二.重定向的实现
什么是重定向?
方法1:
2.1关闭stdin:
运行结果:
编辑由结果知:fd1指向文本文件cyq.txt,原本fd1是默认被fd_array[2]指向的,现在fd1却被fd_array[0]指向了。
2.2关闭stderr
运行结果:
2.3关闭stdout :
查看该文件cyq.txt:
运行结果:编辑
总结:
方法2:
2.4重定向函数dup2()的学习
2.4.1函数作用:
2.4.2.参数
2.4.3返回值问题:
2.4.4案例——输出重定向:一般用于写内容到指定的地方
运行结果:
运行结果:
一次运行:
二次运行:
2.4.6输入重定向案例:一般用于从指定的地方读取内容。
运行结果:编辑
方法3:符号形式的重定向—— “ > ” 、 “ >> ”、“ < ”
2.5.1 符号 “ > ” ——输出重定向符号
实验案例:该案例将围绕123.txt文件内容为中心理解符号形式的重定向实现
编辑
2.5.2 符号“ >> ” ——追加重定向
案例:
2.5.3符号 “ <” ——输入重定向
前言:
之前,我讲解了Linux下文件的特性、讲解了关于Linux关于文件的系统调用函数open、write、read、close函数,讲解了它们与C语言库中fopen、fclose、fgets、fputs等函数的区别,其实fopen、fclose、fgets、fputs这些函数是在Linux系统调用函数的底层原理下封装形成的优化C库函数。
Linux文件基础IO的理解1
接下来,我将继续深入讲解Linux中文件的一些底层知识。
首先来回顾一下open函数,即在进程中同时打开多个文件:
#include<errno.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<stdlib.h>#include<unistd.h>#define Filename1 "big1.txt"#define Filename2 "big2.txt"#define Filename3 "big3.txt"#define Filename4 "big4.txt"int main(){//同时打开多个文件int f1=open(Filename1,O_CREAT| O_WRONLY |O_TRUNC,0664);int f2=open(Filename2,O_CREAT| O_WRONLY |O_TRUNC,0664);int f3=open(Filename3,O_CREAT| O_WRONLY |O_TRUNC,0664);int f4=open(Filename4,O_CREAT| O_WRONLY |O_TRUNC,0664);//打印这些文件描述符的值printf("f1: %d\n",f1);printf("f2: %d\n",f2);printf("f3: %d\n",f3);printf("f4: %d\n",f4); close(f1);close(f2);close(f3);close(f4);return 0;}
运行结果:
从结果中,我们看到这4个文件描述符的值分别是3-6,那么有人会想为什么是从3开始往后呢?为什么不是从1开始,或者从0开始呢?
其实这就要说到标准流了! 我们在学C语言的过程中,相必从书中就已经了解到了多种流,它们分别是标准输入流stdin、标准输出流stdout、标准错误流stderr,这三种流分别对应键盘、显示器、显示器。stdin标准输入流就是通过键盘向计算机中写入数据的;stdout标准输出流是通过读取计算机中的正常运行得出的数据到显示器中,方便查看的;stderr标准错误流是读取计算机在运行过程中产生的错误信息到显示器中。
Linux下一切皆文件,那么这三个流也不例外,它们也是文件,而且它们三个是处于一直被打开的状态,因为每个进程的运行都离不开它们几个,于是stdin、stdout、stderr所代表的也是文件描述符,每个进程都有一个文件描述符表,它相当于一个数组,专门用于存放该进程打开的各个文件。这三个文件描述符就占据了数组的榜一榜二榜三位置,导致后面被进程打开的文件只能从数组索引位置3开始!
求证一下:
结果运行:
Linux底层进程与文件的关系 :
每个进程都有一个task_struct的结构体,该结构体中存放着该进程的各个属性,既然一个进程打开了多个文件,那么这几个被该进程打开的文件就必须要被管理和维护,所以task_struct的进程属性中就又多了一个属性:file_struct* files指针,该指针指向记录着该进程所打开的所有文件的结构体:files_struct。
Linux下一切皆文件的本质和 “先描述,再组织 ” 的核心管理理念,使得我们能够清楚的了解到这个结构体中包含着每个被打开文件的各个属性,其中的成员file* fd_array[ ]指针数组便是存放着文件的各个文件描述符。所以本质上,文件描述符就是指针数组的下标。所以进程和被打开文件的关系就是通过文件描述符表files_struct映射的。
二.重定向的实现
什么是重定向?
重定向就是说改变数据的流向!举个例子大家就明白了:有一家厂商给超市供大量货物时,由于厂商偷工减料赚黑心钱,被超市发现后立即停止该家厂商继续供货,另外寻找其他厂商商量超市货物供应。这就是重定向,在原本的基础数据流程方向上进行转换,重新选择新的数据流程方向。
重定向的实现共有三种方式,其中两种是需要在程序中使用代码去实现重定向,还有一种就是直接使用>、>>、<符号去实现重定向。
方法1:
既然stdin,stdout,stderr这三个流的类型也是文件,那么意味着它们可以被close,假如我们关闭了file* fd_array[ ]数组中的前3个标准流,会发生什么?
2.1关闭stdin:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main(){close(0); //关闭stdin流int fd1=open("cyq.txt",O CREAT O WRONLY O TRUNC,0666);if(fd1<0){perror("open");exit(-1);}printf("open fd1: %d\n", fd1);close(fd1);return 0;}
由上面代码可知:关闭了文件描述符表中数组的首元素,意味着fd_array[0]不再指向stdin——键盘,那么该数组的首元素为空。
运行结果:
由结果知:fd1指向文本文件cyq.txt,原本fd1是默认被fd_array[2]指向的,现在fd1却被fd_array[0]指向了。
2.2关闭stderr
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main(){close(2); //关闭stderr流int fd1=open("cyq.txt",O CREAT O WRONLY O TRUNC,0666);if(fd1<0){perror("open");exit(-1);}printf("open fd1: %d\n", fd1);close(fd1);return 0;}
运行结果:
根据这个案例的结果,我们可以证出了一些东西:假如当fd_array[ ]数组中有某个指向文件的描述符下标2被关闭了,变成空闲的了,那么后面的文件描述符指向新的文件时,就会由描述符下标2去指向新文件。
一般来说,不关闭这3个默认的流时,fd默认是在指针数组fd_array[ ]的下标3处开始分配,一旦关闭了这3个的某一个,那么fd就会被分配到数组中空闲的那一个下标位置处。
2.3关闭stdout :
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(){close(1); //关闭stdout流int fd1=open("cyq.txt",O CREAT O WRONLY O TRUNC,0666);if(fd1<0){perror("open");exit(-1);}printf("open fd1: %d\n", fd1);fprintf(stdout, "open fd:%d\n",fd1);close(fd1);return 0;}
运行结果:
从结果可知:我们发现执行该程序时,没有给出任何结果,什么都没有显示!这是为什么?其实很好解释。因为我们关闭了stdout输出流,那么printf函数的内容也就不再从显示屏中输出了,且czq.txt的文件描述符放在了fd_array[1]中,所以printf的输出内容都打印到了cyq.txt文件中去,所以当我们执行该程序时才什么都不会显示。
查看该文件cyq.txt:
使用cat指令查看该文件的内容时,也是什么都没有显示出来,这又是为什么?
其实,printf的内容先是传输到了系统的缓冲区中,需要使用fflush(stdout);才会强制刷新缓冲区,将内容传到文件中!
运行结果:
总结:
fd的分配规则:
从小到大,自顶向下的,且该处数组下标处没有被使用的位置为其分配fd地址。
这就是所谓的“重定向”!改变数据的流向,让原本写入A文件的数据不再写入A中,而是写入指定的B文件中。而上面的几个案例是:让原本写入流中的数据,写入指定的文本文件中。
但是这几个案例的方式有些低效率!是需要先关闭数组的某个下标处的元素,然后重定向新的文件描述符到该下标处。
方法2:
2.4重定向函数dup2()的学习
dup2();
2.4.1函数作用:
该函数的作用就是直接改变指针数组中下标处元素的数据流向。
2.4.2.参数
最常用的是dup2函数接口,参数oldfd是当前新建文件的文件描述符下标处(源目的地),newfd是要拷贝到的指定的文件数组下标处(最终目的地)。
2.4.3返回值问题:
若调用成功,则成功改变数组的下标处数据指向;若失败,则返回-1
2.4.4案例——输出重定向:一般用于写内容到指定的地方
#include<std1o.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<fcntl.h>
#include<errno.h>
#include<unistd.h>
#include<stdlib.h>
int main(){int fd2=open("cyq.txt",0_CREAT O WRONLY O TRUNC ,0666);if(fd2<0){perror("open file");exit(-1);}//方法2:dup2(fd2,1); //将old fd,拷贝到new fd中printf("open fd2:%d\n",fd2); //打印到文件中了fprintf(stdout,"open fd:%d\n",fd2);int cnt=3;while(cnt){printf("hello my girl,%d\n",cnt--);} //因为内容在缓冲区中,必须强制刷新出来fflush(stdout);close(fd2);return 0;}
运行结果:
结果解析:虽然dup2函数改变了fd_array[1]的数据指向到了文本文件中,但fd_array[3]的数组元素也是指向该文本文件的,所以fd2的值为3是正确的。
使用dup2(fd,1); 函数造成的结果和使用close(1); 造成的结果是一样的,都是切断了从显示屏中输出的内容转而到了指定文件中。
2.4.5案例——追加重定向(是输出重定向的一种):
该案例在输出重定向的基础下只改变了open函数的O_APPEND属性。其他不变。
运行结果:
一次运行:
二次运行:
2.4.6输入重定向案例:一般用于从指定的地方读取内容。
int fd2=open("cyq.txt",O_RDONLY); if(fd2<0){perror("open file");exit(-1);}dup2(fd2,0); //输入重定向char str[64];while(1){printf(">: ");if(fgets(str,sizeof(str),stdin)==NULL){break;}printf("%s",str);}
解析:输入重定向是从改变数组元素指向的stdin流,转而指向文本文件,之后我们便可以读取该文件的内容。
运行结果:
方法3:符号形式的重定向—— “ > ” 、 “ >> ”、“ < ”
2.5.1 符号 “ > ” ——输出重定向符号
实验案例:该案例将围绕123.txt文件内容为中心理解符号形式的重定向实现
如上图:在该目录下共有四个文件,其中文本文件123.txt内容为空。
执行语句“ ls > 123.txt ”后,查看123.txt文本文件的内容可知,里面存放着4行内容,这4行的内容的来源是:ls命令为查看当前目录下存在的文件和目录名称,而“ ls > 123.txt ” 就是将ls显示出来的结果输出到123.txt文件中去。
再做几组练习,加深理解:
pwd命令为显示当前所在位置的绝对路径。pwd >123.txt 就是将pwd的显示结果输出到123.txt中。
echo "......"的作用就是在屏幕中显示echo指令后面的内容,而echo+内容>file 就是输出重定向到文件中。
2.5.2 符号“ >> ” ——追加重定向
上面说过,追加重定向是输出重定向中的一种形式,只不过每次输出重定向是在指定流向的开头去输出内容;而每次追加重定向是在指定流向的上一次输出内容的末尾进行追加内容。
案例:
2.5.3符号 “ <” ——输入重定向
”<” 表示的是输入重定向的意思,就是把<后面跟的文件取代键盘作为新的输入设备。
cat -n < a.txt
将文件a.txt中的内容作为cat命令的输入grep 'a' < a.txt
将文件a.txt中的内容作为grep命令的输入
以上就是Linux系统中重定向的理解和三种做法。