首页 > 代码库 > 学习理解shell的好办法--编写自己的shell 之二

学习理解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反之。

太长了,下一篇再写...