實現一個自己的shell程序,這個程序有這些功能:解釋執行命令,支持輸入輸出重定向,支持管道,后台運行
程序。當運行該程序后,它支持以下的命令格式:
1.單個命令,如:ls。2.帶l到多個參數的命令,如ls -l。3.帶一個輸出重定向的命令。4.帶一個輸入重定向的
命令。5.帶一個管道的命令。6.后台運行符&可加在各個命令的最后面。7.輸入exit或logout退出myshell。
錯誤處理:1.輸入錯誤的命令格式報錯。2.輸入不存在的命令報錯。
程序主函數的流程圖如下:
程序的各個函數的功能及說明:
(1)void print_prompt():該函數只是簡單地打印myshell的提示符,即“myshell$”。
(2)void get_input(char *buf):獲得一條用戶輸入的待執行命令,參數buf用於存放輸入的命令。如果輸入的
命令過長(大於256個字符),則終止程序。輸入的命令以換行符'\n'作為結束標志。
(3)void explain_input(char *buf,int *argcount,char arglist[100][256]):解析buf中存放的命令,把每個
選項存放在arglist中。
(4)do_cmd(int argcount,char arglist[100][256]):執行arglist中存放的命令,arglist為待執行命令的參數
的個數。
(5)int find_command(char *command):功能是分別在當前目錄下、/bin、/usr/bin目錄下查找命令的可執行程
序。
下面是該程序的源代碼:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <fcntl.h> #include <sys/stat.h> #include <dirent.h> #define normal 0 //一般的命令 #define out_redirect 1 //輸出重定向 #define in_redirect 2 //輸入重定向 #define have_pipe 3 //命令中有管道 void print_prompt(); //打印提示符 void get_input(char*); //得到輸入的命令 void explain_input(char*,int*,char a[][256]); //對輸入命令進行解析 void do_cmd(int,char a[][256]); //執行命令 int find_command(char*); //查找命令中的可執行程序 int main(int argc,char *argv[]) { int i; int argcount= 0; char arglist[100][256]; char *buf= NULL; char **arg= NULL; buf= (char*)malloc(256); if(buf == NULL) { perror("malloc failed"); exit(-1); } while(1) { memset(buf,0,256); //將buf所指向的空間清零 print_prompt(); get_input(buf); //若輸入的命令為exit或logout則退出本程序 if(strcmp(buf,"exit\n")== 0||strcmp(buf,"logout\n")== 0) break; for(i= 0;i< 100;i++) { arglist[i][0]= '\0'; } argcount= 0; explain_input(buf,&argcount,arglist); do_cmd(argcount,arglist); } if(buf!= NULL) { free(buf); buf= NULL; } return 0; } void print_prompt() { printf("myshell$ "); } //獲取用戶輸入 void get_input(char *buf) { int len= 0; int ch; ch= getchar(); while(len< 256&&ch!= '\n') { buf[len++]= ch; ch= getchar(); } if(len== 256) { printf("command is too long\n"); exit(-1); //輸入的命令過長則退出程序 } buf[len]= '\n'; len++; buf[len]= '\0'; } /*解析buf中的命令,將結果存入arglist中,命令以回車符號\n結束,如輸入命令為 *"ls -l /tmp",則arglist[0]、arglist[1]、arglist[2]分別為ls、-l、/tmp */ void explain_input(char *buf,int *argcount,char arglist[100][256]) { char *p= buf; char *q= buf; int number= 0; while(1) { if(p[0]== '\n') break; if(p[0]== ' ') p++; else { q = p; number= 0; while((q[0]!= ' ')&&(q[0]!= '\n')) { number++; q++; } strncpy(arglist[*argcount],p,number+ 1); arglist[*argcount][number]= '\0'; *argcount= *argcount+ 1; p = q; } } } void do_cmd(int argcount,char arglist[100][256]) { int flag= 0; int how= 0; //用於指示命令中是否含有>、<、| int background= 0; //標識命令中是否有后台運行標識符& int status; int i; int fd; char* arg[argcount+ 1]; char* argnext[argcount+ 1]; char* file; pid_t pid; //將命令取出 for(i= 0;i< argcount;i++) { arg[i]= (char*)arglist[i]; } arg[argcount]= NULL; //查看命令行是否有后台運行符 for(i= 0;i< argcount;i++) { if(strncmp(arg[i],"&",1)== 0) { if(i== argcount-1) { background= 1; arg[argcount-1]= NULL; break; } else { printf("wrong command\n"); return; } } } for(i= 0;arg[i]!= NULL;i++) { if(strcmp(arg[i],">")== 0) { flag++; how= out_redirect; if(arg[i+ 1]== NULL) flag++; } if(strcmp(arg[i],"<")== 0) { flag++; how= in_redirect; if(i== 0) flag++; } if(strcmp(arg[i],"|")== 0) { flag++; how= have_pipe; if(arg[i+ 1]== NULL) flag++; if(i== 0) flag++; } } /* flag大於1,說明命令中含有多個>,<,|符號,本程序是不支持這樣的命令的,或者命令格式不對,如"ls -l /tmp >" */ if(flag> 1) { printf("wrong command\n"); return; } if(how== out_redirect) { //命令只含有一個輸出重定向符號 for(i= 0;arg[i]!= NULL;i++) { if(strcmp(arg[i],">")== 0) { file= arg[i+ 1]; arg[i]= NULL; } } } if(how== in_redirect) { //命令中只含有一個輸入重定向符號 for(i= 0;arg[i]!= NULL;i++) { if(strcmp(arg[i],"<")== 0) { file= arg[i+ 1]; arg[i]= NULL; } } } if(how== have_pipe) { /*命令只含有一個管道符號|,把管道符號后面的部分存入argnext中,管道后面的部分是一個可執行的shell命令 */ for(i= 0;arg[i]!= NULL;i++) { if(strcmp(arg[i],"|")== 0) { arg[i]= NULL; int j; for(j= i+ 1;arg[j]!= NULL;j++) { argnext[j-i-1]= arg[j]; } argnext[j-i-1]= arg[j]; break; } } } if((pid= fork())< 0) { printf("fork error\n"); return; } switch(how) { case 0: /*pid為0說明是子進程,在子進程中執行輸入的命令,輸入的命令不含>、<、和| */ if(pid== 0) { if(!(find_command(arg[0]))) { printf("%s : command not found\n",arg[0]); exit(0); } execvp(arg[0],arg); exit(0); } break; case 1: //輸入的命令中含有輸出重定向符> if(pid== 0) { if(!(find_command(arg[0]))) { printf("%s : command not found\n",arg[0]); exit(0); } fd= open(file,O_RDWR|O_CREAT|O_TRUNC,0644); dup2(fd,1); execvp(arg[0],arg); exit(0); } break; case 2: //輸入的命令中含有輸入重定向符< if(pid== 0) { if(!(find_command(arg[0]))) { printf("%s : command not found\n",arg[0]); exit(0); } fd= open(file,O_RDONLY); dup2(fd,0); execvp(arg[0],arg); exit(0); } break; case 3: //輸入的命令中含有管道符| if(pid== 0) { int pid2; int status2; int fd2; if((pid2= fork())< 0) { printf("fork2 error\n"); return; } else if(pid2== 0) { if(!(find_command(arg[0]))) { printf("%s : command not found\n",arg[0]); exit(0); } fd2= open("/tmp/youdonotknowfile",O_WRONLY|O_CREAT|O_TRUNC,0644); dup2(fd2,1); execvp(arg[0],arg); exit(0); } if(waitpid(pid2,&status2,0)== -1) printf("wait for child process error\n"); if(!(find_command(argnext[0]))) { printf("%s : command not found\n",argnext[0]); exit(0); } fd2= open("/tmp/youdonotknowfile",O_RDONLY); dup2(fd2,0); execvp(argnext[0],argnext); if(remove("/tmp/youdonotknowfile")) printf("remove error\n"); exit(0); } break; default: break; } //若命令中有&,表示后台執行,父進程直接返回,不等待子進程結束 if(background== 1) { printf("[process id %d]\n",pid); return; } //父進程等待子進程結束 if(waitpid(pid,&status,0)== -1) printf("wait for child process error\n"); } //查找命令中的可執行程序 int find_command(char *command) { DIR *dp; struct dirent *dirp; char *path[]= {"./","/bin","/usr/bin",NULL}; //使當前目錄下的程序可以運行,如命令"./fork"可以被正確解釋和執行 if(strncmp(command,"./",2)== 0) command= command+ 2; //分別在當前目錄、/bin和/usr/bin目錄查找要執行的程序 int i= 0; while(path[i]!= NULL) { if((dp= opendir(path[i]))== NULL) printf("can not open /bin \n"); while((dirp= readdir(dp))!= NULL) { if(strcmp(dirp->d_name,command)== 0) { closedir(dp); return 1; } } closedir(dp); i++; } return 0; }