# [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)
:::