---
# System prepended metadata

title: MIT-Missing Semester Lecture 2
tags: [CS自學指南]

---

# MIT-Missing Semester Lecture 2: Shell Tools and Scripting (2020)
> 課程影片：[英文字幕](https://www.youtube.com/playlist?list=PLyzOVJj3bHQuloKGG59rS43e29ro7I57J) / [中文字幕](https://space.bilibili.com/518734451/channel/seriesdetail?sid=3320015) / [課程其他資源中文版](https://missing-semester-cn.github.io/)

## ■ shell腳本 ■

### ▋ 賦予變數、訪問變數值`($)`
```
foo=bar 
echo $foo //輸出bar
```
![](https://hackmd.io/_uploads/H1U8rZElp.png)

值得注意的點：
使用bash時，空格相當重要，主要是因為空格式用來分隔參數的符號，所以如果輸入`foo = bar`就會無法作用。

```
foo = bar
```
![](https://hackmd.io/_uploads/H1Py8bEgp.png)

### ▋ 雙引號、單引號
在單純字串中，單雙引號並不會有影響、差別，以下方為例。
```
echo "Hello" //輸出Hello
echo 'World' //輸出World
```
![](https://hackmd.io/_uploads/rJiJvWEl6.png)

但如果我們會用到「變數」，那麼使用單雙引號就會有差別了，如下所示，可以發現單引號包住變數的話，輸出時並不會幫忙轉譯，只有在雙引號之下的變數才成功被轉議程變數的值"bar"。
```
foo=bar
echo "Value is $foo." //輸出 Value is bar.
echo 'Value is $foo.' //輸出 Value is $foo.
```
![](https://hackmd.io/_uploads/B1iov-4g6.png)

### ▋ 定義function（利用vim建立、source指令定義）
在bash之中，你除了可以像一般程式語言一樣寫一些for loop、while loop等等外，最重要的是你還可以定義函數。

#### ▋ 利用vim建立

我們可以在終端上進入我們定義的函數：
```
vim mcd.sh
```
![](https://hackmd.io/_uploads/HkPbqWNeT.png)

他會進到vim編輯器中
![](https://hackmd.io/_uploads/S1opY-NlT.png)

按下i、a 或 o 這三個鍵其中一個進入 Insert 模式（如下圖），才能開始打字。之後我們輸入以下內容：
```
mcd () {
    mkdir -p "$1"
    cd "$1"
}
```
![](https://hackmd.io/_uploads/Bk6lqZNea.png)

上面的程式碼代表著一個mcd函式會執行哪些內容，他會先利用mkdir建立資料夾，而 `$1`代表腳本或函式獲取的第一個參數，接著進入腳本或函式獲取的第一個參數資料夾中。

bash使用了大量特殊變數來表示參數，錯誤碼與其他相關變數，可以參考[Advanced Bash-Scripting Guide:Chapter 3. Special Characters](https://tldp.org/LDP/abs/html/special-chars.html)。

以下為一些較常見的參數：
![](https://hackmd.io/_uploads/HJ3rRbEe6.png)


那定義好該函式之後，我們可以先按下 ESC 鍵或是 Ctrl + `[` 組合鍵，可退回至 Normal 模式。
![](https://hackmd.io/_uploads/ryo5sWExT.png)

接著輸入 :w ，按下enter會進行存檔。
![](https://hackmd.io/_uploads/SJ_0jW4ep.png)

之後輸入:q ，按下enter會關閉這個檔案，當然也可以直接輸入 :wq 進行存檔並關閉檔案。

#### ▋ source指令定義
建立好這個函式之後，我們要輸入以下內容，在shell中定義mcd函式。
```
source mcd.sh
```
![](https://hackmd.io/_uploads/S14-pWEla.png)

接著，我們就能利用mcd函式了，可以看出執行mcd後，確實幫我們建立了一個名為"whatMatterYouWantToCall"的資料夾，並且cd進入該資料夾中。
```!
mcd whatMatterYouWantToCall 
//whatMatterYouWantToCall 是參數，該參數會傳入我們先前打的"$1"
```
![](https://hackmd.io/_uploads/BkxFTZEep.png)

![](https://hackmd.io/_uploads/HJmZRZ4ea.png)

### ▋ 特殊變數
shell中使用了大量特殊變數來表示參數，錯誤碼與其他相關變數，可以參考[Advanced Bash-Scripting Guide:Chapter 3. Special Characters](https://tldp.org/LDP/abs/html/special-chars.html)。
![](https://hackmd.io/_uploads/rJUZyfEe6.png)

#### ▋ 特殊變數： !! bang bang
這裡尤其提到變數"!!"(bang bang <--很可愛的名字)，輸入`sudo !!`按下enter之後，他會完整的出現上一條指令（可能上一條指令因為權限錯誤導致失敗），這個時候利用`sudo !!`重新執行一次，也因為擁有sudo權限，所以這一次會請你輸入密碼，並且可以正確的執行原本想執行的指令。

#### ▋ 特殊變數： `$?`
`$?`是一個錯誤代碼，他可以告訴你上一段執行的指令中的錯誤，以下為範例。

```
echo "Hello" //輸出Hello，並沒有任何問題
echo $? //輸出0，代表沒有錯誤
```
![](https://hackmd.io/_uploads/H1VZbfVxT.png)

接著我們嘗試執行一段有錯誤的代碼，我們想在先前創建的mcd函式中尋找一個變數名為"foobar"（但是根本沒有這個變數嘛，所以心裡肯定早有得到錯誤的答案）。
```!
grep foobar mcd.sh 
//在先前創建的mcd函式中尋找一個變數名為"foobar"
echo $? //輸出1，因為上方出錯了，他根本找不到foobar
```
![](https://hackmd.io/_uploads/S1x1zG4xT.png)

而我們也可以從`$?`知道true以及false各自默認錯誤代碼為0跟1。
```
true
echo $? //輸出0

false
echo $? //輸出1
```
![](https://hackmd.io/_uploads/rkJUGfVla.png)

### ▋ 邏輯運算子： || &&

再來是邏輯運算子的部份，我覺得跟JavaScript的Logical Operator概念差不多。

可以參考[MDN的Logical operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_operators#logical_operators)或者前幾天念React時有讀到的[11-182. Conditional Rendering](https://hackmd.io/@noz915/Skc_aHtyp#11-182-Conditional-Rendering)。

#### ▋ 邏輯運算子： ||
or(||)，會回傳第一個為truthy value的內容。
```
false || echo "Oops fail"
//輸出Oops fail
```
![](https://hackmd.io/_uploads/Byy8EMVga.png)

```
true || echo "Will be not be printed"
//會直接回傳true，所以不會再執行後方的statement
```
![](https://hackmd.io/_uploads/SJKKEfNxp.png)

#### ▋ 邏輯運算子： &&
and(&&)，會回傳第一個為falsy value的內容或者回傳最後一個為truthy value的內容。
```
true && echo "Things went well"
```
![](https://hackmd.io/_uploads/B1C-rz4la.png)

```
false && echo "This will not print"
//會直接回傳false，不會再執行後方的statement
```
![](https://hackmd.io/_uploads/ry5OBfEla.png)

### ▋ 指令替換(command substitution) ：`$()`
使用 $( pwd ) 時， pwd 會被執行，然後用它的執行結果替換掉 $( pwd ) ，以下為範例。
```
foo=$(pwd)
echo $foo //輸出pwd執行後的內容，也就是印出當前所在位置
```
![](https://hackmd.io/_uploads/ry1zwzNeT.png)

所以結合前面的利用雙引號包住`$(pwd)`，將`$(pwd)`做轉譯，如下範例。
```
echo "We are in $(pwd)."
```
![](https://hackmd.io/_uploads/HJ_9wfNl6.png)

＃＃＃

接著我們再利用vim創建一個`example.sh`，並在裡頭輸入以下內容：
```c!
#!/bin/bash

echo "Starting program at $(date)" 
# date將會被替代為日期與時間

echo "Running program $0 with $# arguments with pid $$"
# $0是我們目前正在執行的腳本
# $#是我們給該命令的參數數量
# $$是正在運行該命令的process ID（又稱PID）

for file in "$@"; do
# $@是全部參數
    grep foobar "$file" > /dev/null 2> /dev/null
    # /dev/null被稱為黑洞文件
    # >是作為輸出的重新定向
    # 2>則是作為「錯誤」的輸出重新定向
    # 當字串沒有被找到，grep將會退出並返回狀態碼 1
    # 我們將標準輸出流(STDOUT)和標準錯誤流(STDERR)重新導向到Null，因為我們並不關心這些訊息
    if [[ $? -ne 0 ]]; then
    # -ne為not equal為一個比較運算子
        echo "File $file does not have any foobar, adding one"
        echo "# foobar" >> "$file"
    fi
done
```
![](https://hackmd.io/_uploads/Sy-EKf4xT.png)

接著執行`example.sh`，並在後方給予三個參數`mcd.sh`, `script.py`, `example.sh`。
```
source example.sh mcd.sh script.py example.sh
```
![](https://hackmd.io/_uploads/rkr3nfNl6.png)

然後因為先前創立的`mcd.sh`裡並沒有foobar這個變數，所以經過執行`example.sh`後，他會幫`mcd.sh`最後面加上註腳`#foobar`。
![](https://hackmd.io/_uploads/HkTVafEe6.png)


****

## ■ 習題與解答 ■

投影片：[Merge-Sort](https://docs.google.com/presentation/d/1_ssQLGKDk6IchOB0QBIqKYrQi2S_pq_sQfKN9ttY2qg/edit?usp=sharing)