自己动手写Linux Shell(一) —— 简单的命令解释器
寒假在做Linux Kernel Project这本书上的习题,第二章的练习是写一个简单的shell,看了一下要求觉得这个练习很有价值,涉及到很多Linux C Programming的知识,所以准备认真地做一下。
- 最终的目标如下:
- 命令解释执行
- 支持后台执行(&)
- 支持输入输出重定向(<, >, >>)
- 支持管道IPC
- 内建命令cd, pwd, exit等
可见写一个shell并不是一件简单的事,从简单的一步一步做起吧,手头有APUE,一边做一边查。
一个简单的命令解释器
命令解释执行是shell最基本的功能,实现的方法很简单:从标准输入流中读入命令,然后exec一下就行了。但是还有很多琐碎的地方需要处理:
1.命令行参数传递
首先需要将输入的命令字符串按空格打断(strsep实在是太方便了),然后将打断的字符串构建成一个char*数组,通过execv的第二个参数传递给程序。
注:man exec可以得到关于exec函数族的详细说明。需要说明的是execlp和execvp会在PATH环境变量中的目录搜索可执行程序,而其他的exec函数族函数不会,如果不使用这两个函数,则需要自己编写代码搜索PATH环境变量。
2.使用fork建立子进程
直接在当前进程里exec的话,exec执行的程序结束后,整个程序也就结束了,因为exec直接将原来的进程上下文替换。所以需要fork一个新进程来执行命令,而父进程阻塞直到子进程结束后继续执行,这个可以通过wait函数实现。
3.处理命令的返回值
大多数的shell在命令程序返回非零值(异常退出)会打印出其返回值。而子进程的返回值可以在父进程里通过wait函数的第一个参数得到。然后通过一组宏可以方便地确定子进程的返回状态。这部分内容在APUE里有详细说明(8.6节),下面代码里的pr_exit函数基本上就是从APUE上抄过来的。
4.检查各个函数的返回值
Linux C Programming的一个原则就是在所有可能fail的地方加入检查代码。绝大多数C库函数和Linux系统函数都以负数返回值表示出错,并且通过C库的全局变量errno可以获得错误号,从而得到错误原因,并输出到标准错误流。由于整个过程动作固定,就用一个CHKERR宏来完成了。
下面是源代码
CODE BELOW ARE UNDER GPLV3 LISENCE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | /* By JackalDire, Jan 29 2010 * Tested on Linux Kernel 2.6.32, gcc 4.4.3 */ #include <unistd.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define LINE_MAX 8192 #define ARG_MAX 1024 #define ARG_NR_MAX 32 #define CHKERR(ret, msg) if (ret < 0) {\ fprintf(stderr, "ERROR : \"%s\", %s\n", \ msg, strerror(errno)); \ exit(-1); \ } char * args[ARG_NR_MAX + 1]; extern char ** environ; char line[LINE_MAX + 1]; void parse_command(char * cmd) { char * res; size_t cnt = 0; /* tokenize the command string by space */ while ((res = strsep(&cmd, " ")) != NULL) { printf("%s\n", res); args[cnt++] = strdup(res); } args[cnt] = NULL; } void pr_exit(const char * name, int status) { if (WIFEXITED(status)) // exit normally return; else if (WIFSIGNALED(status)) fprintf(stderr, "%s exit abnormally, signal %d caught%s.\n", name, WTERMSIG(status), #ifdef WCOREDUMP WCOREDUMP(status) ? " (core file generated)" : ""); #else ""); #endif else if (WIFSTOPPED(status)) fprintf(stderr, "child stopped, signal %d caught.", WSTOPSIG(status)); } int main(int argc, char * argv[]) { char c; size_t idx; int r; int status; while (1) { idx = 0; bzero(line, LINE_MAX + 1); c = fgetc(stdin); while (c && c != '\n') { line[idx++] = c; c = fgetc(stdin); } parse_command(line); r = fork(); CHKERR(r, "fork"); if (r == 0){ r = execvp(args[0], args); //printf("ret : %d\n", r); CHKERR(r, args[0]); } else { wait(&status); pr_exit(args[0], status); } } return 0; } |
一个简单的命令解释器就这样完成了,下面的工作就是添加后台执行功能,休息一会^ ^
转载前请先阅读:版权声明







郁闷……怎么搜索了半天没找到你说的这本书呢……
JackalDire 回复:
一月 31st, 2010 at 1:10 下午
Linux Kernel Project? 中文译名叫“Linux操作系统内核实习”
http://www.douban.com/subject/1222518/
书比较老了,用的还是2.2内核,反正也就是本习题册,也就无所谓了