--- tags: Linux --- # 2021-04-06 Chialiang86 ## 測驗一 - 程式實做一個小型 Unix shell 風格的命令直譯器 - 功能如下 - 執行簡單的命令,如 vim, echo hello world, uname -a 等等 - 有限度地支援 UNIX pipeline,例如 ls /dev | wc -l - 提供檔案重新導向功能,例如先執行 hello > x 再執行 cat < x | grep hello,預期可見 hello 字樣 ### 考慮主要的函式 `run` 的實做 - pipe 實做 : 需提供不同 process 之間的溝通管道 - 使用 `pipe` 系統呼叫搭配 `dup2` 系統呼叫來實做。 - 當要呼叫 pipe 建立通道時,需要開啟兩個 [file descriptor](https://www.computerhope.com/jargon/f/file-descriptor.htm)、`int pipefds[2] = {0, 0}` 即為存放兩個 file descriptor 的資料結構、`pipefds[0]` 為 read end (讀取資料)、 `pipefds[1]` 為 write end (寫入資料),兩 process 要溝通時即可使用 `write` 系統呼叫,將資料寫進 pipe 中,接收端可用 `read` 來讀取 - dup2 : 在此的功能為將 pipe 所建立的兩個 file descripter 進行複製到標準輸入輸出中(例如 : dup2(pipefds[0], STDIN_FILENO) 即是將 pipe 的 read end 複製到標準輸入 stdin ) - redirection 實做 : 將系統呼叫產生的標準輸出及輸入導向其他檔案(例如 command > file1 就是將 command 命令產生的結果入 file1 中 ; command < file2 就是將 file2 當作標準輸入作為 command 命令的 input) ```c= /* Recursively run right-most part of the command line printing output to the * file descriptor @t */ static void run(char *c, int t) { // c 為命令字串的尾端,因為要遞迴優先執行右邊的命令,因此命令皆從尾端往回讀 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)) { /** * 移出新的空間來放置新的命令 * 若有多個命令要執行,例如 "ls -al" 會把 "-al" 和 "ls" 暫存,到 84 行 execvp(*u, u) 時會執行完整 ls -al 的命令 * **/ u--; // 指派新的空間 unsigned cnt = 1; char *buf = NULL; while(!is_special(*(--c))) cnt++; // 計算命令字串的長度 c++; /* Copy word of regular chars into previous u */ buf = (char *)malloc(cnt + 1); if(!buf) exit(0); strncpy(buf, c, cnt); // 將命令複製進入一個 buf *u = buf; // 把命令指派到命令陣列 v 的尾端 } if (is_redir(*c)) { /* If < or > */ if (*c == '<') redir_stdin = *u; // 為當作標準輸入的 input file 檔名字串開頭 else redir_stdout = *u; // 為當作標準輸出目標 output file 檔名字串開頭 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) { // *c 非 '\0' ,代表為 pipe 命令 pipe(pipefds); outfd = pipefds[1]; /* write end of the pipe */ } pid_t pid = fork(); if (pid) { /* Parent or error */ // parent process 當作 write 端 fatal(pid, 1); if (outfd) { // 要等待 output 端(有用到 pipe "|" 命令左邊的命令)將資料傳入 run(c, outfd); // 遞迴呼叫,將 c 作為新的命令尾端,且 pipe 命令 | 左邊的命令皆當作 pipe output 端,故將 outfd file descripter 作為 argument close(outfd); /* close output fd */ close(pipefds[0]); // read 用不到,直接關閉 } 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; } ``` ### 執行結果 1. 簡單命令: ls, cd 等 ```bash $ ./picosh $ ls a.cpp picosh picosh.c $ cd .. $ ls lab test07 ``` :::danger 文字訊息不要用圖片展現! :notes: jserv ::: 2. 測試 pipe, redirection - a.cpp 內容如下 ```cpp #include <iostream> using namespace std; int main(){ cout << "this is a dog" << endl; return 0; } ``` - 編譯 a.cpp 為 a 、將輸出導入 out.txt - 將 out.txt 結果印出 - 使用 ls /dev | wc -l 將 dev/ 目錄下的檔案個數回傳印出 ```bash $ ./picosh $ g++ -o a a.cpp $ ./a > out.txt $ ls a a.cpp out.txt picosh picosh.c $ cat out.txt this is a dog $ ls /dev | wc -l 234 ``` :::danger command 的翻譯是「命令」,instruction 的翻譯是「指令」,不要搞錯! :notes: jserv ::: ## 測驗二 ## 測驗三 ## 測驗四