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

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()的用法

。我們要知道,標準的C語言中是沒有getline這個函式的,好像是在08年新加入的對gcc編譯器的擴充套件。定義為:

#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等其它引數,在網上都能查到。