Shell 腳本中最易踩坑的莫過於比較操作:數值用 -eq 還是 ==?字符串用 = 還是 ==?[ ]、[[ ]]、(( )) 三者邊界在哪?本文系統梳理,附帶真實踩坑案例。
| 運算符 | 含義 | 語法環境 | 示例 |
|---|---|---|---|
-eq | 等於 | [ ]、[[ ]] | [ "$a" -eq "$b" ] |
-ne | 不等於 | [ ]、[[ ]] | [ "$a" -ne "$b" ] |
-gt | 大於 | [ ]、[[ ]] | [ "$a" -gt "$b" ] |
-ge | 大於等於 | [ ]、[[ ]] | [ "$a" -ge "$b" ] |
-lt | 小於 | [ ]、[[ ]] | [ "$a" -lt "$b" ] |
-le | 小於等於 | [ ]、[[ ]] | [ "$a" -le "$b" ] |
< | 小於 | (( )) | (("$a" < "$b")) |
<= | 小於等於 | (( )) | (("$a" <= "$b")) |
> | 大於 | (( )) | (("$a" > "$b")) |
>= | 大於等於 | (( )) | (("$a" >= "$b")) |
最佳實踐:純整數運算首選 (( )),支持 C 風格語法,可直接使用變量名 (( a < b )),無需 $ 符號,性能最好且不易出錯。
| 運算符 | 含義 | 語法環境 | 關鍵差異 |
|---|---|---|---|
= | 等於(POSIX 標準) | [ ]、[[ ]] | [ ] 中 = 做模式匹配時需引號 |
== | 等於(Bash 擴展) | [[ ]] 推薦;[ ] 同 = | [[ ]] 中 == 右側不加引號為 glob 模式匹配 |
!= | 不等於 | [ ]、[[ ]] | [[ ]] 中支持 glob 匹配 |
< | 字典序小於 | [[ ]];[ ] 需轉義 \< | 基於當前 locale 的字典序 |
> | 字典序大於 | [[ ]];[ ] 需轉義 \> | 同上 |
-z | 空串(長度 0) | [ ]、[[ ]] | [ -z "$str" ] 必須加雙引號 |
-n | 非空串 | [ ]、[[ ]] | [ -n "$str" ] 同理加引號 |
| 特性 | [ ] (test) | [[ ]] (Bash/Ksh) | (( )) (算術) |
|---|---|---|---|
| 標準 | POSIX | Bash/Ksh/Zsh 擴展 | Bash/Ksh/Zsh 擴展 |
| 模式匹配 | =/== 右側 glob,但易受 word splitting 干擾 | ==/!= 右側 glob,不 進行 word splitting/glob 展開 | 不支持 |
| 邏輯運算 | -a/-o(不推薦) | &&/|| 短路邏輯 | C 風格 &&/|| |
| 變量引用 | 必須 "$var" | 可裸用 $var(不分裂) | 可裸用 var(無需 $) |
| 算術運算 | 不支持 | 支持 + - 等 | 原生支持 |
| 正則匹配 | 不支持 | =~ 支持 ERE | 不支持 |
[ $a == z* ] vs [[ $a == z* ]]a="zebra" [ $a == z* ] # 錯誤!z* 被展開為當前目錄下以 z 開頭的文件名 [ "$a" == "z*" ] # 字符串字面比較,結果 false [[ $a == z* ]] # 正確!glob 匹配,結果 true(以 z 開頭)
[ ] 中var="" [ -n $var ] # 語法錯誤:-n 後無參數 [ -n "$var" ] # 正確:false [ $var ] # 同 -n,同樣報錯
export LC_ALL=C [[ "A" < "a" ]] # true (ASCII 碼 65 < 97) export LC_ALL=zh_CN.UTF-8 [[ "A" < "a" ]] # 可能不同,視 collate 規則
(( a < b ))[[ "$a" == "$b" ]] 或 [[ $a == pattern ]][[ "$a" < "$b" ]](注意 Locale)[[ -z "$s" ]] / [[ -n "$s" ]][ ],嚴格加雙引號,邏輯用多個 [ ] 組合。牢記「整數進雙圓括號,字符串進雙中括號,移植性用單中括號加引號」,90% 的比較錯誤可避免。寫腳本前先 set -euo pipefail,配合 shellcheck 靜態檢查,更穩健。