一、前言
來(lái)制作一個(gè)簡(jiǎn)易的 [Shell 命令]行解釋器。
首先這是與 Shell 的互動(dòng)::
用下圖的[時(shí)間軸]來(lái)表示事件的發(fā)生次序。其中時(shí)間從> > 左向右。shell 由標(biāo)識(shí)為 sh 的方塊代表,它隨著時(shí)間的流逝從左向右移動(dòng)。shell 從用戶讀入字符串 "ls"。shell 建立一個(gè)新的進(jìn)程,然后在那個(gè)進(jìn)程中運(yùn)行 ls 程序并等待那個(gè)進(jìn)程結(jié)束。
然后 shell 讀取新的一行輸入,建立一個(gè)新的進(jìn)程,在這個(gè)進(jìn)程中運(yùn)行程序 并等待這個(gè)進(jìn)程結(jié)束。所以要寫(xiě)一個(gè) shell,需要循環(huán)以下過(guò)程:
1. 獲取命令行
2. 解析命令行
3. 建立一個(gè)子進(jìn)程(fork)
4. 替換子進(jìn)程(execvp)
5. 父進(jìn)程等待子進(jìn)程退出(wait)
二、準(zhǔn)備工作
1.輸出提示符
這里的提示字符為用戶名 @主機(jī)名 當(dāng)前路徑# 直接打印出來(lái)作為提示所用
printf("用戶名@主機(jī)名當(dāng)前路徑#");
這里沒(méi)有 n,會(huì)有緩沖區(qū)的問(wèn)題,類(lèi)似于我們之前所說(shuō)的進(jìn)度條所遇到的問(wèn)題,可以用 fflush(stdout) 刷新緩沖區(qū)。
2. 輸入和獲取命令
輸入
我們需要輸入一連串命令,其中可能出現(xiàn)空格,所以不能使用 gets 函數(shù),需要用到 fgets 函數(shù),同時(shí),可以定義一個(gè) lineCommand[NUM] 數(shù)組
#defineNUM1024 charlineCommand[NUM]; char*s=fgets(lineCommand,sizeof(lineCommand)-1,stdin); assert(s!=NULL);
但是打印的時(shí)候卻多換了一行,這是我們把 n 也讀取到了,直接進(jìn)行處理即可, 清除最后一個(gè) n
lineCommand[strlen(lineCommand)-1]=0;
可以通過(guò)打印看看效果和測(cè)試是否有 BUG
printf("test:%s ",lineCommand);
獲取
輸入之后,我們自然需要去進(jìn)行獲取,我們需要分割命令行,這個(gè)地方用 strtok。把字符串切割成若干個(gè)子串:
strtok: 第一次直接傳遞參數(shù),第二次則必須傳 NULL。且在最終 strtok 會(huì)返回 NULL。
3.shell 運(yùn)行原理
同時(shí),在理解一下 shell 的運(yùn)行原理:shell 內(nèi)部提取命令行做分析,然后調(diào)用 exec. shell 執(zhí)行命令必須通過(guò)創(chuàng)建子進(jìn)程,如果不創(chuàng)建子進(jìn)程會(huì)把我們所有的 shell 全部替換,所以執(zhí)行命令時(shí)一般磁盤(pán)上的程序必須創(chuàng)建子進(jìn)程。
4. 內(nèi)建命令
我們?cè)谶\(yùn)行自己寫(xiě)的 shell 的時(shí)候,發(fā)現(xiàn)輸入 cd … 輸入 cd path 等命令時(shí)發(fā)現(xiàn)路徑并沒(méi)有改變!
沒(méi)有發(fā)生改變是因?yàn)樽约簩?xiě)的 shell 執(zhí)行很多命令都要 fork() 創(chuàng)建子進(jìn)程,讓子進(jìn)程執(zhí)行的 cd,子進(jìn)程有自己的工作目錄,所以更改的子進(jìn)程的目錄,子進(jìn)程執(zhí)行完畢,繼續(xù)用的是父進(jìn)程,既 shell,并沒(méi)有影響父進(jìn)程,所以并沒(méi)有改變。
對(duì)于 cd, 我們可以采用內(nèi)建命令:不需要?jiǎng)?chuàng)建子進(jìn)程執(zhí)行,讓 shell 自己執(zhí)行命令,稱(chēng)為內(nèi)建命令。本質(zhì)就是執(zhí)行系統(tǒng)接口,我們可以調(diào)用一個(gè)系統(tǒng)接口 chdir,可解決上述問(wèn)題:
5. 替換
采用 execvp 進(jìn)行替換進(jìn)程
pid_tid=fork(); assert(id!=-1); if(id==0) { execvp(myargv[0],myargv); exit(1); }
三、整體代碼
#include #include #include #include #include #include #include #defineNUM1024 #defineOPT_NUM64 charlineCommand[NUM]; char*myargv[OPT_NUM];//指針數(shù)組 intlastcode=0; intlastsig=0; intmain() { while(1) { //1.輸出提示符 printf("lj@VM-8-2-centos當(dāng)前路徑#"); fflush(stdout); //2.獲取用戶輸入的命令,輸入的時(shí)候,用戶最后還輸入了 char*s=fgets(lineCommand,sizeof(lineCommand)-1,stdin); assert(s!=NULL); (void)s;//避免Linux認(rèn)為s變量未使用,導(dǎo)致警告 //清除最后一個(gè) ;例如:abcd lineCommand[strlen(lineCommand)-1]=0; //printf("test:%s ",lineCommand); //"ls-a-l-i"-->字符串分割-->"ls""-a""-l""-i" myargv[0]=strtok(lineCommand,""); inti=1; if(myargv[0]!=NULL&&(strcmp(myargv[0],"ls")==0)) { myargv[i++]=(char*)"--color=auto"; } //如果沒(méi)有子串了,strtok會(huì)返回NULL,即myargv[end]=NULL while(myargv[i++]=strtok(NULL,"")); //如果是cd命令,不需要?jiǎng)?chuàng)建子進(jìn)程,讓shell自己執(zhí)行對(duì)應(yīng)的命令,本質(zhì)就是執(zhí)行系統(tǒng)接口 //像這種不需要讓我們的子進(jìn)程來(lái)執(zhí)行,而是讓shell自己執(zhí)行的命令—內(nèi)建命令 //其中echo是一個(gè)自建命令 if(myargv[0]!=NULL&&(strcmp(myargv[0],"cd")==0)) { if(myargv[1]!=NULL)chdir(myargv[1]); continue; } if(myargv[0]!=NULL&&myargv[1]!=NULL&&(strcmp(myargv[0],"echo")==0)) { if(strcmp(myargv[1],"$?")==0) { printf("%d,%d ",lastcode,lastsig); } else { printf("%s ",myargv[i]); } continue; } //利用條件編譯測(cè)試是否成功 #ifdefDEBUG for(inti=0;myargv[i];++i) { printf("myargv[%d]:%s ",i,myargv[i]); } #endif //執(zhí)行命令 pid_tid=fork(); assert(id!=-1); if(id==0) { execvp(myargv[0],myargv); exit(1); } intstatus=0; pid_tret=waitpid(id,&status,0); assert(ret>0); (void)ret; lastcode=(status>>8)&0xFF; lastsig=status&0x7F; } return0; }
審核編輯:湯梓紅
-
Linux
+關(guān)注
關(guān)注
87文章
11314瀏覽量
209807 -
命令行
+關(guān)注
關(guān)注
0文章
77瀏覽量
10404 -
Shell
+關(guān)注
關(guān)注
1文章
366瀏覽量
23411 -
進(jìn)程
+關(guān)注
關(guān)注
0文章
203瀏覽量
13964 -
解釋器
+關(guān)注
關(guān)注
0文章
103瀏覽量
6542
原文標(biāo)題:Linux 實(shí)現(xiàn)簡(jiǎn)易的 Shell 命令行解釋器
文章出處:【微信號(hào):良許Linux,微信公眾號(hào):良許Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論