minishell的实现
直接上各个模块的代码,注释都在文档代码中,非常详细,加上最后的Makefile文件完全可以自行运行看懂:
main函数一个文件main.c
1 /* 2 minishell实现的功能:简单命令解析、管道行解析、输入输出重定向解析、一些内置命令实现、简单的信号处理 3 未能实现的功能:语法分析、别名处理、路径扩展、通配处理、算术处理、变量处理、作业控制 4 shell_loop{ 5 read_command //读 6 parse_command //解析 7 execute_command //执行 8 } 9 */ 10 #include "parse.h" 11 #include "init.h" 12 #include "def.h" 13 14 char cmdline[MAXLINE+1];//定义全局变量存放读取的命令 15 char avline[MAXLINE+1];//保存解析出来的参数 16 char *lineptr;//初始指向cmdline数组 17 char *avptr;//初始指向avline数组 18 19 char infile[MAXNAME+1];//输入文件名,用于保存输入重定向文件名 20 char outfile[MAXNAME+1];//输出文件名 21 COMMAND cmd[PIPELINE];//参数列表 22 23 int cmd_count;//命令个数 24 int backgnd;//是否是后台操作 25 int lastpid;//这是最后一个子进程退出 26 27 int append; 28 int main() 29 { 30 31 setup();//安装信号,划分到初始化模块 32 shell_loop();//进入shell循环 33 return 0; 34 }
setup信号安装部分在初始化模块中,分为两个部分init.h和init.c
1 #ifndef _INIT_H_ 2 #define _INIT_H_ 3 void setup(void); 4 void init(void); 5 #endif
1 #include "init.h" 2 #include "externs.h" 3 #include<stdio.h> 4 #include<signal.h> 5 #include<string.h> 6 void sigint_handler(int sig) 7 { 8 printf("\n[minishell]$ "); 9 fflush(stdout);//没有(\n) 10 11 } 12 void setup(void) 13 { 14 signal(SIGINT,sigint_handler); 15 signal(SIGQUIT,SIG_IGN); 16 } 17 18 void init(void) 19 { 20 21 memset(cmd,0,sizeof(cmd)); 22 int i=0; 23 for(i=0;i<PIPELINE;i++) 24 { 25 cmd[i].infd=0;//初始命令的输入默认为标准输入0。 26 cmd[i].outfd=1;//初始所有输出默认标准输出1 27 } 28 memset(&cmdline,0,sizeof(cmdline)); 29 lineptr=cmdline; 30 avptr=avline; 31 memset(avline,0,sizeof(avline)); 32 memset(infile,0,sizeof(infile)); 33 memset(outfile,0,sizeof(outfile)); 34 cmd_count=0; 35 backgnd=0; 36 lastpid=0; 37 append=0; 38 printf("[minishell]$ "); 39 fflush(stdout);//无\n 40 }
shell_loop的主循环在parse.h和parse.c这两个命令解析模块中:
#ifndef _PARSE_H_ #define _PARSE_H_ //定义函数接口 void shell_loop(void);//shell循环 int read_command(void); int parse_command(void); int execute_command(void); int check(const char*str); #endif
#include"parse.h" #include<stdio.h> #include "def.h" #include "externs.h"//声明外部变量 #include "init.h" #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include "builtin.h" #include "execute.h" void get_command(int i); void getname(char *name); void print_command(); /* shell 循环 */ void shell_loop(void) {while(1){//初始化环境init();//每次循环前初始化cmdline和COMMOND//读取命令if(read_command()==-1)break;//解析命令 parse_command();//print_command();//打印命令//执行命令 execute_command();}printf("\nexit\n"); } /* 读取命令,成功返回0,失败或者读到文件结束符返回-1 */ int read_command(void) {/* 按行读取命令,cmdline中包含\n字符 */if(fgets(cmdline,MAXLINE,stdin)==NULL)//利用extern来引用全局变量,将所有的extern引用声明放入头文件return -1;return 0; } /* 解析命令:成功返回解析到的命令个数,失败返回-1 例如: cat < test.txt | grep -n public > test2.txt &这个命令 cat 先解析出来cmd[0] 以及参数cmd[0].args 然后 <输入重定向 将test.txt保存 然后 |管道 再解析命令grep到 cmd[1] 以及两个参数到cmd[1].args... */ int parse_command(void) { //开始就检测到\nif (check("\n"))return 0;/*先判定是否内部命令并执行它*/if(builtin())return 0;//内部命令直接执行,不用解析//cmd [< filename] [| cmd]...[or filename] [&]:方括号表示可选,省略号表示前面可以重复0次或多次//or 可以是 > 或者 >> 输出重定向清除文件或者追加到文件尾部方式//&是否由后台处理//例如:cat < test.txt | grep -n public > test2.txt &if(check("\n"))return 0;//一开始回车/*第一步:解析第一条简单语句*/get_command(0);/*第二步:判定是否有输入重定向符*/if(check("<"))getname(infile);//解析文件名字,check成功时,lineptr移动过所匹配的字符串/*第三步:判定是否有管道*/int i; for(i=1;i<PIPELINE;i++){if(check("|"))get_command(i);elsebreak;}/*第四步:判定是否有输出重定向符*/if(check(">")){//连续两个>if(check(">")){append=1;//以追加的方式打开 }getname(outfile);//获取后面文件名,解析到全局变量outfile中 }/*第五步:判定是否有后台作业&*/if(check("&"))backgnd=1;/*第六步:判定命令结束'\n'*/if(check("\n")){cmd_count=i;//总的命令个数 cat grep...return cmd_count;}//解析失败else{fprintf(stderr,"Command line systax error\n");return -1;}return 0; } /* 执行命令:成功返回0,失败返回-1 */ int execute_command(void) { /*执行外部命令*/execute_disk_command();return 0; }//例如cmd[] ls | wc -w \n // avline[] ls \0 wc \0 -w \0 参数列表数组 //COMMAND cmd[PIPELINE]; cmd[i]是第i条命令,cmd[i].args[j]:是第i条命令的第j个参数 //解析命令至cmd[i],提取cmdline命令参数到avline数组中,并且将COMMAND结构中的args[]中的每个指针指向avline对应参数字符串 void get_command(int i) {int j=0;int inword;//针对cat 之后有无参数。如果无参数直接遇到<,inword就不会置1.那么switch遇到<直接args[1]为NULL//cat < test.txt | grep -n public > test2.txt &while(*lineptr!='\0'){//lineptr指向cmdlinewhile(*lineptr==' '||*lineptr=='\t')lineptr++;if(*lineptr=='\n'||*lineptr=='\0')break;//将第i条命令第j个参数指向avptrcmd[i].args[j]=avptr;//例如 agrs[0]指向cat args[1]应该指向空,所以引入inwordwhile(*lineptr!='\0'&&*lineptr!='\n'&&*lineptr!='<'&&*lineptr!='|'&&*lineptr!='>'&&*lineptr!='&'&&*lineptr!=' '&&*lineptr!='\t'){*avptr++=*lineptr++;//参数提取至avptr指针指向的数组avline。inword=1;}*avptr++='\0';switch(*lineptr){//解析到下一个参数。break回来继续。case ' ':case '\t':inword=0;j++;break;//这条命令提取结束case '<':case '>':case '|':case '&':case '\n':if(inword==0) cmd[i].args[j]=NULL;return ;//只解析第i条语句。完了函数就返回// for \0default:return ; }} } void print_command() {int i;int j;printf("cmd_count=%d\n",cmd_count);if(infile[0]!='\0')printf("infile=[%s]\n",infile);if(outfile[0]!='\0')printf("outfile=[%s]\n",outfile);for(i=0;i<cmd_count;i++){j=0;while(cmd[i].args[j]!=NULL){printf("[%s] ",cmd[i].args[j]);j++;}printf("\n");}} /* 将lineptr中的字符串与str进行匹配 成功返回1,失败返回0,成功时lineptr移过所匹配的字符串。失败时lineptr不变 */ int check(const char*str) { //lineptr指向cmd 遇到< > | & 会返回char *p;while(*lineptr==' '||*lineptr=='\t')lineptr++;p=lineptr;while(*str!='\0'&&*str==*p){str++;p++;}//*str==\0 或者*str!=*pif(*str=='\0')//str中字符都匹配完了,之前的全部一致 {lineptr=p;//移动lineptr.return 1;}//未解析到则不用移动return 0; } void getname(char *name) {while(*lineptr==' '||*lineptr=='\t')lineptr++;while(*lineptr!='\0'&&*lineptr!='\n'&&*lineptr!='<'&&*lineptr!='|'&&*lineptr!='>'&&*lineptr!='&'&&*lineptr!=' '&&*lineptr!='\t'){*name++=*lineptr++;} *name='\0'; }
在shell_loop的主循环while(1)中解析完命令就是执行命令,执行命令在execute.h和execute.c两个文件中实现
1 #ifndef _EXECUTE_H 2 #define _EXECUTE_H 3 4 void execute_disk_command(void); 5 void forkexec(int); 6 #endif
1 #include "execute.h" 2 #include "def.h" 3 #include "externs.h" 4 #include <unistd.h> 5 #include <sys/wait.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <fcntl.h> 9 #include <signal.h> 10 #include <stdio.h> 11 //执行命令,通过输入输出文件是否为空来判断是否有重定向命令要执行 12 //通过命令个数来判断是否有管道符来决定直接执行命令(标准输入输出)还是执行管道命令 13 void execute_disk_command(void) 14 { 15 if(cmd_count==0) 16 return ;//没有命令只有换行就不执行,除BUG 17 //解析执行带输入输出重定向命令. 18 //cat < test.txt | grep -n public > test2.txt & 19 if(infile[0]!='\0')// < 输入重定向,只可能是第一条命令 20 { 21 cmd[0].infd=open(infile,O_RDONLY); 22 } 23 24 if(outfile[0]!='\0')// > 或者 >> 输出重定向只能是最后一条命令 25 { 26 if(append)//追加方式 27 { 28 umask(0); 29 cmd[cmd_count-1].outfd=open(outfile,O_WRONLY|O_CREAT|O_APPEND,0666); 30 } 31 else 32 { 33 umask(0); 34 cmd[cmd_count-1].outfd=open(outfile,O_WRONLY|O_CREAT|O_TRUNC,0666); 35 } 36 } 37 //后台作业,忽略掉SIGCHLD信号,防止僵尸进程 38 //后台作业不会调用wait等待子进程退出 39 if(backgnd==1) 40 { 41 signal(SIGCHLD,SIG_IGN);//下个命令之前需要还原,忽略了退出信号。无法在backgnd==0时等待 42 } 43 else signal(SIGCHLD,SIG_DFL);//如果不还原。例如刚执行了一个wc & 那么后台进程会时SIGCHLD被忽略,前台进程父进程才要wait。执行ls前台进程时,如果不将SIGCHLD处理函数还原就会使得while(wait(NULL)!=lastpid) 44 /*只带管道的话 例如: ls | grep init | wc -w*/ 45 int i=0; 46 int fd; 47 int fds[2]; 48 //ls | grep init | wc - w cmd[0]:ls cmd[1]:grep cmd[2]:wc 49 for(i=0;i<cmd_count;i++) 50 { 51 if(i<cmd_count-1)//不是最后一条命令。如果cmd_count=1那么就没有管道符就不用创建管道符 52 { 53 pipe(fds);//创建管道 cmd[i]的输出为 cmd[i+1]的输入。所以把cmd[i]的输出置为管道的写端,管道的读端作为cmd[i+1]的输入 54 cmd[i].outfd=fds[1];//将当前命令的输出定向到管道的写端 55 cmd[i+1].infd=fds[0];//将下一条命令的输入定向到管道的读端 56 57 } 58 forkexec(i);//fork子进程执行命令,传入结构体指针cmd结构体数组 59 60 if((fd=cmd[i].infd)!=0)//进程执行完,还原 61 close(fd);· 62 if((fd=cmd[i].outfd)!=1)//标准输出 63 close(fd); 64 } 65 //后台作业控制,backgnd==1不需要等待,需要防止产生僵尸进程 66 if(backgnd==0)//前台作业0 67 { 68 /* 前台作业,需要等待管道中最后一个命令退出 */ 69 while(wait(NULL)!=lastpid) 70 ;//等待最后一个进程结束。如果不等待,那么父进程可能先退出,重新开始循环等待输入命令,先打印出[minishell$]。子进程再输出结果 71 } 72 } 73 void forkexec(int i) 74 { 75 pid_t pid; 76 pid=fork(); 77 if(pid==-1) 78 ERR_EXIT("fork error"); 79 if(pid>0) 80 { 81 //父进程 82 if(backgnd==1) 83 printf("%d\n",pid);//打印后台进程的进程ID 84 lastpid=pid;//保存最后一个进程ID. 85 86 } 87 else if(pid==0) 88 { 89 //ls | wc -c 90 //backgnd==1,将第一条简单命令的infd重定向至/dev/null 91 //当第一条命令试图从标准输入获取数据的时候,立即返回EOF。 92 //这样就不用考虑作业控制了 93 if(cmd[i].infd==0&&backgnd==1)//输入描述符等于0,肯定是第一条命令 94 cmd[i].infd=open("/dev/null",O_RDONLY); 95 //将第一个简单命令进程作为进程组组长,信号发给当前整个进程组,父进程不再收到 96 if(i==0) 97 { 98 //将第一个简单命令进程单独设置为一个进程组,那么信号SIGINT只会发给这个进程组。不会发给父进程minishell这样就不会打印两次minishell$ 99 setpgid(0,0); 100 101 } 102 //子进程 103 if(cmd[i].infd!=0) //输入不是标准输入,命令从管道输入 104 { 105 //等价于dup2(cmd[i].infd,0) 106 close(0); 107 dup(cmd[i].infd);//将命令输入描述符也就是管道读端,置位命令的标准输入 108 109 } 110 if(cmd[i].outfd!=1)//命令的输出不是标准输出,那么命令的输出就是输出到管道。 111 { 112 close(1); 113 dup(cmd[i].outfd); 114 } 115 int j; 116 for(j=3;j<sysconf(_SC_OPEN_MAX);j++) 117 close(j);//关闭3以上文件描述符 118 if(backgnd==0)//前台作业恢复信号 119 { 120 signal(SIGINT,SIG_DFL);//前台作业需要将信号还原,不然如果ctrl+c会调用init中的信号处理函数打印两次minshell$ 121 signal(SIGQUIT,SIG_DFL); 122 } 123 /* 124 实现I/O重定向 125 126 调用exec后,原来打开的文件描述符仍然是打开的。利用这一点可以实现I/O重定向。 127 先看一个简单的例子,把标准输入转成大写然后打印到标准输出: 128 129 例大小写转换源码upper.c: 130 #include <stdio.h> 131 132 int main(void) 133 { 134 int ch; 135 while((ch = getchar()) != EOF) { 136 putchar(toupper(ch)); 137 } 138 return 0; 139 } 140 141 程序wrapper.c: 142 #include <unistd.h> 143 #include <stdlib.h> 144 #include <stdio.h> 145 #include <fcntl.h> 146 int main(int argc, char *argv[]) 147 { 148 int fd; 149 if (argc != 2) { 150 fputs("usage: wrapper file\n", stderr); 151 exit(1); 152 } 153 154 fd = open(argv[1], O_RDONLY); 155 if(fd<0) { 156 perror("open"); 157 exit(1); 158 } 159 160 dup2(fd, STDIN_FILENO); 161 close(fd); 162 163 execl("./upper", "upper", NULL); 164 perror("exec ./upper"); 165 exit(1); 166 } 167 168 wrapper程序将命令行参数当作文件名打开,将标准输入重定向到这个文件,然后调用exec执行upper程序,这时原来打开的文件描述符仍然是打开的,upper程序只负责从标准输入读入字符转成大写,并不关心标准输入对应的是文件还是终端。 169 */ 170 execvp(cmd[i].args[0],cmd[i].args); 171 //替换失败就到这行 172 exit(EXIT_FAILURE); 173 } 174 175 }
build.c和build.h是内部命令解析模块,这部分内容基本还没有去实现...
1 #ifndef _BUILTIN_H 2 #define _BUILTIN_H 3 4 int builtin(void); 5 #endif
1 #include "builtin.h" 2 /* 3 内部命令解析,返回1表示内部命令,返回0不是内部命令 4 */ 5 void do_exit(); 6 void do_cd(); 7 int builtin(void) 8 { 9 if (check("exit")) 10 do_exit(); 11 else if (check("cd")) 12 do_cd(); 13 else 14 return 0; 15 return 1; 16 } 17 18 void do_exit() 19 { 20 printf("exit"); 21 exit(EXIT_SUCCESS); 22 } 23 void do_cd() 24 { 25 printf("do_cd...\n"); 26 }
def.h声明一些各个模块中用到的宏
//头文件声明宏 #ifndef _DEF_H_ #define _DEF_H_#include<stdio.h> #include <stdlib.h>#define ERR_EXIT(m)\do\{\perror(m);\exit(EXIT_FAILURE);\}while(0) #define MAXLINE 1024//输入行最大长 #define MAXARG 20 //每个简单命令参数最多个数 #define PIPELINE 5//一个管道行简单命令最多个数 #define MAXNAME 100//IO重定向文件名最大个数typedef struct command {char *args[MAXARG+1];//参数解析出来放到args中,参数列表int infd;//输入描述符int outfd;//输出描述符 } COMMAND;#endif
externs.h主要是一些外部变量的声明
#ifndef _EXTERNS_H #define _EXTERNS_H#include "def.h" extern char cmdline[MAXLINE+1]; extern COMMAND cmd[PIPELINE]; extern char avline[MAXLINE+1]; extern char *lineptr;//指向cmdline数组 extern char *avptr;//指向avline数组 extern int cmd_count; extern int backgnd; extern char infile[MAXNAME+1]; extern char outfile[MAXNAME+1]; extern int lastpid; extern int append; #endif
最后是一个Makefile文件
.PHONY:clean CC=gcc CFLAGS=-Wall -g BIN=minishell OBJS=main.o parse.o init.o execute.o builtin.o $(BIN):$(OBJS)$(CC) $(CFLAGS) $^ -o $@ %.o:%.c$(CC) $(CFLAGS) -c $< -o $@ clean:rm -f *.o $(BIN)
转载于:https://www.cnblogs.com/wsw-seu/p/8280531.html
minishell的实现相关推荐
- C语言实现miniShell
Shell就是命令行的解释器,因为在Linux下,是没有图形界面的,我们需要通过命令行输入我们的命令,然后Shell将命令解析后反馈给Linux内核,内核运算处理后再讲结果通过Shell解析给用户. ...
- 进程间程序替换和minishell
进程间程序替换和minishell 文章目录 进程间程序替换和minishell 一.进程间程序替换的原理 二.exec函数族的用法及 三.它们之间的联系 四.简易的shell 一.进程间程序替换的原 ...
- minishell的实现及IO接口的调用
Minishell编写: 问题:二维数组使用上还是和指针数组有很大差别. 运行结果如下: 基础IO:文件的输入输出操作-标准库IO接口/linux下系统调用IO接口/文件描述符的理解/重定向的理解/文 ...
- C++教程网 linux之miniShell的实战
第一讲: 搭建minishell程序框架 编写Makefile 第二讲: 对简单命令进行解析并执行 第三讲: 搭建复杂命令解析框架 解析输入重定向.输出重定向.管道.后台作业 第四讲 解析复杂命令 解 ...
- 进程控制(二)——minishell延续
文章目录 进程控制的简单相关回顾 环境变量怎么读取的,如何由父进程让子进程拿到呢? minishell代码排错 为什么子进程获取不到父进程的环境变量呢? 程序替换不会替换父进程的环境变量? minis ...
- 进程程序替换(自主实现shell)
进程替换 替换进程所运行的程序 流程:将另一段代码加载到内存中,通过页表将原来进程的映射关系重新建立到新的程序在内存中的地址,相当于替换了进程所运行程序以及所要处理的数据 (替换了代码段,重新初始化数 ...
- Linux中的基础IO(二)
Linux中的基础IO(二) 文章目录 Linux中的基础IO(二) 一.基本接口 二.文件描述符 三.文件描述符的分配规则 四.重定向 五.dup2系统调用 六.minishell 一.基本接口 i ...
- 【Linux操作系统】--攥写一个简易的shell工具
目录 做一个简易的shell 第一步:打印提示符,在同一行打印出提示符 第二步:获取标准输入,获取命令字符串 第三步:将字符串分割,解析字符串 第四步:替换进程,执行第三方命令 3.5.执行第三方命令 ...
- python爬虫(四)cookie模拟登录和反反爬案例
处理不被信任证书的网站 SSL证书:数字证书的一种,配置在服务器上面的,类似于驾驶证.护照和营业执照的电子副本.因为配置在服务器上,也称为SSL服务器证书. SSL证书的特点:遵循了SSL协议,由收信 ...
最新文章
- autodesk许可证服务器,Autodesk软件工作流介绍(十)——配置网络许可服务器的步骤...
- jzoj4051-序列统计【NTT】
- Elasticsearch集群知识笔记
- Javascript学习总结 - JS基础系列三
- java 规则引擎roolie_【智能决策引擎】规则引擎介绍
- java实现微信支付
- 11.程序员的自我修养---运行库
- win10 android4.4 驱动,ST-LINK/V2驱动win10版
- Visual C++ 2010 Express使用教程
- atk-hc05 蓝牙
- linux木马查杀工具,【Kali】linux木马查杀
- 蓝牙inquiry流程之命令下发
- CSS改变table内置tbody滚动条
- 微软同步备份工具SyncToy,值得使用
- HBase中MemStore flush的源码解析
- esp分区中的EFI启动项文件有什么用
- 使用xiaopiu常见技巧
- 【3D 目标检测】Not All Points Are Equal Learning Highly Efficient Point-based Detectors for 3D LiDAR Point
- Java 如何学习?这份5000页Java学习手册值得拥有,适合零基础自学也适合查漏补缺!
- AVR学习笔记之熔丝位
热门文章
- file watchers怎么默认打开_python怎么打印字符
- 需要显卡还是cpu_玩游戏卡顿,帧数低,是该升级显卡,还是升级cpu?
- 怎么批量抠复杂的图_抠图怎么抠?一次教你五招!
- 计算机网络作业第六章,计算机网络与通信第6章习题.doc
- php xml 留言板,php xml留言板 xml存储数据的简单例子
- 新颖性搜索(Novelty Search,NS)算法详解与实现
- Java不满足的依赖异常_java – 新的缺失/不满足的依赖项WildFly 9中的错误
- /proc/cpuinfo_Linux中的/ proc / cpuinfo和/ proc / meminfo文件
- Eclipse中的m2e不支持如何修复maven-dependency-plugin(目标为“ copy-dependencies”,“ unpack”)
- Java FileNameFilter示例