Write a Shell in C 學習(一)
自己的C很差很差,所以趁著這一次寫一個shell的作業來提高一下自己C的程式設計水平。
希望此筆記可以使初學者毫無阻礙的寫成一個shell,並對每一個句子知其所以然。
由於我本人亦是初學者,內容難免謬誤多多,望大神指導海涵。
要寫一個shell出來,同時作為一個C語言的程式,大概需要三個部分:初始化,迴圈,結束。由此我們可以寫出shell的主框架,如下:
int main(int argc, char **argv) { // Load config files, if any. // Run command loop. ssh_loop(); // Perform any shutdown/cleanup. return EXIT_SUCCESS; }
ssh_loop()為shell的主迴圈。
接下來具體寫主迴圈的內容。在主迴圈裡我們要寫些什麼呢?主要分成三個部分,讀取命令列,分析(切割)命令列,執行命令。
體現在程式碼中就是:
void lsh_loop(void) { char *line; //讀取的命令列 char **args; //命令列切割完成後得到的命令 int status; //執行狀態 do { printf("> "); line = ssh_read_line(); args = ssh_split_line(line); status = ssh_execute(args); free(line); //釋放記憶體 free(args); } while (status); }
接下來具體寫讀取命令列的部分。讀取命令列可以通過兩個函式來進行,一個是getchar(),一個是getline()。後者洩出來明顯要簡單許多,但是對於初學者來說,我覺得用第一種可以更加便於大家熟悉C。先放第二種用getline()的程式碼:
char *ssh_read_line(void)
{
char *line = NULL;
ssize_t bufsize = 0; // have getline allocate a buffer for us
getline(&line, &bufsize, stdin);
return line;
}
程式碼很簡單,主要是getline()的用法
#include <stdio.h> ssize_t getline(char **lineptr, size_t *n, FILE *stream);
其中*lineptr指向一個動態分配的記憶體區域。*n是所分配記憶體的長度。如果*lineptr是NULL的話,getline函式會自動進行動態記憶體的分配(忽略*n的大小),所以使用這個函式非常注意的就使用要注意自己進行記憶體的釋放。 如果*lineptr分配了記憶體,但在使用過程中發現所分配的記憶體不足的話,getline函式會呼叫realloc函式來重新進行記憶體的分配,同時更新*lineptr和*n。具體關於geline函式的更多內容詳見連線。【1】
這段程式碼中還可能造成疑惑的有ssize_t, 其實我也沒搞明白,,,我大概理解的就是一個類似於int的資料型別。
如果用getchar函式來寫的話,程式碼如下:
#define SSH_RL_BUFSIZE 1024
char *ssh_read_line(void)
{
int bufsize = SSH_RL_BUFSIZE;
int position = 0;
char *buffer = malloc(sizeof(char) * bufsize);
int c;
if (!buffer) {
fprintf(stderr, "ssh: allocation error\n");
exit(EXIT_FAILURE);
}
while (1) {
// Read a character
c = getchar();
// If we hit EOF, replace it with a null character and return.
if (c == EOF || c == '\n') {
buffer[position] = '\0';
return buffer;
} else {
buffer[position] = c;
}
position++;
// If we have exceeded the buffer, reallocate.
if (position >= bufsize) {
bufsize += SSH_RL_BUFSIZE;
buffer = realloc(buffer, bufsize);
if (!buffer) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
}
}
}
這段程式碼的邏輯是,先分配一塊動態記憶體buffer給字串(固定範圍,範圍為bufsize),然後判斷輸入字元正確與否和輸入是否結束,如果符合,跳出迴圈,反之為正常輸入,則將把字元加入到已分配好的記憶體(buffer)中。在每次輸入之後,判斷字串是否超過範圍,如果超過,則擴大記憶體範圍(對應程式碼是最後一段)。
這段程式碼涉及到的對初學者的難點在於指標的理解,和動態記憶體分配的使用。先說指標,可能直接上定義的話,對於初學者(我)來說,真的有點難以理解,所以在看了一些例子之後,我想用比喻結合一段程式碼來給大家解釋一下我的我對指標的理解。
#include <stdio.h>
const int MAX = 3;
int main () {
//新建陣列 int var[] = {10, 100, 200}; int i, *ptr[MAX];
//給指標陣列賦值
for ( i = 0; i < MAX; i++) { ptr[i] = &var[i]; /* 賦值為整數的地址 */ }
//第一個輸出 for ( i = 0; i < MAX; i++) { printf("Value of var[%d] = %d\n", i, var[i] ); } printf("\n");
//第二個輸出
for ( i = 0; i < MAX; i++) { printf("Address of value of var[%d] = %d\n", i, var+i ); } printf("\n");
//第三個輸出
for ( i = 0; i < MAX; i++) { printf("Value of var[%d] = %d\n", i, *ptr[i] ); } printf("\n");
//第四個輸出
for ( i = 0; i < MAX; i++) { printf("Address of value of var[%d] = %d\n", i, ptr[i] ); } printf("\n");
//第五個輸出
for ( i = 0; i < MAX; i++) { printf("Address of var[%d] = %d\n", i, (ptr+i) ); } printf("\n");
return 0; }
這這段程式碼中,我們首先定義了一個數組為var[3],裡面分別存放了10,100,200,三個數。我們把三個數想象成三個商品,對應的var[ ]想象成三個商品的包裝盒。然後再把ptr[ ]想象成三個裝包裝盒的箱子。符號代表的是,*代表包裝盒或箱子裡裝的東西,&代表現在所指的包裝盒或箱子。好,讓我們結合下面的結果來看程式碼對應的含義。
第一段程式碼輸出的是var[i]的結果,這個符號代表的是第i個包裝盒裡裝的東西。
Value of var[0] = 10 Value of var[1] = 100 Value of var[2] = 200
第二段程式碼輸出的是var+i的結果,這個符號代表的是第i個包裝盒的資訊。
Address of value of var[0] = 6356712 Address of value of var[1] = 6356716 Address of value of var[2] = 6356720
第三段程式碼輸出的是*ptr[i]的結果,先說ptr[i]代表的是第i個箱子裡裝的第i個包裝盒的資訊。加上*後代表第i個包裝盒(ptr[i])裡裝的商品。
Value of var[0] = 10 Value of var[1] = 100 Value of var[2] = 200
第四段程式碼同上,為第i個箱子裡裝的第i個包裝盒的資訊。
Address of value of var[0] = 6356712 Address of value of var[1] = 6356716 Address of value of var[2] = 6356720
第五段程式碼輸出的為第i個箱子的資訊。
Address of var[0] = 6356684 Address of var[1] = 6356688 Address of var[2] = 6356692
這樣說,就可以大致對指標的指向關係有了個比較初步的印象,應該足夠理解這個shell所需要的指標知識。具體的關於指標的講解請百度,網上可太多了,講的也都很好。
再談一下malloc。先說只有指標才用malloc。為什麼需要用malloc?一,需要用的記憶體太大了。二,因為要用的記憶體大小不固定。(這也是用指標的原因,當然用了指標一般要用malloc)
再說一下malloc的用法。例:
char *p = NULL;
p = (char *)malloc(sizeof(char) * 100);
首先malloc返回的是void *,所以要在malloc前加一個強制轉換,C語言中其實不用,但是C++必須要,為了方便,都加上好了。這兩句的含義是,分配100個連續的char型單元,將其首地址儲存在指標變數p中。
瞭解了指標和malloc之後,理解程式碼已經夠了,想要更詳細的內容請百度。
還有可能造成疑惑的有exit(EXIT_FAILURE)這個更簡單啦,exit就是推出小迴圈,後面的EXIT_FAILURE是引數,代表失敗了。同時還有EXIT_SUCCESS等其它引數,在網上都能查到。