# Shell Script
## 建立 Shell Script
- 建立副檔名為 *.sh* 的文字檔案
```shell
vim my_script.sh
```
- 在檔案最一開始,可以用註解指定在執行此腳本檔案時,要使用的 shell
```shell
#!/path/to/shell
```
- 如果沒有指定,會使用預設的 shell
- 指定使用 bash
```shell
#!/bin/bash
```
- 指定使用 dash
```shell
#!/bin/dash
```
- 查看可用的 shell
```bash
cat /etc/shell
```
- 在腳本檔案中,加上其他要被執行的指令
```bash
#!/bin/bash
echo "Hello, World!"
```
- 完成編輯後,對腳本檔案加上執行的權限
```bash
chmod +x my_script.sh
```
- 輸入檔案的完整路徑 (絕對或相對都可以) 可以執行腳本
```
./my_script
```
## Shell Script 基本語法
### 註解
```bash
# 註解的部分
```
### 一般變數
- 作用域僅在 script 內的變數
```bash
# 指派變數值
<name>=<val>
# 展開變數 (把變數的值插入到腳本中)
$<name>
```
- `=` **前後不能有空格**
- 字串內容可以不用引號,除非字串中有空格
```bash
str=Hello,World!
# 或是
str="Hello,World!"
```
- **使用變數**時,在變數名稱前加 `$`
```bash
#!/bin/bash
str=Hello,World!
echo $str
```
- 可以想成變數的值會被插入到腳本中的對應位置,類似巨集展開
- 使用變數時,可以加上大括號
- E.g., `${str}`
:::success
如果字串中有空格,必須加引號
```bash
str="Hello, World!"
```
:::
:::danger
下面是錯誤的寫法
```bash
str=Hello, World!
```
:::
- 變數可以當作指令的 arguments 或 flag
```bash
#!/bin/bash
COMMON_FLAGS="-O2 -lm"
gcc main.c $COMMON_FLAGS -o main
# 等同 gcc main.c -O2 -lm -o main
```
- 變數的內容可以是指令
```bash
#!/bin/bash
CC=mpicc
COMMON_FLAGS="-O2"
$CC main.c $COMMON_FLAGS -o main
# 等同 mpicc main.c -O2 -lm -o main
```
### 展開指令的執行結果
```bash
# 等同把 <command> 的執行結果插入到腳本中
$(<command>)
```
- **變數儲存** 某指令的執行結果
```bash
#!/bin/bash
res=$(ls /some/dir | grep .cpp)
echo $res
```
- 把某指令的執行結果,當作其他指令的 argument
```bash
#!/bin/bash
rm $(ls /some/dir | grep .cpp)
```
### 數學運算
```bash
((<expression>))
```
- 把數學式包在兩對括號中間可以執行數學運算
- 但只會進行運算,不會把運算結果展開
- 也不會輸出運算結果
- 要看到結果,可以用 `echo` 指令,並加上 `$` 把運算結果展開
```bash
echo $((1 + 2))
```
- 用變數儲存運算結果
```bash
#!/bin/bash
sum=$((1 + 2))
echo $sum
```
[Bash 中的 Operators](https://www.tutorialspoint.com/unix/unix-basic-operators.htm)
### Command Line Argument
- Shell 中用一些特別的變數表示 Command Line Argument
- `$#`: **Arguments 的數量**,不包含指令本身 (C 語言中的 argc - 1)
- 當執行 `./my_script a b c 123`
- `$#`: `4`
- `$<n>`: **第 n 個** argument
- 當執行 `./my_script a b c 123`
- `$1`: `a`
- `$2`: `b`
- `$3`: `c`
- `$4`: `123`
```bash
#!/bin/bash
# 輸出第一個 argument
echo $1
```
- `$@`: 一個陣列,**包含所有 arguments**
[How can I pass a command line argument into a shell script?](https://unix.stackexchange.com/questions/31414/how-can-i-pass-a-command-line-argument-into-a-shell-script)
### If Else
```bash
if [ <cond1> ]
then
# ...
elif [ <cond2> ]
then
# ...
else
# ...
fi
```
或是把 then 和 if 寫在同一行 (then 之前要有;)
```bash
if [ <cond1> ]; then
# ...
fi
```
- **注意**
- `else` 底下沒有 `then`
- **`[` 的後面**,**和 `]` 的前面**,一定要有空格
- 用 `=` 運算子判斷數值是否相同
- 如果比較的是字串,*習慣上*會把變數展開在 **一對引號中**
```bash
#!/bin/bash
if [ "$1" = "file" ] # 和字串比較,所以也把變數放在引號 "" 中
then
echo "Delete a file"
elif [ $1 = "process" ] # 變數可以不加引號
then
echo "Launch a process"
elif [ $1 = user ] # 也可以兩邊都不加引號
then
echo "Creat a use"
else
echo "Other ..."
fi
```
- 執行 `./my_script.sh file`,輸出 `Delete a file`
:::info
**常用運算子**
- 字串比較
- `=`: 比較字串是否相等,運算子的前後可以有空格
- `!=`: 比較字串是否不相等
- `-z`: 字串是否為空 (長度 0)
- `-n`: 字串是否不為空 (長度不是 0)
- 數字比較 (變數值可以轉換成數字時才能使用)
- `-eq`: 比較數字是否相等
- `-ne`: 比較數字是否不相等
- `-lt`: a < b
- `-le`: a <= b
- `-gt`: a > b
- `-ge`: a >= b
- 邏輯運算子
- 和 C 語言相同: `&&`, `||`, `!`
- 檔案測試
- `-f`: 測試檔案是否存在並且是一個普通檔案
- `-d`: 測試檔案是否存在並且是一個目錄
- `-e`: 測試檔案是否存在(通用測試)
- `-x`: 測試檔案是否可執行
:::
[Shell Script if / else 條件判斷式](https://www.ltsplus.com/linux/shell-script-if-else-elseif)
[[Shell Script]Day04-if else 判斷式](https://ithelp.ithome.com.tw/articles/10129897)
### Switch ... case ...
```bash
case <expression> in
<case1> )
# 當 expression = case1
# ...
;;
<case2> )
# 當 expression = case2
# ...
;;
* )
# 當 expression 等於任何字串
# ...
esac
```
- 可以簡化 if...else
```bash
#!/bin/bash
case "$1" in
"hello" )
echo "Hi!"
;;
"bye" )
echo "See you!"
;;
* )
# 等同 C 的 default
echo "???"
esac
```
[[Shell Script] Day14-可以提高if-then-else的 switch case](https://ithelp.ithome.com.tw/articles/10133727)
### For ... in ...
```bash
for <var> in <obj1> <obj2> <obj3> ...
do
# ...
done
```
- 依序輸出 1~5
```bash
#!/bin/bash
# 輸出 1 ~ 5
for num in 1 2 3 4 5
do
echo $num
done
```
- `{num1..num2}` 表示包含數字 `num1`~`num2` 的物件
```bash
#!/bin/bash
# 輸出 1 ~ 5
for num in {1..5}
do
echo $num
done
```
- 使用 `seq <num1> <num2>` 指令也有相同效果
```bash
#!/bin/bash
for num in $(seq 1 5)
do
echo $num
done
```
- 迭代所有 arguments
```bash
#!/bin/bash
# 輸出所有 argument 並且標上編號
i=0
for arg in $@
do
echo "$i - $arg"
((i++)) # 等同 C 的 i++
# 或是 i=$((i + 1))
done
```
[Bash For Loop Examples - Syntax](https://www.cyberciti.biz/faq/bash-for-loop/#Syntax)
### C-Style For Loop
```bash
for ((<init>; <cond>; <update>))
do
# ...
done
```
- 輸出 1 ~ 5
```bash
#!/bin/bash
for ((i=1; i<=5; i++))
do
echo $i
done
```
[Bash For Loop Examples - C-Style For Loop](https://www.cyberciti.biz/faq/bash-for-loop/#C_style_for_loop)
### While Loop
```bash
while [ <cond> ]
do
# ...
done
```
- 輸出 1 ~ 5
```bash
#!/bin/bash
i=1
while [ "$i" -le 5 ]
do
echo $i
((i++))
done
```
[[Shell Script] Day11-迴圈 while 的三個範例](https://ithelp.ithome.com.tw/articles/10132603)
### Functions
```bash
function fun() {
# do something
}
# 或是
fun() {
# do something
}
```
- 輸出 `Hello, World!`
```bash
#!/bin/bash
function hello() {
echo "Hello, World!"
}
hello
```
- 用 `$1`, `$2`, ... 取得傳入的參數
```bash
#!/bin/bash
function hello() {
echo "Hello, $1!"
}
hello user
```
- `return` 可以回傳值,但是只能回傳數字,回傳值存在變數 `$?` 中
```bash
#!/bin/bash
function addnum() {
return $(($1+$2))
}
addnum 2 3
echo $?
```
- 如果要回傳字串,可以透過 `echo` 或是 `printf` 等指令
```bash
#!/bin/bash
function addnum() {
echo $(($1+$2))
}
sum=$(addnum 2 3)
echo $sum
```
## 環境變數
### 查看所有變數
- `set` 會列出所有變數,包含**一般變數**和**環境變數**
- `env` 會列出所有**環境變數**
### 使用環境變數
```bash
$<env_var>
```
- 和一般變數一樣,在變數名稱前加 `$`
```bash
echo $USER
```
:::info
**常用環境變數**
- `USER`: 目前使用者名稱
- `HOME`: 使用者 home directory 的絕對路徑
- `HOSTNAME`: 目前機器名稱
- `SHELL`: 目前使用的 Shell
- `PWD`: 目前目錄的路徑
:::
### 設定環境變數
```bash
export <var>=<val>
```
- 在 `PATH` 中增加目前目錄
```bash
#!/bin/bash
# PATH 中每個路徑用 : 分隔
# 所以 $PATH 後要接一個 :
export PATH=$PATH:$PWD
```
- 增加自己的環境變數
```bash
#!/bin/bash
export MY_VAR=Hello
```
## 練習
### 閱讀他人的script
嘗試從中找到陌生的語法,並理解其用法意義
- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
- https://github.com/Xilinx-CNS/onload/blob/master/scripts/onload
- https://github.com/grpc/grpc-go/blob/master/vet.sh
- [shell脚本快速入门之-----shell脚本练习100例!!!](https://cloud.tencent.com/developer/article/1691052)