# Shell Script 簡易筆記
<br>
<br>
# 參考資料
> [快快樂樂學會讓電腦幫我做事](https://ithelp.ithome.com.tw/users/20005357/ironman/630)
>
> [shell script 教學](https://crmne0707.pixnet.net/blog/post/316830954-shell-script-教學)
>
> [簡明 Linux Shell Script 入門教學](https://blog.techbridge.cc/2019/11/15/linux-shell-script-tutorial/)
>
> [鳥哥的 Linux 私房菜](http://linux.vbird.org/linux_basic/0340bashshell-scripts.php)
>
> [Shell Script簡易教學](https://blog.twtnn.com/2013/12/shell-script.html)
>
感謝以上各位碩碩的提供的教學與筆記。
<br>
<br>
# Shell
還記得去年在準備資管所考試,考完當天被甩然後沒考上想要的學校又是另外一個故事了。
但重點不是這個。在上到作業系統的時候時常看到一張圖:

所有的電腦,不論是現在手上的 macbook 還是車裡的衛星導航(Microkernel),都是用以上這張圖的架構做出來的,硬體(hardware)的部分就不用多說,核心(kernel)負責主要的運算,而運算歸運算,要有輸入才能算ㄅ,算完也要能夠輸出,這個時候就是要由殼(shell)來將我們使用者所下達的指令翻譯成核心看得懂的程式碼,然後再將輸出翻譯成我們看得懂的樣子,這 94 shell ㄉ功能。
簡單來說就是使用者與 kernel 的橋樑啦!
啊! Terminal 是用來跟 Shell 互動的工具,他們兩個是合作的關係。當你下指令並按下 Enter 之後,Terminal 會負責把 指令的字串傳送給 Shell 執行,Shell 執行完會把純文字的結果回覆給 Terminal,Terminal 再根據字型、字體大小等等樣式把結果呈現出來。
<br>
## Shell Script
在許多的情況之下,我們都需要固定一組可以重覆或判斷資訊的指令,
而把這些指令存被在文字檔中,再交由 Shell 執行,就是 Script。其實就有點像是一般常用的 office 軟體裡的巨集,也就是一連串命令的集合,
一般會將 Shell Script 的副檔名命名為 .sh。
然後在命令提示字元將該檔案設定為可執行:
`chmod +x test.sh`
執行檔案:
`./demo.sh`
<br>
Shell Script 有以下幾個好處:
> 1. 自動化管理的重要依據
> 2. 追蹤與管理系統的重要工作
> 3. 簡單入侵偵測功能
> 4. 連續指令單一化
> 5. 簡易的資料處理
> 6. 跨平台支援與學習歷程較短
shell script 本身就算是個簡單的程式,差別在於不用編譯(compile),他是逐行執行的直譯器(interpreter)。
所以在 script 中也會有變數的宣告、有偵測輸入輸出的方式、有 if, else, for, do, swich..case, ... 等關鍵字,我們就來逐一介紹ㄅ。
<br>
But first, ~~let me take a selfie~~,在首行的部分會有
`!/bin/sh (或者是 #!/bin/bash)`
這裡的 #! 後面要定義的就是命令的解釋器(command interpreter)﹐如果是 /bin/bash 的話﹐那下面的句子就都用 bash 來解釋。
而好的 Script 撰寫習慣,就是要在檔頭以註解 `#` 加上 script 的名稱﹑用途﹑作者﹑日期﹑版本等。
<br>
### 基本輸入與輸出
1. 輸入
` read –p "提示" 變數資料` 程式執行時會在命令列出現提示字元以提示使用者要輸入的是什麼資料,同樣,輸入後的資料便存在該變數名稱中。
`read –t N 變數名稱` 來輸入,系統會限制使用者必須在N秒內輸入,超過N秒,則輸入無效。
2. 輸出
`echo $變數名稱`或`printf $變數名稱` 來輸出。由於 echo 指令內定會自動換行,使用 printf 這個指令來輸出字串就會直接接在後面。
3. 範例:
<br>
```bash=
read -p "Please enter your age:" Y_AGE
echo "你輸入的年齡是 $Y_AGE 歲"
```
<br>

<br>
## 變數
1. `變數名稱=值` 宣告變數或為變數賦值,等號兩端記得不能有空白!
<br>
2. `$變數名稱` 呼叫變數的值時,變數名稱之前要加上一個 `$` 符號。也有人會寫作 `${變數名稱}` ,而花括號主要是輔助了解變數的範圍。
<br>
> 若是值內有空白則需要使用 '' 或 "" 嘎包凱!
>
<br>
3. 刪除變數使用 `unset`
<br>
4. 預設的特殊變數如下:
> `$?` :表示上一個指令的離開狀況,一般指令正常離開會傳回 0。不正常離開則會傳回 1、2 等數值。
>
> `$1` :表示輸入的第一個參數,$2 則為第二個參數,依此類推。
>
> `$0` :shell script的檔名。
>
> `$@` :即代表 $1, $2,....直到所有參數結束。也就是說 $@ 代表了 “$1” “$2” “$3”….。
>
> `$*` :所有參數無間隔的連在一起,成為單一個參數。也就是說 $* 代表了 “$1 $2 $3…”
<br>
5. 字串:
> 字串兩旁並不用任何符號框住。宣告方式為:`變數名稱=字串`。
>
> 字串中不能包含`$`,會被當成呼叫變數用的符號。
>
> 「連續或單一個空白字元」會被當成「單獨一個空白字元」。
>
> 若要使字串中包含`'`,可用反斜線 `\` 跳脫。
<br>
6. 雙引號`""`中若含有變數`$var`,會先將變數轉換成其實際的值,單引號`''`則會將`$var`當成是一個值,而不會作轉換動作。
<br>
```bash=
echo "你們輸入的年齡是 $Y_AGE + $F_AGE 歲"
echo "你們輸入的年齡是 $((Y_AGE + F_AGE)) 歲"
```
<br>

<br>
<br>
## 運算
### 四則運算
整數的四則運算和求餘數,並非直接輸入程式碼就好。
若是直接輸入則會變成字串相加:
```bash=
n=1
m=2
echo $n+$m
```
則會輸出:
```
1+2
```
要執行運算式,其語法應為:`$((運算式))`:
```bash=
n=1
m=2
echo $((n+m))
```
才會輸出
```
3
```
在 Bash Shell 中內建原生不支援運算式,但我們可以使用 expr、awk 等指令來支援實現運算式。
<br>
```bash=
#!/bin/bash
read -p "Please enter your age:" Y_AGE
read -p "Please enter your friend's age:" F_AGE
sum1=$Y_AGE + $F_AGE
sum2=`expr $Y_AGE + $F_AGE`
sum3=$((Y_AGE + F_AGE))
echo "你們輸入的年齡是 $Y_AGE + $F_AGE 歲"
echo "你們輸入的年齡是 $((Y_AGE + F_AGE)) 歲"
#Bash Shell 中內建原生不支援運算式,所以無法顯示
echo "你們輸入的年齡是 $sum1 歲"
echo "你們輸入的年齡是 $sum2 歲"
echo "你們輸入的年齡是 $sum3 歲"
```

<br>
<br>
### 布林值
* 宣告變數為真:`變數名稱=true`
* 宣告變數為假:`變數名稱=false`
<br>
<br>
### 矩陣
宣告矩陣變數,語法如:`變數名稱=("一" "二" "三" …)`
每個元素間以空格做區分。
直接呼叫矩陣會顯示矩陣變數的首元素。
如:
```bash=
age=("34" "25" "29")
echo $age
```
執行後會顯示 34。
而使用`echo ${age[1]}`執行後就會依照`[]`中的次數顯示,此範例中會顯示 25,因矩陣索引由 0 開始計算。
<br>
<br>
## 條件判斷
### if & if else & if elif else:
在 Shell Script 中同樣可以使用 if..else 條件判斷,特別注意的是在 Shell Script 中使用 fi 為結尾(為 if 的倒寫法,同樣的接下來討論的 case 也有類似用法),代表條件判斷結束。== 為等於,!= 為不等於運算子。
若有多個條件需要判斷,可以使用 if elif else
記得比較條件需要放在 [] 中,前後要留空白
> -gt (greater than 縮寫)
> -lt (less than 縮寫)
> -ge (greater equal 縮寫)
> -le (less equal 縮寫)
> -eq (equal)
> -ne (not equal)
>
```bash=
#!/bin/bash
if [ $n -gt $m ]; then
echo "n > m"
else
echo "n is not > m"
fi
```
<br>
### case ... esac
若要使用類似一般程式語言的 switch 來處理多種條件判斷時,可以使用 case 來進行判斷:
```bash=
#!/bin/bash
m='MacDonald'
case $m in
MacDonald*) echo "Ronald McDonald"
;;
KFC*) echo "Harland David Sanders"
;;
TKK*) echo "阿勇"
;;
*) echo "You are really health!"
esac
```
### 迴圈
<br>
#### for
for 使用方法和一般程式語言類似,同樣可以針對條件使用 break、continue 來跳出或是跳過迴圈。
```bash=
#!/bin/bash
for loop in 1 2 3; do
echo "number: $loop"
done
```
<br>
for 還有另外一種寫法,與現在很多主流的程式語言很類似,也支援<、>、<=、>=
<br>
```bash
#!/bin/bash
echo -n "請問你要幾個檔案:"
read F
for ((i=1 ; i<=F ; i++))
do
touch $i.js
echo $i
done
echo "已經給你 $F 個檔案囉了!"
```
<br>
#### while
取自[Day11-迴圈 while 的三個範例](https://ithelp.ithome.com.tw/articles/10132603)
若是需要設定一個條件直到該條件為止,可以使用 while,但要注意避免無限迴圈狀況。而 while 有三種模式:
1. 在條件成立時,就會不斷執行迴圈內容
```bash=
#!/bin/bash
echo -n "請問你要幾個檔案:"
read F
INDEX=1
# 當條件成立,就會不斷執行(le表示小於或等於)
while [ $INDEX -le $F ]
do
touch $INDEX.js
echo -n "$INDEX"
# INDEX 的值會加1
(( INDEX++ ))
done
echo ""
echo "已經給你 $F 個檔案囉了!"
```
2. 無窮迴圈,直到外力介入才會停止
(在這次的範例中是以 Ctrl + C 來終止迴圈,下次的範例會用另一種方式來終止)
```bash=
#!/bin/bash
echo "按下 Ctrl + C 中斷…"
LENGTH=0
while :
do
echo -ne "\r["
sleep 0.2
while [ $LENGTH -le 10 ]
do
sleep 0.1
echo -n ">"
(( LENGTH++ ))
done
LENGTH=0
echo -en "\r "
done
```
3. 輸入一文字檔,在迴圈中一次只讀取一行:
首先要先準備一個檔案,以下程式是在每一行前加上行號
這次檔案是海豚刑警城市逃亡羅曼史ㄉ副ㄍㄍ詞,檔名是dophin.txt

```bash=
#!/bin/bash
echo -n "請輸入要讀取的文字檔名稱:"
read FN
INDEX=1
while read line
do
echo "イルカポリス: $line"
(( INDEX++ ))
done <$FN
```

<br>
#### until
直到某個條件結束可以使用 until 來進行,就像 while 的相反。
```bash=
#!/bin/bash
echo -n "請問你要幾個檔案:"
read F
counter=0
until [ $counter = $F ]; do
((counter++)) #放在後面會變無限迴圈
echo $counter
touch $counter.js
done
echo "已經給你 $F 個檔案囉了!"
```
## 函式
函式基本架構如下:
> 函式名稱(function 關鍵字為選擇性)
> 是否有傳入參數
> 函式內操作
> 是否有回傳值
>
函式的特殊用法如下:
> `$N` 來擷取第N個參數的值(N是整數,從1開始)。
>
> `$#` 可用來擷取參數的數目。
>
> `$*` 可擷取所有輸入參數。
>
> `shift` 指令可以解除第一個參數的設定,並使所有參數「往前靠攏」。此時 `$N` 的值會被原本 `$N+1` 的值所取代,`$#` 的值也會減 1,`$*` 也會改變。
```bash=
function get_mean()
{
ans=0
n=$# #將輸入參數的數目存入n
echo "the mean of "$*":" #輸出所有參數值
while [ $# -ne 0 ] #判斷輸入的參數是否小於等於零
do
ans=$(($ans+$1)) #擷取第1個參數
shift
#使所有參數向前靠攏,
#在本例中第一圈 $#-1 變 3; $N+1 變2; $* 從 1 變 6
done
ans=$(($ans/n)) #計算中位數
echo $ans
}
get_mean 1 6 10 32
```