Write a Shell in C 學習(二)
上一次簡單的談了怎樣從命令列讀取輸入並對輸入進行初步的處理。下一步就是所謂執行命令了。而命令大概又分成兩個部分,一是內建命令,二是執行外部程式。這篇主要講如喝執行內建命令。
我實現的內建命令有,cd,info,pwd,exit,search。值得一提的是,我把執行外部程式的命令ex也放到了內建命令中,只是為了方便管理。具體程式碼如下。
#define CWD_LENTH 64 /* Function Declarations for builtin shell commands: */ int ssh_cd(char **args); int ssh_info(char **args); int ssh_exit(char **args); int ssh_pwd(char **args); int ssh_search(char **args); /* List of builtin commands, followed by their corresponding functions. */ char *builtin_str[] = { "cd", "info", "exit", "pwd", "search", "ex" }; /* An array of function pointers. */ int (*builtin_func[]) (char **) = { &ssh_cd, &ssh_info, &ssh_exit, &ssh_pwd, &ssh_search, &ssh_launch }; int ssh_num_builtins() { return sizeof(builtin_str) / sizeof(char *); } /* Builtin function implementations. */ int ssh_cd(char **args) { if (args[1] == NULL) { fprintf(stderr, "ssh: expected argument to \"cd\"\n"); } else { if (chdir(args[1]) != 0) { perror("ssh"); } } return 1; } int ssh_info(char **args) { printf("XJCO2211 Simplified Shell by sc16h3s.\n"); return 1; } int ssh_exit(char **args) { return 0; } int ssh_pwd(char **args) { char cwd[CWD_LENTH]; getcwd(cwd,CWD_LENTH); printf("Current working directory is %s.\n",cwd); return 1; } int ssh_search(char **args) { if (args[1] == NULL){ printf("Please input values after 'search'.\n"); return 1; } char cwd[CWD_LENTH]; char **filenames = (char **)malloc(sizeof(char *) * 1024); char **filegets = (char **)malloc(sizeof(char *) * 1024); int number = 0; DIR *dirptr = NULL; struct dirent *entry; //get cwd. getcwd(cwd,CWD_LENTH); //open dir. if((dirptr = opendir(cwd)) == NULL){ perror("ssh"); exit(EXIT_FAILURE); } while(entry = readdir(dirptr)){ //printf("%d\n",entry->d_reclen); filenames[number] = entry->d_name; number++; } int i,j=0; for (i=0; i<number; i++){ int count = 0; int num = 1; //find the '.' or the end of filename. And store their position. while(filenames[i][count] != 0){ if (filenames[i][count] == 46){ break; } count++; } //compare names to aim. if correct, store them. while(args[1][num] == filenames[i][count]){ if (args[1][num] == 0){ filegets[i]=filenames[i]; break; } num++; count++; } } //print results. for (j=0; j<number; j++){ if(filegets[j] != NULL){ printf("%s\n", filegets[j]); } } return 1; }
這裡有很多部分,我一一解釋一下。第一段就是函式定義。把要用的函式先宣告。第二段是,就是在命令列輸入的字元指令。第三段是函式指標,這個格式真的是令人頭疼。。。真的,剛開始學指標這部分真的是把我折磨的頭皮發麻,每次以為自己懂了結果看到一段新的程式碼又覺得自己不懂了,,,我已經不敢亂說了,所以這段大家自行百度理解,反正在這裡這麼寫是對的,先這麼著吧。第四段就是為了算一下你有幾個內建指令,方便在後面用,這樣你隨便在裡面加,後面也不用改,自動就算出來了。理解也很簡單,在builtin_str裡,每個記憶體塊都佔char*個大小,所以除一下就知道有多少個了。最後一段,就是具體實現各個內建指令函數了。
前四個指令的實現真的很簡單了,看應該都能看懂,看不懂去百度三十秒搞定。接下來說一個我在教程外自己加的指令search,它可以以“search *.c”的格式搜尋當前路徑下所有的.c檔案。同理別的型別的檔案也可以搜尋,沒有後綴的也可以,寫成“search *”就行。主要是利用#include <dirent.h>實現。這個庫不在C的標準庫中(主要是用標準庫我tmd寫不出來),但是也是一個非常常用的庫。來,接下來我們一點一點看:
if (args[1] == NULL){ printf("Please input values after 'search'.\n"); return 1; }
這段好理解,就是如果search後面什麼都沒跟,那就重新進入迴圈,然後告訴使用者重新輸入。這個return 1要跟後面綜合部分的函式一起看,等我寫完我會把原始碼全部放出來。現在就知道在內建函式中,如果return 1,就是重新進入迴圈,如果return 0,那就是跳出迴圈,整個shell都會結束!
char cwd[CWD_LENTH]; char **filenames = (char **)malloc(sizeof(char *) * 1024); char **filegets = (char **)malloc(sizeof(char *) * 1024); int number = 0; DIR *dirptr = NULL; struct dirent *entry;
cwd是用來儲存當前路徑的。filenames用來放路徑下所有檔案的檔名。我假設資料夾下不會有超過1024個檔案,多了的話,我太菜了,以後再優化吧。filegets用來放搜尋出的符合條件的檔名。number用來計數有多少個檔案。 最後兩個是讀取檔案所需的函式,後面再說。
//get cwd. getcwd(cwd,CWD_LENTH);
//open dir. if((dirptr = opendir(cwd)) == NULL){ perror("ssh"); exit(EXIT_FAILURE); }
while(entry = readdir(dirptr)){ //printf("%d\n",entry->d_reclen); filenames[number] = entry->d_name; number++; }
第一段,讀取當前路徑並存在cwd中。第二段,用opendir函式開啟當前路徑,用dirptr代表(這句話說的不對,但為了新手能夠理解,我姑且如此說)。第三段將路徑下所有檔名讀取出來。entry = readdir(dirptr)會順序從路徑下讀取每個檔案的資訊,每執行一次就會跳到下一個檔案。entry->d_name用來讀取檔案的檔名,entry裡面資訊很多,這裡不一一列舉,需要的網上查。
for (i=0; i<number; i++){ int count = 0; int num = 1;
//find the '.' or the end of filename. And store their position. while(filenames[i][count] != 0){ if (filenames[i][count] == 46){ break; } count++; }
//compare names to aim. if correct, store them. while(args[1][num] == filenames[i][count]){ if (args[1][num] == 0){ filegets[i]=filenames[i]; break; } num++; count++; } }
//print results. for (j=0; j<number; j++){ if(filegets[j] != NULL){ printf("%s\n", filegets[j]); } }
return 1; } 這一段就是具體篩選目標檔名的地方!先說一下我的邏輯,把每一個檔名的字尾拿出來,如“.c”或“.txt”,和我們想選的如“.txt”來一位一位的對比,從第一位開始,一樣就跳到下一位繼續比,比到如果出現NULL,就證明全部相同了!此時就將對應的檔名存到filegets中。注意!為了方便,我沒用stack的結構在filegets中儲存資料,我直接將filenames的檔名存到對應位置的filegets中。舉例,如果filenames中是這樣,{“abc.c”,“abc.txt”,“ab.txt”,“main.c”...}我們要選的是.c檔案的話,filegets中就是,{“abc.c”,NULL,NULL,“main.c”...}。我們在輸出的時候,只要filegets中不是NULL的輸出出來就好了。所以我的程式碼,大迴圈用來遍歷每一個檔名,count用來記錄檔名跳到哪一位,num記錄目標檔名跳到哪一位。第二段,找到檔名的字尾。第三段,因為輸入的是“search *.XXX”,所以從args[1][num]之後記錄的就是我們要的,一一比對就好。在二三段要注意的一點是,filenames[x][y]或filegets[x][y]是數!char*型裡面的每一位都是數!儲存範圍用0~255,輸出時用ascii碼轉化成字元。所以用strcmp函式來比較相同與否是行不通的,直接找ascii碼用對應數字來判斷就好。最後輸出就完了。
如何執行外部程式,實現pipe和IO重定向放到下一篇一起講。