---
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
:::
## 測驗二
## 測驗三
## 測驗四