在学习了Linux的进程控制之后,学习了fork函数和exec函数族,通过这些个函数可以简单的实现一份shell,就是实现一份命令行解释器,当然是简单版的,实现功能如下
- 能执行普通的命令如ls ,ps ,top等
- 可以实现目录的跳转cd命令
- 能执行命令并加上参数如ls-l
- 能执行打开man手册
- 能识别管道符
还不能实现正则表达式,要实现这个我当前的代码根本不能用,要重头开始改写。。。
下面贴代码
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/wait.h> 6 #include <stdlib.h> 7 #include <pwd.h> 8 #include <sys/utsname.h> 9 #include <libgen.h> 10 11 12 void eatblank(char **buf) 13 { 14 while(**buf==‘ ‘) 15 { 16 (*buf)++; 17 } 18 } 19 20 21 void GetHostName(char *hostname,int length) 22 { 23 gethostname(hostname,length); 24 char *p=hostname; 25 while(*p!=‘\0‘) 26 { 27 if(*p==‘.‘) 28 { 29 *p=‘\0‘; 30 } 31 p++; 32 } 33 } 34 35 void Pipe(char **my_argv,char *buf); 36 void BuildCommand(char **my_argv,char *buf) 37 { 38 eatblank(&buf); 39 my_argv[0]=buf; 40 int index=1; 41 char *p=buf; 42 while(*p!=‘\0‘) 43 { 44 if(*p==‘ ‘) 45 { 46 *p=‘\0‘; 47 p++; 48 eatblank(&p) ; 49 if(*p!=‘|‘) 50 { 51 my_argv[index++]=p; 52 } 53 continue; 54 } 55 else if(*p==‘|‘) 56 { 57 p++; 58 //p++; 59 my_argv[index]=NULL; 60 Pipe(my_argv,p); 61 } 62 else 63 { 64 p++; 65 } 66 } 67 my_argv[index]=NULL; 68 } 69 70 71 72 void Pipe(char ** my_argv,char *buf) 73 { 74 int fd[2]; 75 pipe(fd); 76 pid_t id2=fork(); 77 if(id2==0) 78 { 79 close(1); 80 dup(fd[1]); 81 close(fd[1]); 82 close(fd[0]); 83 execvp(my_argv[0],my_argv); 84 } 85 else 86 { 87 waitpid(id2,NULL,0); 88 close(0); 89 dup(fd[0]); 90 close(fd[0]); 91 close(fd[1]); 92 BuildCommand(my_argv,buf); 93 execvp(my_argv[0],my_argv); 94 } 95 //在此处添加exec族函数 96 } 97 98 99 int main() 100 { 101 while(1) 102 { 103 char *my_argv[64]; 104 struct passwd *pwd=getpwuid(getuid()); 105 char hostname[256]={‘\0‘}; 106 char cwd[256]={‘\0‘}; 107 getcwd(cwd,256); 108 GetHostName(hostname,256); 109 printf("[%[email protected]%s %s]#",pwd->pw_name,hostname,basename(cwd)); 110 fflush(stdout); 111 char buf[1024]; 112 buf[0]=‘\0‘; 113 114 int count=read(0,buf,sizeof(buf)); 115 buf[count-1]=‘\0‘; 116 my_argv[0]=buf; 117 pid_t id=fork(); 118 if(id==0) 119 { 120 //child 121 122 if(strncmp(buf,"cd",2)==0) 123 { 124 exit(1); 125 } 126 BuildCommand(my_argv,buf); 127 execvp(my_argv[0],my_argv); 128 printf("if the process has some problem ,I should run here\n"); 129 exit(0); 130 } 131 else 132 { 133 //father 134 int status=0; 135 wait(&status); 136 if(status==256) 137 { 138 my_argv[0]+=3; 139 chdir(my_argv[0]); 140 } 141 } 142 } 143 return 0; 144 }
通过gethostname获取主机名,通过getcwd获得当前工作目录,通过getpwuid获得当前登录的用户信息
这样命令提示符就完成了;
- 普通命令的实现
普通命令的实现并不困难,我的目的是让子进程来执行命令,也就是通常的让fork产生的子进程执行exec族函数,然后自己死掉,通过父进程回收资源,循环并创建新的子进程,这就是shell的大框架,其中用execvp函数来实现命令的执行,函数原型就不多说了,查手册就能查到,简单解释一下参数,第一个参数是命令行的字符串,第二是参数是一个字符串数组,从上到下依次存放,命令,参数(可能有多个,一个参数占一个下标),最后用NULL占据一个下标表示结束。
- cd命令的实现
cd命令的实现有些问题,不是普通命令的实现,就是说简单的使用execvp是不能实现的,因为就算子进程改变了目录之后也会把自己杀死,父进程和子进程之间是不通的(就是说父进程和子进程并不共享环境变量,子进程修改了当前工作目录的环境变量对父进程也没有什么影响),后来使用system来执行系统调用,也失败,因为更多时候shell会产生一个子进程来执行命令,因为shell本身执行会有风险,可能会因为用户的错误操作把自己挂掉,所以使用子进程来执行命令,而这样和刚才的结果是一样的并不会影响到父进程,最后采用了chdir函数,他可以改变当前进程的环境变量中的工作目录(就是专门change dir用的),让父进程来执行,fork出来的子进程会有一份父进程环境变量的拷贝,这就完成了改变目录,并将结果传递了下来
1 else 2 { 3 //father 4 int status=0; 5 wait(&status); 6 if(status==256) 7 { 8 my_argv[0]+=3; 9 chdir(my_argv[0]); 10 } 11 }
- 管道符的实现
管道符的实现很简单,平常我们执行命令的时候,都是结果自动输出到电脑屏幕上,这说明一般命令的输出是write在标准输出stdout的,而我们输出命令的参数是通过键盘,这说明命令的输入来源是标准输入stdin,如果我们关闭了,标准输出和标准输入,而是通过一个管道,让结果写进管道,然后让参数从管道中读取(简单的说就是让管道的两段代替标准输入和标准输出),管道符就实现了。
1 void Pipe(char ** my_argv,char *buf) 2 { 3 int fd[2]; 4 pipe(fd); 5 pid_t id2=fork(); 6 if(id2==0) 7 { 8 close(1); 9 dup(fd[1]); 10 close(fd[1]); 11 close(fd[0]); 12 execvp(my_argv[0],my_argv); 13 } 14 else 15 { 16 waitpid(id2,NULL,0); 17 close(0); 18 dup(fd[0]); 19 close(fd[0]); 20 close(fd[1]); 21 BuildCommand(my_argv,buf); 22 execvp(my_argv[0],my_argv); 23 }