視頻網(wǎng)站做cpa搜索關鍵詞的工具
送給大家一句話:
不管前方的路有多苦,只要走的方向正確,不管多么崎嶇不平,都比站在原地更接近幸福。 —— 宮崎駿《千與千尋》
自主shell命令編寫
- 1 前言
- 2 項目實現(xiàn)
- 2.1 創(chuàng)建命令行
- 2.2 獲取命令
- 2.3 分割命令
- 2.4 運行命令
- 3 源代碼
- Thanks?(・ω・)ノ謝謝閱讀!!!
- 下一篇文章見!!!
1 前言
前幾篇文章,我們學習進程的相關知識:進程概念,進程替換,進程控制。熟悉了進程到底是個什么事情,接下來我們來做一個實踐,來運用我們所學的相關知識。這個項目就是手搓一個shell模塊,模擬實現(xiàn)Xshell中的命令行輸入。
用下圖的時間軸來表示事件的發(fā)生次序。其中時間從左向右。shell由標識為sh的方塊代表,它隨著時間的流逝從左向右移動。shell從用戶讀入字符串"ls"。shell建立一個新的進程,然后在那個進程中運行l(wèi)s程序并等待那個進程結(jié)束:
然后shell讀取新的一行輸入,建立一個新的進程,在這個進程中運行程序 并等待這個進程結(jié)束。
所以要寫一個shell,需要循環(huán)以下過程:
- 獲取命令行
- 解析命令行
- 建立一個子進程(fork),防止打擾主程序的運行
- 替換子進程(execvp),來執(zhí)行對應功能。
- 父進程等待子進程退出(wait)
根據(jù)這些思路,和我們前面的學的技術,就可以自己來實現(xiàn)一個shell了
2 項目實現(xiàn)
為了保證項目文件的優(yōu)雅美觀,我們按照功能來書寫不同函數(shù):
- 創(chuàng)建自己的命令行
- 獲取命令
- 分割命令
- 創(chuàng)建進程執(zhí)行命令
2.1 創(chuàng)建命令行
該模塊我們需要實現(xiàn)類似:
獲取這些信息大家應該都知道吧!通過對環(huán)境變量我們就可以獲取到這些信息。使用getenv()
函數(shù)就可以完成操作。
#include<stdio.h>2 #include<sys/types.h>3 #include<sys/wait.h>4 #include<stdlib.h>5 #include<unistd.h>6 #include<string.h>7 //大小宏8 #define SIZE 2569 //獲取用戶名10 const char* GetUsername() 11 {12 const char* name = getenv("USER");13 if(name == NULL) return "NONE";14 return name; 15 }16 //獲取機器信息17 const char* GetHostName()18 {19 const char* hostname = getenv("HOSTNAME");20 return hostname; 21 }22 //獲取當前目錄23 const char* GetCwd()24 {25 const char* cwd = getenv("PWD");26 if(cwd == NULL) return "NONE";27 return cwd;28 }29 30 void MakeCommandLineAndPrint()31 { //設置命令行字符串32 char line[SIZE];33 const char* username = GetUsername();34 const char* hostname = GetHostName();35 const char* cwd = GetCwd();//將獲取的三個數(shù)據(jù)寫入命令行中 36 sprintf(line,"[%s@%s %s]> ",username,hostname,cwd); 37 printf("%s",line);38 fflush(stdout);//為了將命令行刷新出來39 }40 41 int main()42 {43 //創(chuàng)建我們自己的命令行44 MakeCommandLineAndPrint();45 int a = 0; scanf("%d",&a); //阻斷一下方便查看46 return 0;47 }
這里使用的sprintf()
函數(shù)是向流中寫入格式化信息的好工具。這一段函數(shù)大家都可以看明白,就是獲取三個變量,然后通過Line數(shù)組進行中轉(zhuǎn),然后打印出來。來看效果:
這時候發(fā)現(xiàn),我們的所在目錄全部都別打印出來了,我們可以進行一下優(yōu)化:
#define SkipPath(p) do{ p += strlen(p)-1 ;while(*p != '/') p--; p++; }while(0);
通過這個宏定義就可以只保留最后的目錄。
這里之所以不使用函數(shù),是因為使用函數(shù)會涉及二級指針,會比較復雜!!!
來看效果:
這樣就非常完美了!!!
2.2 獲取命令
這個模塊可以說是非常關鍵的一步了,只有正確獲取了對應命令,我們才好打開新進程來執(zhí)行命令。
#define ZERO '\0'45 int GetUserCommand(char* command,int n)46 {47 if(command == NULL) return -1;48 fgets(command,n,stdin);49 command[strlen(command) - 1] = ZERO; 50 return strlen(command);51 }
這樣我們就可以獲取命令行輸入的字符串了。
2.3 分割命令
獲取命令之后,我們還需要對輸入的一串命令來進行分割,來保證我們可以正常執(zhí)行命令
12 #define SEP " "...14 //全局命令 方便操作15 char* gArgv[NUM];...58 void SplitCommand(char command[] , size_t n)59 {60 (void)n;61 gArgv[0] = strtok(command,SEP);62 int index = 1;63 // done, 故意寫成=,表示先賦值,在判斷. 分割之后,strtok會返回NULL,剛好讓gArgv最后一個元素是NULL, 并且while判斷結(jié)束64 while((gArgv[index++] = strtok(NULL,SEP)));65 }
我們使用來strtok()
函數(shù):
char *strtok(char *str, const char *delim)
- str—要被分解的字符串
- delim—用作分隔符的字符(可以是一個,也可以是集合)在這里我們使用宏定義SEP( 代表 “ ” )
-
第一次調(diào)用strtok(),傳入的參數(shù)str是要被分割的字符串{aaa - bbb -ccc},而成功后返回的是第一個子字符串{aaa};
-
第二次調(diào)用strtok的時候,傳入的參數(shù)應該為NULL,這樣使該函數(shù)默認使用上一次未分割完的字符串繼續(xù)分割 ,就從上一次分割的位置作為本次分割的起始位置,直到分割結(jié)束。(strtok內(nèi)部會記錄相應信息)
這樣就成功分割命令,來看效果:
我們的準備工作做完了,接下來就可以進行最終的操作:創(chuàng)建新進程來執(zhí)行命令!
2.4 運行命令
運行命令就要使用:
- 創(chuàng)建子進程
- 進程替換
這兩個加在一起就有了非常牛批的力量,究極POWER!。
68 //執(zhí)行命令69 void ExecuteCommand()70 {//創(chuàng)建子進程71 pid_t id = fork(); 72 if(id == 0) 73 { //進程替換 74 execvp(gArgv[0],gArgv); 75 exit(errno); 76 } 77 else 78 { 79 int status = 0; 80 pid_t rid = waitpid(id,&status,0);//進程等待 81 if(rid > 0) 82 { //如果錯誤打印錯誤信息 83 int lastcode = WEXITSTATUS(status); 84 if(lastcode != 0) printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode); 85 } 86 } 87 }
前面已經(jīng)做好大部分工作了,執(zhí)行命令這一步就很簡單了。來看效果:
這樣就完成了絕大部分的代碼編寫。我們在加上一個while循環(huán),讓命令行一直運行試試:
這樣就實現(xiàn)了shell的大部分功能,但是還是有一些功能沒有做到:比如我們運行cd等內(nèi)建命令時,會無法運行,所以我要加上特殊情況來保證內(nèi)建命令可以執(zhí)行!!!
90 char* GetHome()91 {92 char* home = getenv("HOME");93 return home;94 }95 96 char cwd[SIZE];97 98 void cd()99 {
100 const char* path = gArgv[1];
101 if(path == NULL) path = GetHome();
102 chdir(path);
103
104 char temp[SIZE];
105 getcwd(temp,sizeof(temp));
106 snprintf(cwd,sizeof(cwd),"PWD=%s",temp);
107 putenv(cwd);
108 }
109
110 //檢查是否為內(nèi)建命令 并單獨執(zhí)行
111 bool CheckBuildin()
112 {
113 bool yes = false;
114 //if語句判斷即可,內(nèi)建命令是有限的
115 if(strcmp(gArgv[0],"cd") == 0)
116 {
117 cd();
118 yes = true;
119 }
120 return yes;
121 }
123 int main()
124 {
125 int quit = 0;
126
127 while(!quit)
128 {
129
130 //創(chuàng)建我們自己的命令行
131 MakeCommandLineAndPrint();
132
133 //獲取命令行信息
134 char usercommand[SIZE];
135 int n = GetUserCommand(usercommand,sizeof(usercommand));
136 if(n <= 0) return 1;
137
138 //分割命令行信息
139 SplitCommand(usercommand, sizeof(usercommand));
140
141 bool judge = CheckBuildin();
142 if(judge) continue;
143
144 //執(zhí)行命令
145 ExecuteCommand();
146 }
147
148
149 return 0;
150 }
這樣把內(nèi)建命令單獨進行運行就可以了,我這里只寫了一個cd
命令。來看效果:
這樣就完成了我們的自主shell編寫!!!
3 源代碼
#include<stdio.h>2 #include<sys/types.h>3 #include<sys/wait.h>4 #include<stdlib.h>5 #include<unistd.h>6 #include<string.h>7 #include<errno.h>8 #include<stdbool.h> 9 10 #define SIZE 25611 #define SkipPath(p) do{ p += strlen(p)-1 ;while(*p != '/') p--; }while(0); 12 #define ZERO '\0'13 #define NUM 3214 #define SEP " "15 16 //命令17 char* gArgv[NUM];18 int lastcode = 0;19 char cwd[SIZE];20 21 const char* GetUsername()22 {23 const char* name = getenv("USER");24 if(name == NULL) return "NONE";25 return name; 26 }27 28 const char* GetHostName()29 {30 const char* hostname = getenv("HOSTNAME");31 return hostname; 32 }33 34 const char* GetCwd()35 {36 const char* cwd = getenv("PWD");37 if(cwd == NULL) return "NONE";38 return cwd;39 }40 41 void MakeCommandLineAndPrint()42 {43 char line[SIZE];44 const char* username = GetUsername(); 45 const char* hostname = GetHostName();46 const char* cwd = GetCwd();47 SkipPath(cwd);48 sprintf(line,"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1?"/":cwd + 1);49 printf("%s",line);50 fflush(stdout);51 } 52 53 int GetUserCommand(char command[] ,size_t n)54 {55 char* s = fgets(command,n,stdin);56 if(s == NULL) return -1;57 command[strlen(command) - 1] = ZERO; 58 return strlen(command);59 }60 61 void SplitCommand(char command[] , size_t n)62 {63 (void)n;64 gArgv[0] = strtok(command,SEP);65 int index = 1;66 // done, 故意寫成=,表示先賦值,在判斷. 分割之后,strtok會返回NULL,剛好讓gArgv最后一個元素是NULL, 并且while判斷結(jié)束67 while((gArgv[index++] = strtok(NULL,SEP)));68 } 69 70 //執(zhí)行命令71 void ExecuteCommand()72 {73 pid_t id = fork();74 if(id == 0)75 {76 execvp(gArgv[0],gArgv);77 exit(errno);78 }79 else80 {81 int status = 0;82 pid_t rid = waitpid(id,&status,0);83 if(rid > 0)84 {85 lastcode = WEXITSTATUS(status);86 if(lastcode != 0) printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode);87 }88 } 89 }90 91 char* GetHome()92 {93 char* home = getenv("HOME");94 return home;95 }96 97 98 void cd()99 {
100 const char* path = gArgv[1];
101 if(path == NULL) path = GetHome();
102 chdir(path);
103
104 char temp[SIZE];
105 getcwd(temp,sizeof(temp));
106 snprintf(cwd,sizeof(cwd),"PWD=%s",temp);
107 putenv(cwd);
108 }
109
110 //檢查是否為內(nèi)建命令 并單獨執(zhí)行
111 bool CheckBuildin()
112 {
113 bool yes = false;
114 //if語句判斷即可,內(nèi)建命令是有限的
115 if(strcmp(gArgv[0],"cd") == 0)
116 {
117 cd();
118 yes = true;
119 }
120 return yes;
121 }
122
123 int main()
124 {
125 int quit = 0;
126
127 while(!quit)
128 {
129
130 //創(chuàng)建我們自己的命令行
131 MakeCommandLineAndPrint();
132
133 //獲取命令行信息
134 char usercommand[SIZE];
135 int n = GetUserCommand(usercommand,sizeof(usercommand));
136 if(n <= 0) return 1;
137
138 //分割命令行信息
139 SplitCommand(usercommand, sizeof(usercommand));
140
141 bool judge = CheckBuildin();
142 if(judge) continue;
143
144 //執(zhí)行命令
145 ExecuteCommand();
146 }
147
148
149 return 0;
150 }