學習理解shell的好辦法--編寫本身的shell 之二

shell腳本的最簡單形式就是一串命令的羅列,shell充當解釋器,一條條挨個執行,直到最後一個或遇到退出命令。但這隻能作很簡單的事情,只是省去了每次都要敲一遍命令的時間,要想完成更負雜的功能,還要加上這些東西:
1.控制
前面的條件知足了,而後幹什麼;不知足,幹什麼。
2.變量
c=a+b, 用一種形式表明另外一種形式,就是變量。由於形式不一樣了,就能用一種不變的表示另外一種變化的。好比「編程語言」就能夠當一個變量,能夠賦值爲「C語言」,「Perl語言」,「Lisp」語言等。變量還可用緩衝思想理解,一個杯子,可暫存一杯水,一杯果汁,一杯酒,而這三個名稱,又可歸爲「液體」這個變量名
3.環境
其實白馬是屬於馬的,環境也是變量的一種。只不過它通常是由系統負責提供的,是爲了免去軟件每次運行都要設置一些變量的麻煩,好比設置語言什麼的,有了環境就直接能夠用這些變量了。常見的PATH,HOME. 命令env可看到你的系統的環境變量。

首先要作到工做是加入命令行解析,讓用戶能夠在一行中輸入命令和參數。解析器就輸入的一行拆分紅字符串數組,傳給子進程的execvp。

講一些信號的東西。信號是由單個詞組成的消息,用於進程間通訊,進程正在努力的跑着,你要和它說話,讓它退出、暫停,就得用信號。kill -l可查看信號列表。
進程處理信號有三種情形,一是默認處理,一般是消亡,這個調用signal(SIGINT,SIG_DFL)恢復SIGINT的默認處理;二是忽略,經過這個調用signal(SIGINT,SIG_IGN);三是調用一個函數signal(signum,function);

2) SIGINT 程序終止(interrupt)信號, 在用戶鍵入INTR字符(一般是Ctrl-C)時發出,用於通知前臺進程組終止進程

3) SIGQUIT
和SIGINT相似, 但由QUIT字符(一般是Ctrl-\)來控制. 進程在因收到SIGQUIT退出時會產生core(吐核)文件。

20) SIGTSTP
中止進程的運行, 但該信號能夠被處理和忽略. 用戶鍵入SUSP字符時(一般是Ctrl-Z)發出這個信號

在這一版shell中,處理SIGINT和SIGQUIT,在shell中忽略它倆,但在子shell中恢復他們的默認操做,由於這樣能夠在shell中要讓子進程的結束,而不至於把shell本身殺死。

shell的main函數以下:
/* chicken_sh.c
 * 破殼,小雞.
 * 增長了命令行處理,比egg_sh好用了
 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include "smsh.h"

#define DFL_PROMPT "> "

void setup();
void fatal(char *s1, char *s2, int n)


int main()
{
  char *cmdline, *prompt, **arglist;
  int result;
  
  prompt = DFL_PROMPT;
  setup();

  while ((cmdline = next_cmd(prompt, stdin)) != NULL) {
    if ((arglist = splitline(cmdline)) != NULL) {
      result = execute(arglist);
      freelist(arglist);
    }
    free(cmdline);
  }
  return 0;
}

void setup()
/* 設置信號處理函數
 */
{
  signal(SIGINT, SIG_IGN);
  signal(SIGQUIT, SIG_IGN);
}

void fatal(char *s1, char *s2, int n)
/* 錯誤處理函數
 */
{
  fprintf(stderr, "Error: %s, %s\n", s1, s2);
  exit(n);
}



函數解釋:
next_cmd  從輸入讀取下一個命令,用malloc分配內存以接受任意長度參數,碰到文件結束,返回NULL
splitline     解析參數. 將一個字符串分解爲字符串數組,並返回這個數組

chicken_sh 比較複雜些,因此代碼分紅三個文件 :chicken_sh.c, splitline.c, execute.c  .( 源碼下載)
這樣編譯:
gcc -o chichen_sh  chicken_sh.c splitline.c execute.c



其中的execute.c變化不大,只是增長了信號處理

splitline.c有點複雜,須要說明一下,代碼以下:
/*splitline.c
 * 爲chicken_sh讀取並解析命令
 *char *next_cmd(char *prompt, FILE *fp)  取下一條指令
 *char **splitline(char *str);            解析字符串
 */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "chicken_sh.h"

