【Linux实践系列】:匿名管道收尾+完善shell外壳程序

🔥 本文专栏:Linux Linux实践项目
🌸作者主页:努力努力再努力wz

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

💪 今日博客励志语录人生总会有自己能力所不及的范围,但是如果你在你能力所及的范围尽了全部的努力,那你还有什么遗憾呢

★★★ 本文前置知识:

匿名管道


输入以及输出重定向


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外壳程序的全部过程了,那么希望读者也可以自己去实现一下,那么我下一期会继续更新匿名管道的相关内容,并且来动手实现一个简易的进程池,那么我会持续更新,希望你多多关注,如果本文有帮组到你的话,还请多多三连加关注哦,你的支持就是我最大的动力!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/75626.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++初学】课后作业汇总复习(七) 指针-深浅copy

1、 HugeInt类:构造、、cout Description: 32位整数的计算机可以表示整数的范围近似为&#xff0d;20亿到&#xff0b;20亿。在这个范围内操作一般不会出现问题&#xff0c;但是有的应用程序可能需要使用超出上述范围的整数。C可以满足这个需求&#xff0c;创建功能强大的新的…

【C++】 —— 笔试刷题day_16

刷题_day16&#xff0c;继续加油啊 一、字符串替换 题目解析 这道题是一道简单的字符题目&#xff0c;题目给我们一个字符串A&#xff0c;和n表示A字符串的长度&#xff0c;再给出一个字符数组arg&#xff0c;m表示arg中是数据个数。 然我们在字符串A中找到%s然后替换成arg中的…

n8n 本地部署及实践应用,实现零成本自动化运营 Telegram 频道(保证好使)

n8n 本地部署及实践应用&#xff0c;实现零成本自动化运营 Telegram 频道&#xff08;保证好使&#xff09; 简介 n8n 介绍 一、高度可定制性 二、丰富的连接器生态 三、自托管部署&#xff08;本地部署&#xff09; 四、社区驱动 n8n 的部署 一、前期准备 二、部署步…

flutter 桌面应用之系统托盘

系统托盘(Tray) 系统托盘就是状态栏里面对应的图标点击菜单 主要有两款框架 框架一句话评价tray_manager轻量、简单、易用&#xff0c;适合常规托盘功能system_tray更底层、更强大、支持图标/菜单/消息弹窗等更多功能&#xff0c;但复杂度更高 &#x1f9f1; 基础能力对比 …

修改idea/android studio等编辑器快捷注释从当前行开头的反人类行为

不知道什么时候开始&#xff0c;idea编辑的快捷注释开始从当前行开头出现了&#xff0c;显得实在是难受&#xff0c;我只想让在当前行代码的部份开始缩进两个字符开始&#xff0c;这样才会显得更舒服。不知道有没有强迫症的猴子和我一样&#xff0c;就像下面的效果&#xff1a;…

MySQL慢查询全攻略:定位、分析与优化实战

&#x1f680; MySQL慢查询全攻略&#xff1a;定位、分析与优化实战 #数据库优化 #性能调优 #SQL优化 #MySQL实战 一、慢查询定位&#xff1a;找到性能瓶颈 1.1 开启慢查询日志 -- 查看当前配置 SHOW VARIABLES LIKE %slow_query%; -- 动态开启&#xff08;重启失效&…

当原型图与文字说明完全不同时,测试要怎么做?

当测试遇上左右手互搏的需求&#xff0c;怎么办&#xff1f; "这个弹窗样式怎么和文档写的不一样&#xff1f;"、"按钮位置怎么跑到左边去了&#xff1f;"——根据Deloitte的调查&#xff0c;62%的项目存在原型图与需求文档不一致的情况。这种"精神分…

关于量化交易在拉盘砸盘方面应用的部分思考

关于“砸盘”的深层解析与操盘逻辑 ​​一、砸盘的本质与市场含义​​ ​​砸盘​​指通过集中抛售大量筹码导致价格快速下跌的行为&#xff0c;其核心目标是​​制造恐慌、清洗浮筹或实现利益再分配​​。不同场景下的砸盘含义不同&#xff1a; ​​主动砸盘&#xff08;操控…

【项目管理】第12章 项目质量管理-- 知识点整理

项目管理-相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 (一)知识总览 项目管理知识域 知识点: (项目管理概论、立项管理、十大知识域、配置与变更管理、绩效域) 对应:第6章-第19章 第6章 项目管理概论 4分第13章 项目资源管理 3-4分第7章 项目…

