--- title: '建構腳本 - 結構化之判斷' disqus: kyleAlien --- 建構腳本 - 結構化之判斷 === ## Overview of Content 讓腳本可以 **依照邏輯判斷去執行**;這一類命令會根據條件去跳躍執行,這樣的命令通就稱為 **結構化命令** (structured command) :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**Shell 腳本程式中的條件語句和高級特性 | Shell 結構化 &判斷**](https://devtechascendancy.com/shell-script-conditional-statements/) ::: [TOC] ## if-then 語句 如同在多個程式語言中使用的 `if` 判斷,其格式如下 ```shell= if command then commands fi # 或是 ----------------- if command ; then commands fi ``` ### if-then 的判斷 * `if-then` 語句的是用來判斷 command 的執行結果,這個 **執行結果就是退出碼 (exit status code)**,而退出碼成功的定義則是 0;所以 **command 執行完返回 0 則這個 `if-then` 就會判斷成立** :::warning * 這與一般程式的 if 語句判斷稍有不同,如果使用一般程式語言來說那我們就可改寫成如下 ```kotlin= // Kotlin, Java or C... if(command() == 0) { // do some commands } ``` 對應 Shell 判斷(上下範例是相同的判斷) ```shell= # Shell if command ; then fi ``` ::: * **`if-then` 範例**: 1. 判斷運行的指令成功、失敗 ```shell= #!/bin/bash # 成功,exit code 0 if ls -laF ; then echo "command done." fi # 失敗,exit code 非 0 if ABC ; then echo "command done2." fi ``` 運行結果 >  2. 使用多個指令 + 區域變數 ```shell= #!/bin/bash userName=alien # 多個命令 (cat, grep # 使用 `$` 引用區域變數 if cat /etc/passwd | grep -i $userName ; then echo "command done." fi ``` 運行結果 >  3. 嵌套 `if-then` 判斷 ```shell= #!/bin/bash if ls -laF ; then echo "command done." if ABC ; then echo "command done2." fi fi ``` 運行結果 >  ### if-then-else 語句 * `if-then-else` 語句重點是多了 else 處理,當 if 命令執行後的退出碼不為 0 時,else 內容就會執行;格式如下 ```shell= if command ; then commands else commands fi ``` * `if-then-else` 語句使用範例: ```shell= #!/bin/bash if Yoyo ; then echo "command done." else # 上面指令返回錯誤碼不為 0 時就執行 echo "command fail=($?)" fi ``` >  ### if-then-elif-then 語句 - 多判斷 * `if-then-elif-then` 語句可以解決 `if-then` 多嵌套導致腳本程式看起來不優雅的問題(多嵌套會導致波動拳);格式如下 > case 命令也可以解決 (下面小節會介紹) ```shell= if command1 ; then commands1 elif command2 ; then commands2 elif command3 ; then command3 fi ``` * `if-then-elif-then` 使用範例: 以下還多了 else 處理所有情況都不符合的狀況 ```shell= #!/bin/bash if grep "ALIEN" /etc/passwd ; then echo "ALIEN exists" elif grep "Alien" /etc/passwd ; then echo "Alien exists" elif grep "alien" /etc/passwd ; then echo "alien exists" else echo "No relate with alien" fi ``` >  ### 複合執行 - `&&` 前方必須成立 * **`&&` 執行**:多個條件 **都必須符合才算成立**(退出碼為 0 代表成功),格式如下 `<前方指令> && <後方指令>` **也就是說前方指令成功,後方指令才會運行**,範例如下: ```shell= #!/bin/bash if ls -laF && pwd; then echo "command done." else echo "command failure." fi ``` >  ### 複合執行 - `||` 前方不能成立 * **`||` 執行**:與 `&&` 執行相反,只有當前方指令執行失敗時(退出碼為非 0 代表失敗),後方指令才會執行,格式如下 `<前方指令> || <後方指令>` 範例如下: ```shell= cd e || echo "cd failed" ``` >  :::info * 善用這個特性,甚至不需要使用 `if` 判斷句,也可以控制結構流程 ```shell= ## --------------------------------------- # cd e || echo "cd failed" # 相當於如下程式 #!/bin/bash cd e 2> /dev/null if [ $? -ne 0 ]; then echo "cd failed" fi ``` ::: ## test 命令 前面我們說到 `if-then` 語句都是判斷退出碼(`exit status code`),如果 **要判斷退出碼之外的條件就必須使用 test 命令**;test 命令之後是 **判斷**,格式如下: ```shell= # 單獨使用 test 命令 test condition ``` `if-then` 語句使用 test 命令時,格式如下: ```shell= # 用法 1 if test condition ; then commands fi # 用法 2 if [ condition ] ; then commands fi ``` :::info * **如果 test 命令中的判斷成立,則 test 會退出並返回狀態碼 0** > test 命令後面的 condition 不寫時,則退出碼為非零; ```shell= #!/bin/bash if test ; then echo "empty test done." else echo "empty test fail=($?)" fi ``` >  :::success * `if-then` 語句除了使用 test 命令之外,還可以使用中括號 `[ ]` 替代 test 命令 ```shell= #!/bin/bash if [ "Hello" ] ; then echo "test done." else echo "test fail=($?)" fi ``` >  ::: ::: ### Condition - 數值比較 * 如果需要對於數字作比較(比大小、是否等於... 等等);下表列出了列出 test 命令比較數值的 options | 功能 | options | 使用 | | - | - | - | | 相等 | -eq | n1 -eq n2 | | 不等於 | -ne | n1 -ne n2 | | 大於等於 | -ge | n1 -ge n2 | | 大於 | -gt | n1 -gt n2 | | 小於等於 | -le | n1 - n2 | | 小於 | -lt | n1 - n2 | :::warning * test condition 只能處理整數比較,不能比較浮點數否則會失敗 ::: ```shell= #!/bin/bash varA=100 varB=1 if [ $varA -eq $varB ] ; then echo "A == B" elif [ $varA -lt $varB ] ; then echo "A <= B" elif [ $varA -le $varB ] ; then echo "A < B" elif [ $varA -gt $varB ] ; then echo "A >= B" elif [ $varA -ge $varB ] ; then echo "A > B" fi ``` >  ### Condition - 字符串比較 * **String 字串的比較是透過 ASCII Code 值比較**;下表列出了列出 test 命令比較數值的 options | 功能 | options | 使用 | | - | - | - | | 相同 | = | n1 = n2 | | 不相同 | != | n1 != n2 | | 小於 | < | n1 < n2 | | 大於 | **\\>** | n1 > n2 | | 字串長度非 0 | -n | -n n1 | | 字串長度 0 | -z | -z n1 | :::info * 字串 **大於** 需要加跳脫字元 `\`,**避免腳本誤認為重新導向符號** (會變成判斷輸出一個文件是否成功) ::: 1. **字串相等性比較**:字串的比較有大小寫之分 ```shell= #!/bin/bash if [ $USER = "alien" ] ; then echo "Account is alien." fi if [ $USER != "Alien" ] ; then echo "Account is not Alien." fi ``` >  2. **字串大小比較**:其比較的其實也是 ASCII Code 的數值 ```shell= #!/bin/bash # 注意跳脫符號 if [ "Alien" /> "alien" ] ; then echo "Alien > alien" else echo "Alien < alien" fi ``` > ASCII Code 值:`a` -> 96, `A` -> 65 > >  :::danger * `>` 要使用跳過符號(`\>`),否則會被認成重新導向 (你執行腳本後就產生一個 alien 檔案,並且比較還錯誤) > 如果認成重新導向,那 test 命令就會判斷重新導向的退出碼是否為 0,如果為 0 則 test 命令也會給外部退出碼 0 ::: >  3. **字符串是否存在**:判斷字串的操作符要放置在字串之前 > 未被定義的變量也可以判斷,不過會被判斷為空字串 ```shell= #!/bin/bash var1="Apple" var2="" if [ -n $var1 ] ; then echo "var1 not empty." fi if [ -z $var2 ] ; then echo "var2 is empty." fi # 未定義的變量也可以判斷 if [ -z $var3 ] ; then echo "var3 is empty." fi ``` >  ### Condition - 文件比較 * test 命令的判斷,在腳本中最常用的就是 **測試 Linux 文件系統上文件、目錄的狀態**;下表為常用的文件判斷相關 test 命令 | 功能 | options | 使用 | | - | - | - | | 是否是一個目錄 (directory) | -d | -d file | | 是否是一個檔案 (file) | -f | -f file | | 檔案是否存在 (exist) | -e | -e file | | 檔案是否可讀(readable) | -r | -r file | | 檔案是否可寫(writeable) | -r | -w file | | 檔案是否可執行 (exec) | -x | -x file | | 檔案是否非空 | -s | -s file | | 檔案是否屬於當前用戶 | -O | -O file | | 檔案是否與當前用戶相同群組 | -G | -G file | | 檔案新舊比較(新為主) | -nt | file1 -nt file 2 (檢查 file1 是否比 file2 新) | | 檔案新舊比較(舊為主) | -ot | file1 -ot file 2 (檢查 file1 是否比 file2 舊) | 這裡只寫幾個比較特別的檔案判斷 1. **文件所屬、群組判斷**: ```shell= #!/bin/bash if [ -O /etc/passwd ] ; then echo "$USER is the onwer of /etc/passwd file." else echo "$USER is not the onwer of /etc/passwd file." fi if [ -G $HOME ] ; then echo "$HOME group is same as $USER." else echo "$HOME group is not same as $USER." fi ``` >  2. **文件新舊比較**:直接寫文件名就可相互比較 :::danger 選項 `-nt`、`-ot` 不會判斷檔案是否存在,**如果檔案不存在它 -可能會返回錯的結果!!** ::: ```shell= #!/bin/bash if [ str_3.sh -nt str_4.sh ] ; then echo "The file of 'str_3.sh' is new than 'str_4.sh'" else echo "The file of 'str_3.sh' is not new than 'str_4.sh'" fi if [ str_4.sh -ot str_3.sh ] ; then echo "The file of 'str_4.sh' is old than 'str_3.sh'" else echo "The file of 'str_4.sh' is not old than 'str_3.sh'" fi ``` >  ### 非 Condition - `!` * 非 Condition 判斷只需要 **在 test 命令之前添加 `!` 即可進行非的判斷** ```shell= #!/bin/bash # 使用 `!` 進行非判斷 if [ ! -e $PWD/str_100.sh ] ; then echo "The file of '$PWD/str_100.sh' is not exist." fi ``` :::warning * `!` 之間必須使用空格格開 ::: >  ### 複合 test 條件 * 使用 test 命令也可以允許多個邏輯條件判斷,其使用方式如下 1. **OR 運算**:其中一個成立,就算成立 `[ condition1 ] || [ condition2 ]` > `||` 可以對應 test 命令的 option `-o`,也就是 `[ condition1 -o condition2 ]` 2. **AND 運算**:全部成立,就算成立 `[ condition1 ] && [ condition2 ]` > `&&` 可以對應 test 命令的 option `-a`,也就是 `[ condition1 -a condition2 ]` * 復合條件範例: ```shell= #!/bin/bash if [ -G $HOME ] && [ -O $HOME ] ; then echo "The $HOME is same group and owner of $USER." fi if [ -w /etc/group ] || [ -w /etc/passwd ] ; then echo "The /etc/group or /etc/passwd is writeable." fi ``` >  ## if-then 高級特性 bash shell 提供了幾個高級特性可以使用:^1.^ 數學表達式的雙括號、^2.^ 高級字串處理功能的雙方括號 ### 雙括號 - 針對計算 `(( ))` * 使用雙括號時的 `if` 判斷,就可以依照原先程式中的 `if` 判斷,也就是數值非 0 時才進入條件句(類似於開發用的程式的判斷),概念程式如下 ```shell= if (( 1 )); then echo "Hello 1." # 數值非 0, 所以會進入判斷 fi ``` * **雙括號可以提供更高級的數學運算式**(很像高級編程中的運算),其格式為:`(( experssion ))`;experssion 如下表 :::success * **雙括號內不用使用 `$` 符號 !! 就可引用外部變數~** * **雙括號內部會假設變數內容皆為「數值」**!可以用來判斷使用者輸入的數值,避免「字符的數字」與「數」搞混,可以用這個特性在比較判斷上 ::: | 功能 | 符號 | | - | - | | 後增 | val++ | | 後減 | val-- | | 先增 | ++val | | 先減 | --val | | Not 邏輯 | ~val | | 反位 | !val | | 幂運算 | val ** | | 左位移 | val >> | | 右位移 | val << | | 與運算 | val1 & val2 | | 或運算 | val1 \| val2 | | 邏輯與 | val1 && val2 | | 邏輯或 | val1 \|\| val2 | ```shell= #!/bin/bash var=10 if (( var ** 2 > 99 )) ; then echo "var squre big then 99." fi (( --var )) echo "var:$var" echo "var: $(( var >> 1 ))" ``` >  ### 雙方括號 - 針對字串 `[[ ]]` * 雙方括號則是針對字符串的高級特性,它除了 test 命令字串比較,還 **拓展了匹配模式**,其格式:`[[ expression ]]` :::warning * 並不是每個 Shell 都支持雙方括號(bash shell 支援) ::: ```shell= #!/bin/bash if [[ $USER == al* ]] ; then echo "Hello, $USER" else echo "Who are you?" fi ``` >  ## case-in 命令 如果有一組固定的答案,那使用 case 命令可以讓 Shell 看起來更整潔(大多是用在字符串配對時使用);其格式如下 > 使用 3 個符號 `)`、`;;`、`case/esac` ```shell= case variable in pattern1) commands1;; pattern2 | patter3) command2;; patter*) command3; *) default commands;; esac ``` ### case-in 使用 * 使用 case-in 命令取代 if-then 命令!(注意結尾使用雙分號 `;;`) ```shell= #!/bin/bash case $USER in Apple) echo "I got the Fruit";; Testing) echo "In Testing account";; alien | Alien) echo "Hello $USER";; *) echo "Other account...?";; esac ``` :::info * 其他非預設 case 可以使用 `*)` 來擷取 ::: >  ## Appendix & FAQ :::info ::: ###### tags: `Linux Shell`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up