char *next_cmd(char *prompt, FILE * fp)
{
    char *buf;
    int bufspace = 0;
    int pos=0;		/* 當前位置 */
    int c;		

    printf("%s", prompt);
    while ((c = getc(fp)) != EOF) {
	/*若須要空間*/
	if (pos + 1 >= bufspace) {
	    if (bufspace == 0)	
		buf = emalloc(BUFSIZ);
	    else	
		buf = erealloc(buf, bufspace + BUFSIZ);	/* 擴大分配的內存 */
	    bufspace += BUFSIZ;	
	}

	/* 命令結束 */
	if (c == '\n')
	    break;

	/* 若是不結束,則添加進緩衝區 */
	buf[pos++] = c;
    }
    if (c == EOF && pos == 0)	
	return NULL;
    buf[pos] = '\0';
    return buf;
}


#define is_delim(x) ((x) == ' ' || (x) == '\t')    /*參數分隔符是空格或tab*/

char *newstr(char *s, int l);
char **splitline(char *line)
{
    char **args;	/*要返回的參數數組*/
    int spots = 0;	/*參數指針的容量*/	
    int bufspace = 0;	/*緩衝空間*/	
    int argnum = 0;	/*參數計數*/	
    char *cp = line;		
    char *start;
    int len;

    if (line == NULL)		/* 什麼輸入也沒有 */
	return NULL;

    args = emalloc(BUFSIZ);	/* 分配參數數組 */
    bufspace = BUFSIZ;
    spots = BUFSIZ / sizeof(char *);

    while (*cp != '\0') {
	while (is_delim(*cp))	
	    cp++;
	if (*cp == "\0")
	    break;
	/* 確保參數數組的空間 */
	if (argnum + 1 >= spots) {
	    args = erealloc(args, bufspace + BUFSIZ);
	    bufspace += BUFSIZ;
	    spots += (BUFSIZ / sizeof(char *));
	}

	/* 標記開始的地方,查找以\0 結束的位置 */
	start = cp;
	len = 1;
	while (*++cp != '\0' && !(is_delim(*cp)))
	    len++;
	args[argnum++] = newstr(start, len);
    }
    args[argnum] = NULL;
    return args;
}

/*
 * 構造字符串,以'\0' 結尾*/
char *newstr(char *s, int l)
{
    char *rv = emalloc(l + 1);

    rv[l] = '\0';
    strncpy(rv, s, l);
    return rv;
}

void freelist(char **list)
	/*參數用完後,釋放空間*/
{
    char **cp = list;
    while (*cp)
	free(*cp++);
    free(list);
}

void *emalloc(size_t n)
{
    void *rv;
    if ((rv = malloc(n)) == NULL)
	fatal("out of memory", "", 1);
    return rv;
}

void *erealloc(void *p, size_t n)
{
    void *rv;
    if ((rv = realloc(p, n)) == NULL)
	fatal("realloc() failed", "", 1);
    return rv;
}



emalloc和erealloc爲封裝上錯誤處理的分配空間的函數;
next_cmd接受用戶輸入的命令和參數並保存在一個鏈表中;
splitline負責將接受的參數拆分紅每一個參數分開的一個指針數組,空間都是動態分配的, 由於用戶輸入命令時,各個參數是以空格或tab隔開的,newstr把他們拆分紅字符獨立的字符串,以'\0'結尾.

編譯後的chicken_sh, 運行起普通命令來和正常shell沒兩樣了,而且能夠按ctrl+D退出。還能夠作這幾個改進:
1.一行多個命令,用分號隔開
2.後臺進程,即命令最後加上 "&"
3.能夠用exit命令退出,這樣能夠設置退出碼,像exit 1, exit 0

下面增長if..then控制語句
shell中的if和C中的if一個不一樣之處是,C中if做用在它後面一條語句或花括號包住的語句塊,而shell是用then開始,用"fi"表示if的結束,這樣對於解釋運行的程序流程設計來講是簡單的。
還有,if是基於命令以0退出表示成功的假設,好比:
if cat hello.c
then
echo hello
fi
若是cat命令執行成功,返回0,那麼then到fi的語句塊就會執行。注意,命令返回0和直接在if後面敲個0是不一樣的,若是想直接說明真假,能夠用true和false, if true就至關與if後面跟了個執行成功的命令,false反之。 太長了,下一篇再寫...
相關文章
相關標籤/搜索