一个好看的图集展示html页面源码

源码介绍 一个好看的图集展示html页面源码&#xff0c;适合展示自己的作品&#xff0c;页面美观大气&#xff0c;也可以作为产品展示或者个人引导页等等 源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c; 双击html文件可以本地运行…

2021第十二届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

记录刷题的过程、感悟、题解。 希望能帮到&#xff0c;那些与我一同前行的&#xff0c;来自远方的朋友&#x1f609; 大纲&#xff1a; 1、空间-&#xff08;题解&#xff09;-字节单位转换 2、卡片-&#xff08;题解&#xff09;-可以不用当组合来写&#xff0c;思维题 3、直…

LabVIEW 中 JSON 数据与簇的转换

在 LabVIEW 编程中&#xff0c;数据格式的处理与转换是极为关键的环节。其中&#xff0c;将数据在 JSON 格式与 LabVIEW 的簇结构之间进行转换是一项常见且重要的操作。这里展示的程序片段就涉及到这一关键功能&#xff0c;以下将详细介绍。 一、JSON 数据与簇的转换功能 &am…

蓝桥杯大模板

init.c void System_Init() {P0 0x00; //关闭蜂鸣器和继电器P2 P2 & 0x1f | 0xa0;P2 & 0x1f;P0 0x00; //关闭LEDP2 P2 & 0x1f | 0x80;P2 & 0x1f; } led.c #include <LED.H>idata unsigned char temp_1 0x00; idata unsigned char temp_old…

通过HTTP协议实现Git免密操作的解决方案

工作中会遇到这样的问题的。 通过HTTP协议实现Git免密操作的解决方案 方法一&#xff1a;启用全局凭据存储&#xff08;推荐&#xff09; 配置凭证存储‌ 执行以下命令&#xff0c;让Git永久保存账号密码&#xff08;首次操作后生效&#xff09;&#xff1a; git config --g…

Java常见面试问题

一.Liunx 二.Java基础 1.final 2.static 3.与equals 三.Collection 1.LIst 2.Map 3.Stream 四、多线程 1.实现方法 2.线程池核心参数 3.应用场景 五、JVM 1.堆 2.栈 六、Spring 1.面向对象 2.IOC 3.AOP 七、Springboot 1.自动装配 八、SpringCloud 1.Nacos 2.seata 3.ga…

【蓝桥杯】第十六届蓝桥杯 JAVA B组记录

试题 A: 逃离高塔 很简单&#xff0c;签到题&#xff0c;但是需要注意精度&#xff0c;用int会有溢出风险 答案&#xff1a;202 package lanqiao.t1;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWrit…

PyTorch Tensor维度变换实战:view/squeeze/expand/repeat全解析

本文从图像数据处理、模型输入适配等实际场景出发&#xff0c;系统讲解PyTorch中view、squeeze、expand和repeat四大维度变换方法。通过代码演示对比不同方法的适用性&#xff0c;助您掌握数据维度调整的核心技巧。 一、基础维度操作方法 1. view&#xff1a;内存连续的形状重…

Kubernetes nodeName Manual Scheduling practice (K8S节点名称绑定以及手工调度)

Manual Scheduling 在 Kubernetes 中&#xff0c;手动调度框架允许您将 Pod 分配到特定节点&#xff0c;而无需依赖默认调度器。这对于测试、调试或处理特定工作负载非常有用。您可以通过在 Pod 的规范中设置 nodeName 字段来实现手动调度。以下是一个示例&#xff1a; apiVe…

即时编译器(JIT)的编译过程是什么?

1. 触发编译 JIT编译的触发基于热点代码检测&#xff0c;主要通过两种计数器&#xff1a; • 方法调用计数器&#xff1a;统计方法被调用的次数&#xff08;默认阈值&#xff1a;C1为1,500次&#xff0c;C2为10,000次&#xff09;。 • 回边计数器&#xff1a;统计循环体的执行…

Java基础:集合List、Map、Set(超详细版)

集合体系概述 Collection常用方法 补充&#xff1a;addAll() Collection的遍历方式 迭代器 增强for&#xff08;空集合可以&#xff0c;null不可以&#xff09; lambda 集合对象存储对象原理 遍历方式的区别 List集合 特点、特有方法 遍历方式 &#xff08;同上&#xff09…