---
# System prepended metadata

title: 2021-04-06  Chialiang86
tags: [Linux]

---

---
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
:::

## 測驗二

## 測驗三

## 測驗四