正好最近看了csapp,然后正好也借着这个机会补一补C语言,并且加深下自己的理解,于是开始动手自己写一个shell。
总体架构
总体架构其实非常非常简单,就是一个死循环,不停处理用户的输入,除非用户退出。所以在bash中,整体架构是这样子的:
- 初始化阶段。shell也会读取自己的配置文件,然后进行初始化。
- 解释阶段。shell会从标准输入、文件等地方获取命令并且执行。
- 终止阶段。shell执行关闭命令,并且释放内存,终止。
首先肯定从最简单的入手,那么只需要中间的解释阶段即可。
循环的实现
再次进行具体化,在循环里不停读取用户的输入,然后解析用户的输入,将其解析成对应的参数,最后执行就可以了,由此可以抽象出三个函数:
csh_read_line():获取用户的输入,其实有对应的c语言的封装可以非常容易获取用户输入的一行,不过这里打算还是老老实实写。
csh_split_line(line):将用户的输入解析成对应的args的数组。
csh_execute(args):真正执行用户的命令。
获取用户输入
由于不确定用户输入的一行到底有多长,所以我们需要使用malloc来动态分配内存。并且还需要注意,因为读到文件末尾之后会返回一个EOF,所以需要用int来接受(这点我不理解)。
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
| char *csh_read_line() { int bufsize = BUFSIZE; int position = 0; char *buffer = malloc(sizeof(char) * bufsize); int c;
if (!buffer) { fprintf(stderr, "csh: allocation error\n"); exit(EXIT_FAILURE); }
while (1) { c = getchar();
if (c == EOF || c == '\n') { buffer[position] = '\0'; return buffer; } else { buffer[position] = c; } position++;
if (position >= bufsize) { bufsize += BUFSIZE; buffer = realloc(buffer, bufsize); if (!buffer) { fprintf(stderr, "csh: allocation error\n"); exit(EXIT_FAILURE); } } } }
|
其实上面的这么多,用标准库的一个函数就可以解决了。
解析用户的输入
这一步为了简化,就是直接利用中间的空格作为分隔符来进行分割,而且这个简单的shell并不支持引号,所以如果是echo 'a b'这条命令,那么在正常的shell里面是会输出a b的,但是在我们的shell里,会输出'a和b'。
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
| char **csh_split_line(char *line) { int bufsize = BUFSIZE; int position = 0; char **tokens = malloc(bufsize * sizeof(char *));
char *token;
if (!tokens) { fprintf(stderr, "csh: allocation error\n"); exit(EXIT_FAILURE); }
token = strtok(line, DELIM); while (token != NULL) { tokens[position] = token; position++;
if (position >= bufsize) { bufsize += BUFSIZE; tokens = realloc(tokens, bufsize * sizeof(char *)); if (!tokens) { fprintf(stderr, "csh: allocation error\n"); exit(EXIT_FAILURE); } }
token = strtok(NULL, DELIM); } tokens[position] = NULL; return tokens; }
|
其实核心就是strtok这个函数,就是利用这个获取到参数的。
执行用户的命令
这一部分反而是最简单的,因为它只需要利用fork创建一个子进程,然后利用exec对应的函数进行处理就可以了。
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
| int csh_launch(char **args) { pid_t pid, wpid; int status;
pid = fork(); if (pid == 0) { if (execvp(args[0], args) == -1) { perror("csh"); } exit(EXIT_FAILURE); } else if (pid < 0) { perror("csh"); } else { do { wpid = waitpid(pid, &status, WUNTRACED); } while (!WIFEXITED(status) && !WIFSIGNALED(status)); }
return 1; }
|
OK,到这里一个最简单的shell就写完了。
CSAPP Lab