💪 今日博客励志语录:
人生总会有自己能力所不及的范围,但是如果你在你能力所及的范围尽了全部的努力,那你还有什么遗憾呢
★★★ 本文前置知识:
匿名管道
输入以及输出重定向
1.内容回顾
那么在上一篇文章中,我详细介绍了父子进程或者说有血缘关系的进程之间进行进程通信的方式就是采取匿名管道来实现,那么所谓的管道,它其实是一个特殊的文件,之所以特殊是因为它不需要像普通的文件那样写入到磁盘中,所以我们称管道叫做内存级文件,那么父子进程通过管道来通信,那么只能进行单向通信,如果双方同时对管道文件进行读写那么必定会造成内容混乱,所以只能一个进程向管道文件中写入,另一个进程从管道文件中读取,那么就要求父子进程关闭各自的读写端,那么这就是对于上文的匿名管道的内容的一个大致的回顾,那么如果对此感到有点陌生或这样遗忘的话,可以看我介绍匿名管道的那一期博客
2.Linux的匿名管道指令
那么在我们初学Linux的时候,我们学过其中一个指令,那么就是我们可以将一个进程本来向标准输出文件也就是显示器文件写入的内容给重定向到另一个进程的标准输入中,那么就是通过一个竖线“|”来连接,那么此时竖线左边的指令或者说进程要输出的内容就会写入给竖线右边的进程中
cmd1 | cmd2
那么这个竖线“|”其实就是匿名管道,因为Linux下所有的指令本质上就是一个进程,那么指令的执行就是创建一个进程,那么这些指令的对应的进程的创建则都是由命令行解释器也就是shell外壳程序调用fork接口来完成,所以这些指令的对应的父进程都是shell外壳程序,那么意味着这些指令对应的进程之间关系便是是兄弟进程,而我们知道父子进程或者具有血缘关系进程之间通信的方式就是通过匿名管道来实现的,那么fork系统接口创建子进程的方式是通过拷贝父进程的task_struct结构体,然后修改其中的部分属性得到子进程自己的task_struct结构体,其中就包括文件描述表,意味着子进程会继承父进程打开的文件
那么对于此时“|”隔开的指令,那么它们也会继承父进程也就是shell外壳程序的文件描述表,那么shell外壳程序再调用fork之前,那么首先便会调用pipe接口来创建一批管道,然后再调用fork创建出来的这几个指令对应的子进程,其便会继承之前创建出的管道,这些指令对应的子进程本来是向显示器文件写入,由于匿名管道的存在,此时就会被输出重定向写入到管道文件中,然后另一个读取的进程本来从标准输入也就是键盘文件中读取,那么此时就会从输入重定向到管道文件中读取,那么这就是shell外壳程序的匿名管道来让子进程通信的一个大致实现
3.完善shell外壳程序
(1).整体框架
那么有可匿名管道的概念之后,我们可以完善之前写的shell外壳程序,使其支持匿名管道,那么在实现shell外壳程序之前,那么首先我们的脑海中要有一个大致的一个实现的框架,也就是代码整体的一个实现思路,梳理出这个shell外壳程序所涉及的各个模块,那么有了模块之后,再来谈每一个模块实现的一个具体的细节
1.获取用户输入的字符串
那么首先shell外壳程序的第一个环节就是获取用户输入的字符串,那么由于这里我们引入了管道,那么意味着用户输入的字符串的内容可能不只一个基本的指令,而是可能包含多个基本指令然后中间用管道(|)隔开,所以这里我们首先获取到用户输入的字符串,将其保存在一个临时的数组中
2.解析字符串
那么获取到用户输入的字符串之后,那么下一步便是解析字符串,那么解析字符串我们可以用一个函数来完成这个模块,那么其中要实现的功能就是统计用户输入的基本指令的个数以及分割出每一个基本指令的指令部分以及参数部分并且还要判断每一个基本指令是否具有输入以及输出重定向,那么这一个环节是我们这个shell外壳程序实现的难点之一,因为这个模块要干的工作有很多不仅要分割出基本指令还要分割基本指令对应的指令部分以及参数部分等等,而且其中还有一个很大的坑,那么我们在下文谈其具体实现的时候,我会说道
3.判断指令个数
那么在上一个环节我们会获取用户输入的基本指令的个数,是零个还是两个还是4个,那么这里我们就要判断,如果是零个基本指令,那么代表用户就只是敲了一个回车键,意味着没有输入任何相关内容,那么此时就得回到开头的第一步,而到时候这所有的模块会处于一个死循环的逻辑,那么这里如果指令个数为0,那么就会continue回到开头的第一步去继续获取用户下一次的输入,那么如果用户输入的命令个数为1个,那么接下来我们就得判断用户输入的指令是内置指令还是外部命令,那么如果是内置指令则不需要调用fork接口创建子进程来执行,而如果基本指令的个数大于1,那么用户必定输入了管道符进行了分割,那么这里就要执行关于管道相关的处理逻辑了
4.指令的执行
那么这里指令的执行的前提一定是有指令,也就是指令的个数大于等于1,那么指令的个数等于1,如何执行,那么上文第三个环节说过,这里就不在重复,而如果指令的个数大于1,那么必定涉及到管道,那么这里我们就得专门创建一个函数模块来执行各个基本指令以及要实现这些基本指令通过管道来传输内容,也就是通过输出重定向来实现
那么这就是我们shell外壳程序的一个大致的执行思路,那么梳理清楚之后,我们就要落实到每一个模块如何去实现,以及要注意的一些实现细节
(2).具体模块实现
1.获取用户输入的字符串
那么平常我们获取用户的键盘输入的话,采取的函数都是scanf函数来获取用户的输入,但是这里由于用户在输入每一个基本指令的时候,那么有意思的手动添加空格将基本指令的指令部分以及参数部分隔开,而scanf读取到空格时,便停止读取输入了,那么无法获取用户完整输入的字符串,所以这里我们不能采取scanf函数来获取用户的输入,而是应该采取fgets函数,那么它会读取包括空格的连续字符,遇到换行符停止读取,所以这里在实现的时候,注意一定不能选择scanf来读取
那么这里我们调用fegts函数则是从标准输入流中,读取指定长度的字符串,然后保存到一个临时的temp字符数组中
char temp[MAX_SIZE];
if(fgets(temp,sizeof(temp),stdin)==NULL)
{
perror("fgets");
continue;
}
2.解析字符串
而我们知道用户输入的基本指令可能不止一个,因为有管道的存在,并且我们还得获取其中每一个基本指令的指令部分已经参数部分,以及它是否有输入或者输出重定向,如果有的话,我们还得保存输入以及输出重定向的目标文件的文件名,那么所以我们得采取一个数据结构来保存每一个基本指令以及其对应的这各种属性,所以这里可以通过一个对象或者结构体,其中封装一个字符指针数组 _ argv,那么字符指针数组每一个元素都是一个指向字符串的字符指针,对应该基本指令的指令部分以及参数部分的字符串,然后就是一个包括指令部分以及参数部分的参数总个数 _ argc,以及一个输入以及输出重定向的标记位_check_redir,那么它是一个int类型的变量,如果该基本指令涉及到输入重定向,那么它的值就会被设置为1,追加重定向其值会被设置为2,而如果是输入重定向其值会被设置为3,而没有输入以及输出重定向,那么它的值就是0,而该对象或者结构体还会封装一个字符指针filename,指向一个字符串,如果有输入或者输出重定向,那么它保存的就是输入以及输出重定向的目标文件的文件名
而这里我是采取c++的类来实现的:
class order
{
public:
char* _argv[MAX_SIZE];
int _argc=0;
int _check_redir=0;
char* filename=nullptr;
};
然后再字符串解析之前,我会先创建一个固定大小的vector数组,其中每一个元素是order类,通过vector来保存每一个基本指令以及其对应的这各种属性,那么创建完然后交给字符串解析函数去进行初始化
而对于字符串解析函数模块内部的实现,那么首先我们需要调用strtok函数,那么以管道文件符(|)作为分隔符来分割出一个个的基本指令,那么这些基本指令我们先保存在临时的字符指针数组中,那么在这个过程中我们顺便可以统计基本指令的个数,并且该函数调用结束就返回基本指令的个数交给外部主函数接收
int pipe_string(std::vector<order>& order_array,char temp[])
{
int cmdnum=0;
char* tmp[MAX_SIZE];
char* token=strtok(temp,"|");
while(token!=NULL&&cmdnum<MAX_SIZE)
{
tmp[cmdnum]=token;
cmdnum++;
token=strtok(NULL,"|");
}
for(int i=0;i<cmdnum;i++)
{
get_string(order_array[i],tmp[i]);
}
return cmdnum;
}
接着我们在对每一个基本指令进行分割,其中会专门创建一个函数get_string来实现这个内容,并且其会接收一个vector数组中的一个order对象,然后调用strtok函数,以空格作为分割符,将分割出来的指令部分以及参数部分保存到order对象中的_ argv字符指针数组中,其中注意 _ argv的最后一个参数要设置为NULL,因为exec函数在遍历这个字符指针数组的时候,会遍历到NULL为止结束
接下来还会判断是否有输入以及输出重定向符号,那么就会涉及到遍历这个_ argv数组,并且是从后往前遍历,因为我们输入以及输出重定向规定是重定向符号后面的字符串是重定向的文件名,那么其中我们会调用strcmp函数来匹配参数部分的字符串是否是重定向符号,斌哥注意我们只能从倒数第二个位置开始往前匹配,因为倒数第一个位置的值是NULL,最后根据匹配的重定向符号来初始化order对象的 _chekc _redir变量
其中还要注意的是,重定向符号以及后面重定向的目标文件并不是指令的有效内容,那么我们得手动将指向这两部分的字符指针设置为NULL,但是在设置为NULL之前,一定要保存重定向的目标文件的文件名也就是初始化order对象的filename成员变量,并且由于重定向的符号以及文件名不是有效指令内容,所以最后在判断重定向结束之后,我们要再最后统计指令部分以及参数部分的总个数,那么其中就是从前往后遍历,因为重定向符号的字符指针已经被设置为空了,那么我们采取一个while循环的逻辑,每次遍历是order对象的_ argc加一,直到遍历带 _ agrv的字符指针为NULL结束
//解析基本指令
void get_string(order& a,char* temp_order)
{
int len=strlen(temp_order);
if(len>0&&temp_order[len-1]=='\n')
{
temp_order[len-1]='\0';
}
char* token=strtok(temp_order," ");
int argc=0;
while(token!=NULL&&argc<MAX_SIZE)
{
a._argv[argc++]=token;
token=strtok(NULL," ");
}
a._argv[argc]=NULL;
a._argc=0;
for(int i=argc-2;i>=0;i--)
{
if(strcmp(a._argv[i],">")==0)
{
a._check_redir=1;
if(i+1<argc){
a.filename=a._argv[i+1];
a._argv[i]=NULL;
a._argv[i+1]=NULL;
}
break;
}else if(strcmp(a._argv[i],">>")==0)
{
a._check_redir=2;
if(i+1<argc){
a.filename=a._argv[i+1];
a._argv[i]=NULL;
a._argv[i+1]=NULL;
}
break;
}else if(strcmp(a._argv[i],"<")==0)
{
a._check_redir=3;
if(i+1<argc){
a.filename=a._argv[i+1];
a._argv[i]=NULL;
a._argv[i+1]=NULL;
}
break;
}
}
while(a._argv[a._argc]!=NULL&&a.argc<MAX_SIZE)
{
a._argc++;
}
}
注意这里有一个坑,你会发现我这里在实现pipe_string函数的时候,那么我采取的方式是会先分割出所有的基本指令,然后将其保存在一个临时的字符指针数组中,分割完所有的基本指令在对每一个基本指令进行进一步的解析,而并没有采取分割出一个基本指令之后就直接将这个基本指令解析然后初始化vector数组中的一个order对象:
while(token!=NULL&&cmdnum<MAX_SIZE)
{
get_string(order_array[i],token);
cmdnum++;
token=strtok(NULL,"|");
}
那么首先给出一个答案,这种方式肯定是错误的,那么为什么是错误的?
那么这就和strtok函数有关,因为strtok函数再被调用的时候,那么内部为维护一个静态的指针,指向要分割的字符串的下一个位置的起始位置,那么此时我们最开始在pipe_string调用strtok函数,然后基本指令还没分割完,就接着在get_string函数中又再一次调用strtok函数,那么此时它会覆盖之前的静态指针,然后指向新的字符串的起始位置开始往后分割,等到get_string调用结束,那么此时strtok内部维护的静态的指针已经指向了字符串结尾,那么此时它会返回NULL,那么根据此时while循环的循环调节,那么会导致直接退出循环,不会继续分割基本指令,那么你会发现无论你输入多少个带有管道的基本指令,最终只会保存一个基本指令,也就是第一个基本指令,那么我之前就是这么做的,可谓把我给坑惨了,那么这里提醒读者不要跳进这坑里面,那么这是非常关键的细节
3.判断指令个数
那么接下来就是判断指令的个数,如果指令的个数为0,那么就会继续回到起始位置处,而如果指令个数为1,那么我们就得再判断它是否为内建指令,那么所谓的内建指令,就是该指令的内容和父进程所处的运行环境有关,比如cd指令,cd指令的内容是要更换用户或者说是父进程bash所处的工作目录,那么如果你交给子进程,那么子进程会有自己独立的一份环境变量,那么它的工作目录的更改不影响父进程,所以像这样的指令就是内置指令,一定得交给父进程来完成,那么我们就得准备一个全局的字符指针数组来保存内置指令对应的字符串,而order对象中的_argv字符指针数组的第一个元素便是指令部分,那么我们便会去和保存内置指令的字符指针数组中的每一个元素依次去匹配看是否为内置指令,那么这个过程会涉及调用strcmp函数,并且将其封装到check _ order函数中
//判断内置指令
bool check_order(order& a)
{
for(int i=0;in_cmd[i]!=NULL;i++)
{
if(strcmp(a._argv[0],in_cmd[i])==0)
{
return true;
}
}
return false;
}
那么如果调用check_order函数返回为true,那么就意味着其是内置指令,那么便会由父进程去亲自执行,那么所谓的内置指令,那么意味着该指令的内容会被封装为了一个模块化的函数,那么我这里在实现的时候只涉及到cd以及pwd两个内置指令,那么读者可以试着尝试添加更多的内置指令,那么内置指令的对应的函数调用,那么我将其封装到了in_order_complete函数中
void in_order_complete(order& a)
{
if(strcmp(a._argv[0],"cd")==0)
{
if(a._argc==2)
{
if(chdir(a._argv[1])!=0)
{
perror("chdir");
return;
}
}else
{
std::cout<<"the order is not right"<<std::endl;
return;
}
}
if(strcmp(a._argv[0],"pwd")==0)
{
char cwd[1024];
if(getcwd(cwd,sizeof(cwd))!=NULL)
{
if(a._check_redir==1||a._check_redir==2)
{
if(a._check_redir==1)
{
int _fd=open(a.filename,O_CREAT|O_WRONLY,0666);
if(_fd<0)
{
perror("open");
return;
}
int n=write(_fd,cwd,sizeof(cwd));
if(n<0)
{
perror("write");
return;
}
}else if(a._check_redir==2)
{
int _fd=open(a.filename,O_CREAT|O_WRONLY|O_APPEND,0666);
if(_fd<0)
{
perror("open");
return;
}
int n=write(_fd,cwd,sizeof(cwd));
if(n<0)
{
perror("write");
return;
}
}
}else
{
std::cout<<cwd<<std::endl;
}
}
}
}
4.指令的执行
那么刚才指令个数为1,如何执行以及其各种细节在上文已经说明,而如果基本指令的个数大于1,那么则说明有管道,那么对于含有管道的多个基本指令的执行我们可以创建一个 pipe_order_complete函数来实现
那么其中pipe_order_complete函数模块实现的一个思路,我们可以这样来理解:
假设基本指令的个数为n,那么管道的个数就是n-1,那么首先我们会先调用pipe接口利用for循环来创建n-1个管道,所以这里我会定义一个pipe类,那么pipe类封装一个长度为2的int类型的数组,然后接着我会创建一个长度为n-1的vector数组,其中每一个元素就是pipe对象,而调用pipe接口的创建的每一个管道的读写端的文件描述符,就保存在了对应的数组的pipe对象的长度为2的int类型的数组中
class _pipe
{
public:
int pipe_fd[2];
};
for (int i = 0; i < cmdnum - 1; i++) {
int n=pipe(pipe_array[i].pipe_fd);
if (n<0) {
perror("pipe");
exit(EXIT_FAILURE); // 错误时直接退出
}
}
而创建完了一批管道文件,那么下一步便是创建n个进程,那么还是采取的是一个for循环的逻辑,然后其中调用fork接口,那么子进程会继承父进程打开的文件,那么其中对于起始指令以及结尾指令要进行特殊的处理,对于起始指令来说,那么它只能向管道文件中写入而不能读取,因为它处于起始位置,同理对于最后一个基本来说,那么它只能从管道文件中读取而不能写入
而对于中间的进程来说,那么我们就得将其的标准输入给重定向到上一个管道文件的读端,而它的标准输入则重定向到对应管道文件的写端
其次注意的是如果基本指令含有输入或者输出重定向,那么输入以及输出重定向的优先级是要大于管道文件的重定向,比如
cmd1 >> log.txt | cmd2
那么对于cmd1来说,如果它会像显示器也就是标准输出文件中写入的话,那么此时它会将写入到内容重定向到log.txt中而不是管道文件中,一定要注意这个细节,那么最后对于父进程来说则是关闭其之前创建的所有的管道文件
其中注意的是,对于带有管道文件的多个基本指令的执行,那么我们不需要在去判断每一个基本指令是否为内置指令,因为即使是内置指令,那么它也只能在子进程中执行,因为如果内置指令还在父进程中执行,那么此时父进程也就是bash进程会向管道中读取以及写入,那么这些管道肯定都是子进程之间通信传递信息,那么和父进程没有任何关系,并且管道文件涉及到输入以及输出重定向也就是关闭标准输入以及标准输出文件,那么明显会影响父进程,因为父进程还需要从标准输入文件中读取用户下一次输入的指令以及将指令的执行结果打印到终端上,所以不管是内置还是外部都统一交给子进程来执行,所以一定要注意这个细节
那么这里对于fork创建出的每一个子进程的执行逻辑和之前执行单个基本指令的执行逻辑是一样的,先判断是否有输入以及输出重定向,没有就重定向相应的管道文件,然后再进行进程的替换
void pipe_order_complete(int cmdnum, std::vector<order>& order_array, std::vector<_pipe>& pipe_array) {
// 1. 创建所有管道
for (int i = 0; i < cmdnum - 1; i++) {
int n=pipe(pipe_array[i].pipe_fd);
if (n<0) {
perror("pipe");
exit(EXIT_FAILURE); // 错误时直接退出
}
}
// 2. 创建子进程处理每个命令
for (int i = 0; i < cmdnum; i++) {
int pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // 子进程
// 2.1 处理输入重定向或前一个管道
if (order_array[i]._check_redir == 3) { // 输入重定向 <
int fd_in = open(order_array[i].filename, O_RDONLY);
if (fd_in < 0) {
perror("open");
exit(EXIT_FAILURE);
}
dup2(fd_in, STDIN_FILENO);
close(fd_in);
} else if (i > 0) { // 从上一个管道读取输入
dup2(pipe_array[i-1].pipe_fd[0], STDIN_FILENO);
close(pipe_array[i-1].pipe_fd[0]);
}
// 2.2 处理输出重定向或下一个管道
if (order_array[i]._check_redir == 1 || order_array[i]._check_redir == 2) {
// 输出到文件(优先级高于管道)
int flags = O_WRONLY | O_CREAT;
flags |= (order_array[i]._check_redir == 1) ? O_TRUNC : O_APPEND;
int fd_out = open(order_array[i].filename, flags, 0666);
if (fd_out < 0) {
perror("open");
exit(EXIT_FAILURE);
}
dup2(fd_out, STDOUT_FILENO);
close(fd_out);
} else if (i < cmdnum - 1) { // 输出到下一个管道
dup2(pipe_array[i].pipe_fd[1], STDOUT_FILENO);
close(pipe_array[i].pipe_fd[1]);
}
// 2.3 关闭所有无关的管道端
for (int j = 0; j < cmdnum - 1; j++) {
if (j != i && j != i-1) {
close(pipe_array[j].pipe_fd[0]);
close(pipe_array[j].pipe_fd[1]);
}
}
// 2.4 执行命令
execvp(order_array[i]._argv[0], order_array[i]._argv);
perror("execvp");
exit(EXIT_FAILURE);
}
}
// 3. 父进程关闭所有管道端
for (int i = 0; i < cmdnum - 1; i++) {
close(pipe_array[i].pipe_fd[0]);
close(pipe_array[i].pipe_fd[1]);
}
// 4. 等待所有子进程结束
for (int i = 0; i < cmdnum; i++) {
wait(NULL);
}
}
(3).完整实现
myshell.h
#include<iostream>
#include<cstdio>
#include<cstdio>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
#include<vector>
#include<cstring>
#include<fcntl.h>
#include<cstdlib>
#include<cstdbool>
#define MAX_SIZE 1024
const char* in_cmd[]={"cd","pwd",NULL};
class order
{
public:
char* _argv[MAX_SIZE];
int _argc=0;
int _check_redir=0;
char* filename=nullptr;
};
class _pipe
{
public:
int pipe_fd[2];
};
void get_string(order& a,char* temp_order);
int pipe_string(std::vector<order>& order_array,char temp[]);
bool check_order(order& a);
void in_order_complete(order& a);
void pipe_order_complete(int cmdnum,std::vector<order>& order_array,std::vector<_pipe>& pipe_array);
myshell.hpp
```cpp
#include"myshell.h"
void get_string(order& a,char* temp_order)
{
int len=strlen(temp_order);
if(len>0&&temp_order[len-1]=='\n')
{
temp_order[len-1]='\0';
}
char* token=strtok(temp_order," ");
int argc=0;
while(token!=NULL&&argc<MAX_SIZE)
{
a._argv[argc++]=token;
token=strtok(NULL," ");
}
a._argv[argc]=NULL;
a._argc=0;
for(int i=argc-2;i>=0;i--)
{
if(strcmp(a._argv[i],">")==0)
{
a._check_redir=1;
if(i+1<argc){
a.filename=a._argv[i+1];
a._argv[i]=NULL;
a._argv[i+1]=NULL;
}
break;
}else if(strcmp(a._argv[i],">>")==0)
{
a._check_redir=2;
if(i+1<argc){
a.filename=a._argv[i+1];
a._argv[i]=NULL;
a._argv[i+1]=NULL;
}
break;
}else if(strcmp(a._argv[i],"<")==0)
{
a._check_redir=3;
if(i+1<argc){
a.filename=a._argv[i+1];
a._argv[i]=NULL;
a._argv[i+1]=NULL;
}
break;
}
}
while(a._argv[a._argc]!=NULL&&a.argc<MAX_SIZE)
{
a._argc++;
}
}
int pipe_string(std::vector<order>& order_array,char temp[])
{
int cmdnum=0;
char* tmp[MAX_SIZE];
char* token=strtok(temp,"|");
while(token!=NULL&&cmdnum<MAX_SIZE)
{
tmp[cmdnum]=token;
cmdnum++;
token=strtok(NULL,"|");
}
for(int i=0;i<cmdnum;i++)
{
get_string(order_array[i],tmp[i]);
}
return cmdnum;
}
bool check_order(order& a)
{
for(int i=0;in_cmd[i]!=NULL;i++)
{
if(strcmp(a._argv[0],in_cmd[i])==0)
{
return true;
}
}
return false;
}
void in_order_complete(order& a)
{
if(strcmp(a._argv[0],"cd")==0)
{
if(a._argc==2)
{
if(chdir(a._argv[1])!=0)
{
perror("chdir");
return;
}
}else
{
std::cout<<"the order is not right"<<std::endl;
return;
}
}
if(strcmp(a._argv[0],"pwd")==0)
{
char cwd[1024];
if(getcwd(cwd,sizeof(cwd))!=NULL)
{
if(a._check_redir==1||a._check_redir==2)
{
if(a._check_redir==1)
{
int _fd=open(a.filename,O_CREAT|O_WRONLY,0666);
if(_fd<0)
{
perror("open");
return;
}
int n=write(_fd,cwd,sizeof(cwd));
if(n<0)
{
perror("write");
return;
}
}else if(a._check_redir==2)
{
int _fd=open(a.filename,O_CREAT|O_WRONLY|O_APPEND,0666);
if(_fd<0)
{
perror("open");
return;
}
int n=write(_fd,cwd,sizeof(cwd));
if(n<0)
{
perror("write");
return;
}
}
}else
{
std::cout<<cwd<<std::endl;
}
}
}
}
void pipe_order_complete(int cmdnum, std::vector<order>& order_array, std::vector<_pipe>& pipe_array) {
// 1. 创建所有管道
for (int i = 0; i < cmdnum - 1; i++) {
int n=pipe(pipe_array[i].pipe_fd);
if (n<0) {
perror("pipe");
exit(EXIT_FAILURE); // 错误时直接退出
}
}
// 2. 创建子进程处理每个命令
for (int i = 0; i < cmdnum; i++) {
int pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // 子进程
// 2.1 处理输入重定向或前一个管道
if (order_array[i]._check_redir == 3) { // 输入重定向 <
int fd_in = open(order_array[i].filename, O_RDONLY);
if (fd_in < 0) {
perror("open");
exit(EXIT_FAILURE);
}
dup2(fd_in, STDIN_FILENO);
close(fd_in);
} else if (i > 0) { // 从上一个管道读取输入
dup2(pipe_array[i-1].pipe_fd[0], STDIN_FILENO);
close(pipe_array[i-1].pipe_fd[0]);
}
// 2.2 处理输出重定向或下一个管道
if (order_array[i]._check_redir == 1 || order_array[i]._check_redir == 2) {
// 输出到文件(优先级高于管道)
int flags = O_WRONLY | O_CREAT;
flags |= (order_array[i]._check_redir == 1) ? O_TRUNC : O_APPEND;
int fd_out = open(order_array[i].filename, flags, 0666);
if (fd_out < 0) {
perror("open");
exit(EXIT_FAILURE);
}
dup2(fd_out, STDOUT_FILENO);
close(fd_out);
} else if (i < cmdnum - 1) { // 输出到下一个管道
dup2(pipe_array[i].pipe_fd[1], STDOUT_FILENO);
close(pipe_array[i].pipe_fd[1]);
}
// 2.3 关闭所有无关的管道端
for (int j = 0; j < cmdnum - 1; j++) {
if (j != i && j != i-1) {
close(pipe_array[j].pipe_fd[0]);
close(pipe_array[j].pipe_fd[1]);
}
}
// 2.4 执行命令
execvp(order_array[i]._argv[0], order_array[i]._argv);
perror("execvp");
exit(EXIT_FAILURE);
}
}
// 3. 父进程关闭所有管道端
for (int i = 0; i < cmdnum - 1; i++) {
close(pipe_array[i].pipe_fd[0]);
close(pipe_array[i].pipe_fd[1]);
}
// 4. 等待所有子进程结束
for (int i = 0; i < cmdnum; i++) {
wait(NULL);
}
}
`
myshell.cpp
#include"myshell.hpp"
int main()
{
while(true)
{
char cwd[1024];
getcwd(cwd,sizeof(cwd));
printf("[%s@%s %s]$",getenv("USER"),getenv("HOSTNAME"),cwd);
char temp[MAX_SIZE];
if(fgets(temp,sizeof(temp),stdin)==NULL)
{
perror("fgets");
continue;
}
int cmdnum=0;
std::vector<order> order_array(MAX_SIZE);
cmdnum=pipe_string(order_array,temp);
if(cmdnum==0)
{
continue;
}
else if(cmdnum==1)
{
if(check_order(order_array[0]))
{
in_order_complete(order_array[0]);
}else
{
int id=fork();
if(id<0)
{
perror("fork");
continue;
}
if(id==0)
{
if(order_array[0]._check_redir==1)
{
int _fd=open(order_array[0].filename,O_CREAT|O_WRONLY|O_TRUNC,0666);
if(_fd<0)
{
perror("open");
continue;
}
int n=dup2(_fd,1);
if(n<0)
{
perror("dup2");
continue;
}
}
if(order_array[0]._check_redir==2)
{
int _fd=open(order_array[0].filename,O_CREAT|O_WRONLY|O_APPEND,0666);
if(_fd<0)
{
perror("open");
continue;
}
int n=dup2(_fd,1);
if(n<0)
{
perror("dup2");
continue;
}
}
if(order_array[0]._check_redir==3)
{
int _fd=open(order_array[0].filename,O_CREAT|O_RDONLY,0666);
if(_fd<0)
{
perror("open");
continue;
}
int n=dup2(_fd,0);
if(n<0)
{
perror("dup2");
continue;
}
}
execvp(order_array[0]._argv[0],order_array[0]._argv);
perror("execvp");
exit(EXIT_FAILURE);
}
int statues;
int n=waitpid(id,&statues,0);
if(n<0)
{
perror("waitpid");
}
}
}else{
std::vector<_pipe> pipe_array(cmdnum-1);
pipe_order_complete(cmdnum,order_array,pipe_array);
}
}
}
4.结语
那么这就是本期关于完善shell外壳程序的全部过程了,那么希望读者也可以自己去实现一下,可以加深你对匿名管道的理解,那么我下一期会继续更新匿名管道的相关内容,并且来动手实现一个简易的进程池,那么我会持续更新,希望你多多关注,如果本文有帮组到你的话,还请多多三连加关注哦,你的支持就是我最大的动力!