在學習了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 }
http://xxxxxx/Linuxjc/1137425.html TechArticle