目录
重定向
介绍
重定向是一种操作,可以将一个程序的输入或输出从标准位置(通常是终端)更改为其他地方,如文件或另一个程序
本质
但实际上我们了解fd后就可以知道,内部是使用fd来操作文件的,所以重定向需要改变fd相关数据
fd的分配规则
引入
当我们把fd=1的文件(也就是标准输出)关掉后,且以只读方式打开log.txt
会发现执行printf没有反应:
因为1对应的是标准输出的文件描述符,一旦关闭,1和终端就没有关联了(printf默认输出fd=1的文件)
若以写方式打开,其他不变
会发现log.txt的fd是1,写入的内容在log.txt中
这是为什么呢?
介绍
实际上,分配fd是优先从最小开始找,找到的第一个没有被占用的就是该文件的描述符
- 由于1关掉了(也就是被切断了与文件的联系,即数组中存放的指针变为null)
- 当为log.txt份分配fd时,从0开始找
- 0被stdin占用
- 1的位置是空的
- 所以log.txt的fd就是1了
计算机并不管数组中0,1,2的位置存放的谁的指针,只认fd
- 因为它默认stdout的位置就在1所在位置放着的,所以printf就会直接向1指向的文件中写入
- 而此时1已经是log.txt的fd了
- 所以printf的内容就写入到log.txt中了
重定向引入
图示
上面的例子总的来说,我们将本来应该写入显示器的内容,写入到了其他文件中
这不就是重定向吗!!!
追加重定向模拟
把选项换掉,就可以模拟出追加重定向
上面的代码太挫了,得先把标准流关掉,再打开另一个文件
我们可以直接使用dup2函数来重定向
系统调用 -- dup2
函数原型
- 它使newfd成为oldfd的副本
- 也就是把oldfd中的内容拷贝到newfd中,最终两个空间都指向oldfd指向的文件结构体
- 也就是说 -- 对newfd的操作,实际执行在oldfd的文件中
输出重定向
这里将标准输出重定向到log.txt中:
追加重定向
比上面的代码增加一个追加项即可
输入重定向
将标准输入重定向到log.txt中,也就是让fgets从文件读取:
命令行重定向
符号
拷贝文件内容
先将test.txt内容作为cat命令的输入,再把cat的输出重定向到arr.txt中
stdout和stderr的区别
虽然他俩都对应显示器文件,但他俩是不同的
示例
上面的代码,直接执行,似乎没有什么异常
但是,一旦进行重定向,只有stdout中的数据被重定向
也就是 -- 只有正确信息被重定向到文件,而错误信息还是打印到显示器上
应用
这样就可以在一堆打印中,分出正确的和错误的信息,来进行筛查
如何将打印的错误信息写入文件
./test > test.txt 是一种把前边的标准输出1忽略的写法
- 它将test.txt的fd所在空间保存的指针拷贝进./test的标准输出的位置上
- 这样就将标准输出重定向到test.txt中
2 > err.txt 是标准的语法,意思是将标准错误重定向到err.txt
- 这样显式的写,可以让标准错误进行重定向
- 重定向操作符的语法规则是 n > filename,其中n表示文件描述符,filename表示要重定向到的文件名
- 若想要在filename的位置也表示文件描述符,可以在fd前加&,&会将fd解释为文件描述符,而不是文件名
如何将所有打印信息写入一个文件
前面的例子都是让两种信息分离,如何都放在一起呢?
./test > test.txt 2>&1
- 可以理解为:先将stdout重定向到test.txt中
- 此时1的位置保存的就是test.txt的file*
- 再将2重定向到1
- 也就是将2的位置也保存1的指针 -- test.txt的file*
- 所以,1和2都指向test的file
./test &> test.txt
也可以写成 ./test &> test.txt
- 表示将 标准输出stdout和标准错误输出stderr 都重定向至 指定的文件test.txt中
- ./test >& test.txt同理
在minishell中添加重定向功能
思路
重定向指令会带有>或<,所以可以直接判断里面是否存在这些符号,根据不同的符号,完成不同的操作
这里我们不识别空格
示例
代码
#include<stdlib.h> #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> #include<string.h> #include<fcntl.h> #include<sys/stat.h> #define num 1024 #define size 32 #define SEP " " //设定重定向的标志码 #define WRITE 1 #define APPEND 2 #define INPUT 3 char cmd[num]; char* options[size]; int status; char* judge(char* start){ char* end=start+strlen(cmd)-1; //指向最后一个有效字符 while(end>=start){ if(*end=='>'){ if(*(end-1)=='>'){ //">>" status=APPEND; *(end-1)='\0'; // xxx>>xxx 从第一个>开始截断 end++; //end指向重定向目的位置的文件名的第一个字符 //start指向要被重定向的文件名的第一个字符 break; } else{ status=WRITE; *end='\0'; //同理 end++; break; } } else if(*end=='<'){ status=INPUT; *end='\0'; //同理 end++; break; } else{ end--; //如果不是>,<,就往后轮 } } if(end>=start){ //如果是没有执行完while,就break,就说明有重定向 //exist redirect return end; //返回文件名 } else{ return NULL; } } int main(){ extern char** environ; while(1){ printf("root@localhost myshell#"); fflush(stdout); memset(cmd,'\0',sizeof cmd); if(fgets(cmd,sizeof cmd,stdin)==NULL){ continue; }//ctrl+z to quit cmd[strlen(cmd)-1]='\0'; //输入命令后,就可以先判断里面是否有重定向标志符号了 char* situation=judge(cmd);//返回值为要重定向的文件名,如果没有,该变量为null options[0]=strtok(cmd,SEP); //拿到指令 int i=1; if(strcmp(options[0],"ls")==0){ options[i++]="--color=auto"; } while(1){ //拿到后续的选项 options[i++]=strtok(NULL,SEP); if(options[i-1]==NULL){ break; } } if(strcmp(options[0],"export")==0){ char* my_env=(char*)malloc(sizeof(char)*sizeof(options[1])); strcpy(my_env,options[1]); putenv(my_env); continue; } // for(i=0;options[i];i++){ //用于测试 // printf("%s\n",options[i]); // } if(strcmp(options[0],"cd")==0){ if(options[1]!=NULL){ chdir(options[1]); //change work_path } continue; } pid_t id=fork(); if(id==0){ int fd=0; if(status!=0){ //如果存在重定向,则status不为0 switch(status){ case WRITE: fd=open(situation,O_WRONLY|O_TRUNC|O_CREAT,0666); dup2(fd,1); //将对1的操作执行到fd中 break; case APPEND: fd=open(situation,O_WRONLY|O_APPEND|O_CREAT,0666); dup2(fd,1); break; case INPUT: fd=open(situation,O_RDONLY); dup2(fd,0); break; default: printf("status fail\n"); break; } } //如果没有重定向,普通执行命令即可 execvpe(options[0],options,environ); exit(1); } else{ int status=0; pid_t ret=waitpid(-1,&status,0); if(ret>0){ //success //printf("%s:%d\n",options[0],WEXITSTATUS(status)); } else{ //fail printf("fail\n"); } } } return 0; }