# [2021q1](http://wiki.csie.ncku.edu.tw/linux/schedule) 第 7 週測驗題: 測驗 `1` > [測驗題目總覽](https://hackmd.io/@sysprog/linux2021-quiz7) :::info 本題目檢驗學員對 [lab0](https://hackmd.io/@sysprog/linux2021-lab0) 作業的認知,主要是命令直譯器的實作 ::: 考慮一個小型 [Unix shell](https://en.wikipedia.org/wiki/Unix_shell) 風格的實作,具備以下能力: * 執行簡單的命令,如 `vim`, `echo hello world`, `uname -a` 等等 * 有限度地支援 [UNIX pipeline](https://en.wikipedia.org/wiki/Pipeline_(Unix)),例如 `ls /dev | wc -l` * 提供[檔案重新導向](https://en.wikipedia.org/wiki/Redirection_(computing))功能,例如先執行 `hello > x` 再執行 `cat < x | grep hello`,預期可見 `hello` 字樣 這個 [Unix shell](https://en.wikipedia.org/wiki/Unix_shell) 風格實作的程式碼如下: (`picosh.c`) ```cpp #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <unistd.h> /* Display prompt */ static void prompt() { write(2, "$ ", 2); } /* Display error message, optionally - exit */ static void fatal(int retval, int leave) { if (retval >= 0) return; write(2, "?\n", 2); if (leave) exit(1); } /* Helper functions to detect token class */ static inline int is_delim(int c) { return c == 0 || c == '|'; } static inline int is_redir(int c) { return c == '>' || c == '<'; } static inline int is_blank(int c) { return c == ' ' || c == '\t' || c == '\n'; } static int is_special(int c) { return is_delim(c) || is_redir(c) || is_blank(c); } /* Recursively run right-most part of the command line printing output to the * file descriptor @t */ static void run(char *c, int t) { char *redir_stdin = NULL, *redir_stdout = NULL; int pipefds[2] = {0, 0}, outfd = 0; char *v[99] = {0}; char **u = &v[98]; /* end of words */ for (;;) { c--; if (is_delim(*c)) /* if NULL (start of string) or pipe: break */ break; if (!is_special(*c)) { c++; /* Copy word of regular chars into previous u */ XXXXX /* 在此提交你的程式碼 */ } if (is_redir(*c)) { /* If < or > */ if (*c == '<') redir_stdin = *u; else redir_stdout = *u; if ((u - v) != 98) u++; } } if ((u - v) == 98) /* empty input */ return; if (!strcmp(*u, "cd")) { /* built-in command: cd */ fatal(chdir(u[1]), 0); return; /* actually, should run() again */ } if (*c) { pipe(pipefds); outfd = pipefds[1]; /* write end of the pipe */ } pid_t pid = fork(); if (pid) { /* Parent or error */ fatal(pid, 1); if (outfd) { run(c, outfd); /* parse the rest of the cmdline */ close(outfd); /* close output fd */ close(pipefds[0]); /* close read end of the pipe */ } wait(0); return; } if (outfd) { dup2(pipefds[0], 0); /* dup read fd to stdin */ close(pipefds[0]); /* close read fd */ close(outfd); /* close output */ } if (redir_stdin) { close(0); /* replace stdin with redir_stdin */ fatal(open(redir_stdin, 0), 1); } if (t) { dup2(t, 1); /* replace stdout with t */ close(t); } if (redir_stdout) { close(1); fatal(creat(redir_stdout, 438), 1); /* replace stdout with redir */ } fatal(execvp(*u, u), 1); } int main() { while (1) { prompt(); char buf[512] = {0}; /* input buffer */ char *c = buf; if (!fgets(c + 1, sizeof(buf) - 1, stdin)) exit(0); for (; *++c;) /* skip to end of line */ ; run(c, 0); } return 0; } ``` 作答時,請列出 `static void run(char *c, int t)` 到 `if ((u - v) == 98) /* empty input */` 之間的程式碼,並儘量提供程式碼註解和說明你的考量。 :::success 延伸問題: 1. 解釋上述程式碼運作原理,可搭配研讀 [Tutorial: Write a Shell in C](https://brennan.io/2015/01/16/write-a-shell-in-c/) 2. 檔案 `picosh.c` 缺乏對 `>>` (append), `2>` (導向到 stderr), `&` (背景執行) 等語法的支援,請評估增加以上功能到 `picosh.c` 的修改幅度 3. 搭配 [User-Mode Linux 的實驗環境](https://hackmd.io/@sysprog/user-mode-linux-env),測試你修改過的 `picosh.c`,甚至取代 `init` 行程 > 注意: 可運用 [static linking](https://wiki.debian.org/StaticLinking) :::