1.打印提示符并獲取命令行
我們在使用shell的時候,發現我們在輸入命令是,前面會有:有用戶名,版本,當前路徑等信息,這里我們可以用環境變量去獲取:
1 #include <stdio.h>2 #include <stdlib.h>3 4 const char* getUsername()5 {6 const char* name = getenv("USER");7 if(name) return name;8 else return "none";9 }10 11 const char* getHostname()12 {13 const char* hostname = getenv("HOSTNAME");14 if(hostname) return hostname;15 else return "none";16 }17 18 const char* getCwd()19 {20 const char* cwd = getenv("PWD");21 if(cwd) return cwd;22 else return "none";23 }24 25 int main()26 {27 printf("%s@%s %s\n",getUsername(),getHostname(),getCwd()); 28 return 0;29 }
寫。
看到我們打印出來的是絕對路徑, 而shell顯示的相對路徑, 但為了區分先這樣不去裁剪.?
1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 5 #define NUM 1024 6 7 const char* getUsername() 8 { 9 const char* name = getenv("USER"); 10 if(name) return name; 11 else return "none"; 12 } 13 14 const char* getHostname() 15 { 16 const char* hostname = getenv("HOSTNAME"); 17 if(hostname) return hostname; 18 else return "none"; 19 } 20 21 const char* getCwd() 22 { 23 const char* cwd = getenv("PWD"); 24 if(cwd) return cwd; 25 else return "none"; 26 } 27 28 int getUsercommand(char* command, int num) 29 { 30 printf("[%s@%s %s]",getUsername(),getHostname(),getCwd()); 31 char* r = fgets(command,num,stdin);//最終還是會輸入\n32 if(r == NULL) return 1;33 34 command[strlen(command)-1] = '\0';//去除輸入的換行35 return 0; 36 } 37 38 int main() 39 { 40 char usercommand[NUM];41 //1.打印提示符并且獲取命令字符串42 getUsercommand(usercommand,sizeof(usercommand));43 //2. 44 //3. 45 printf("%s",usercommand);//回顯命令,用于測試46 return 0;47 }
?由于用scanf接收的遇到空格就會停止讀取, 所以用fgets, 而且用戶輸入完命令一定會輸入回車, 所以把最后一個回車符刪掉.
2.解析命令行
我們在輸入命令時, 可能不僅僅只是一段,比如說:"ls -a -l "。但是命令行解釋器內部在解析指令時應該傳遞的是"ls" "-a" "-l"這樣的多個個字符串, 所以我們還需要以空格來分割字符串。
1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 5 #define DEBUG 16 #define NUM 10247 #define SIZE 648 #define SEP " "41 void commandSplit(char* in, char* out[])42 {43 int argc = 1;44 out[0] = strtok(in,SEP);
W> 45 while(out[argc++] = strtok(NULL,SEP));//報警不需要處理46 47 #ifdef DEBUG 48 for(int i = 0; out[i]; i++)49 printf("%d:%s\n",i,out[i]);50 #endif51 }52 53 int main()54 {55 char usercommand[NUM];56 char* argv[SIZE];57 //1.打印提示符并且獲取命令字符串58 getUsercommand(usercommand,sizeof(usercommand));59 //2.分割字符串60 commandSplit(usercommand, argv); 61 //3. 62 return 0;63 }
3.執行對應的命令?
創建子進程和進程替換, 為了不影響shell, 我們將大部分指令的執行讓子進程去完成, 父進程只要阻塞等待子進程完成就好了。
1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 #include <unistd.h>5 #include <sys/types.h>6 #include <sys/wait.h>7 8 //#define DEBUG 19 #define NUM 102410 #define SIZE 6411 #define SEP " "12 13 const char* getUsername()14 {15 const char* name = getenv("USER");16 if(name) return name;17 else return "none";18 }19 20 const char* getHostname()21 {22 const char* hostname = getenv("HOSTNAME");23 if(hostname) return hostname;24 else return "none";25 }26 27 const char* getCwd()28 {29 const char* cwd = getenv("PWD");30 if(cwd) return cwd;31 else return "none";32 }33 34 int getUsercommand(char* command, int num) 35 {36 printf("[%s@%s %s]",getUsername(),getHostname(),getCwd()); 37 char* r = fgets(command,num,stdin);//最終還是會輸入\n38 if(r == NULL) return -1;39 40 command[strlen(command)-1] = '\0';//去除輸入的換行41 return strlen(command);42 }43 44 void commandSplit(char* in, char* out[])45 {46 int argc = 1;47 out[0] = strtok(in,SEP);
W> 48 while(out[argc++] = strtok(NULL,SEP));//報警不需要處理49 50 #ifdef DEBUG 51 for(int i = 0; out[i]; i++)52 printf("%d:%s\n",i,out[i]);53 #endif54 }55 56 int execute(char* argv[])57 {58 pid_t id = fork();59 if(id < 0) return 1;60 else if(id == 0)61 {62 //child63 //exec commond64 execvp(argv[0],argv);65 exit(1);66 }67 68 else69 {70 //father71 pid_t rid = waitpid(id,NULL,0);72 if(rid < 0)73 printf("wait fail\n");74 }75 76 return 0;77 }78 79 int main()80 {81 while(1)82 {83 char usercommand[NUM];84 char* argv[SIZE];85 //1.打印提示符并且獲取命令字符串86 int n = getUsercommand(usercommand,sizeof(usercommand));87 if(n <= 0) continue;//如果得到的是空串或者獲取失敗,不要往后執行88 //2.分割字符串89 commandSplit(usercommand, argv);90 //3.執行命令91 execute(argv); 92 }93 return 0;94 }
?由于shell要一直運行, 所以要循環執行, 這里程序替換用execvp函數比較合適, 因為argv數組就是我們分割出的一個個命令的子串, argv[0]就是程序名, argv就是指令集. 父進程只進行wait即可.?
此外, getUsercommand函數可以優化一下, 返回的是輸入的指令的長度, 如果接收失敗(返回值為-1或者返回值是0只打印了空行)就不需要往下執行了, 直接continue進行下一輪.
4.特殊處理
?有一批命令, 不能讓子進程執行, 必須讓父進程自己執行, 這些命令叫內建命令.
1) cd指令
可以看到cd .. 之后并沒有發生什么異常, 但是pwd之后發現路徑沒有發生變化.?
我們為什么能在linux中進入某個目錄, 就是因為我們改變了shell的工作目錄. 每個進程都有自己的工作目錄, 我們想讓父進程的工作目錄發生改變, 但是程序替換之后都是子進程在執行cd .., 改變的都是子進程的工作目錄, 子進程改變完了又被回收了, 父進程完全沒發生變化, 所以cd應該實現成內建命令。
79 void cd(const char* path)80 {81 chdir(path);82 }83 84 //1->yes,0->no85 int doBuildin(char* argv[])86 {87 if(strcmp(argv[0],"cd") == 0)88 {89 char* path = NULL;
W> 90 if(argv[1] == NULL) path = ".";91 else path = argv[1];92 cd(path);93 return 1;94 }95 else if(strcmp(argv[0],"ls")==0)96 {97 return 1;98 }99 return 0; 100 }101 102 int main()103 {104 while(1)105 {106 char usercommand[NUM];107 char* argv[SIZE];108 //1.打印提示符并且獲取命令字符串109 int n = getUsercommand(usercommand,sizeof(usercommand));110 if(n <= 0) continue;//如果得到的是空串或者獲取失敗,不要往后執行111 //2.分割字符串112 commandSplit(usercommand, argv);113 //3.檢查是不是內建命令,是的話直接執行114 n = doBuildin(argv);115 if(n) continue;//是內建命令不用往后執行了116 //4.執行命令117 execute(argv);118 }119 return 0;120 }
所以在執行命令前先檢查是不是內建命令, 用返回值接收, 如果是就直接執行并返回1, continue不往下執行, 如果不是就返回0, 執行命令.?
?既然當前的工作目錄改變了, 那么環境變量PWD也要改變:?
chdir改變當前工作目錄, getcwd獲取當前的工作路徑, sprintf將tmp中的內容輸出到cwd中, putenv將cwd導入環境變量.?
2) export命令?
?
創建一個數組env儲存要導入的環境變量, 設置size指向導入到第幾個環境變量.
如果argv[1]是空就直接返回, 否則就導入環境變量, 注意不能直接把argv[1]導入進去, 因為argv[1]隨著指令的輸入時刻在變化, 需要開辟額外的空間去存儲.
3)echo指令
4)ls指令
我們執行的ls指令中不同的文件都有不同的顏色,所以對于ls我們可以在分割命令的時候加上一個“--color=auto”.