1. 程式人生 > >Write a Shell in C 學習(二)

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重定向放到下一篇一起講。