# 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)