在学习了Linux的进程控制之后,学习了fork函数和exec函数族,通过这些个函数可以简单的实现一份shell,就是实现一份命令行解释器,当然是简单版的,实现功能如下
还不能实现正则表达式,要实现这个我当前的代码根本不能用,要重头开始改写。。。
下面贴代码
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("[%s@%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命令的实现有些问题,不是普通命令的实现,就是说简单的使用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 }