首页 > 代码库 > 学习理解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(吐核)文件。
在这一版shell中,处理SIGINT和SIGQUIT,在shell中忽略它俩,但在子shell中恢复他们的默认操作,因为这样可以在shell中要让子进程的结束,而不至于把shell自己杀死。
shell的main函数如下:
next_cmd 从输入读取下一个命令,用malloc分配内存以接受任意长度参数,碰到文件结束,返回NULL
splitline 解析参数. 将一个字符串分解为字符串数组,并返回这个数组
chicken_sh 比较复杂些,所以代码分成三个文件 :chicken_sh.c, splitline.c, execute.c .( 源码下载)
这样编译:
splitline.c有点复杂,需要说明一下,代码如下:
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退出表示成功的假设,比如:
太长了,下一篇再写...
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
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; }
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
太长了,下一篇再写...
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。