文章目錄
- Shell 中 ()、(())、[]、{} 的用法詳解
- 一、先明確:四類符號的核心功能定位
- 二、逐個拆解:用法、示例與避坑點
- 1. `()`:子 Shell 執行,隔離環境
- 核心用法1:子 Shell 執行命令,隔離變量
- 核心用法2:`$()` 捕獲命令輸出(命令替換)
- 2. `(())`:算術擴展,專注數值計算與比較
- 核心用法1:整數計算(替代 `expr`)
- 核心用法2:算術條件判斷(替代 `[ ]` 中的 `-gt`/`-lt`)
- 3. `[]`:條件測試(等價于 `test` 命令)
- 先明確:`[]` 與 `test` 的等價性
- 核心用法1:數值比較(需用 `-gt`/`-lt` 等符號)
- 核心用法2:字符串比較
- 核心用法3:文件屬性判斷(常用場景)
- 避坑點:`[]` 的語法嚴格性
- 4. `{}`:變量范圍界定 + 代碼塊
- 核心用法1:變量范圍界定(最常用)
- 核心用法2:代碼塊(批量執行命令,不啟動子 Shell)
- 三、總結:四類符號的“場景選擇指南”
Shell 中 ()、(())、[]、{} 的用法詳解
在 Shell 腳本中,()
、(())
、[]
、{}
是功能差異極大的符號,分別對應“子 Shell 執行”“算術計算”“條件判斷”“變量范圍界定”等核心場景。本文結合你之前關注的字符串操作和運算符,系統拆解這四類符號的用法,幫你徹底理清適用邊界,避免混淆。
一、先明確:四類符號的核心功能定位
首先用一張表區分四類符號的“本質用途”,避免從語法細節陷入混亂:
符號 | 核心功能 | 典型使用場景 | 依賴環境 |
---|---|---|---|
() | 啟動子 Shell 執行命令,不影響當前 Shell | 臨時執行命令、隔離變量作用域 | 所有 Shell(bash、sh 等) |
(()) | 算術擴展,用于數值計算和比較 | 整數運算、算術條件判斷(如 a > b ) | Bash 專屬(不兼容 sh) |
[] | 條件測試(等價于 test 命令) | 字符串比較、數值比較、文件屬性判斷 | 所有 Shell |
{} | 1. 變量范圍界定;2. 代碼塊(需加 ; ) | 明確變量邊界(如 ${var}123 )、批量執行命令 | 所有 Shell |
二、逐個拆解:用法、示例與避坑點
1. ()
:子 Shell 執行,隔離環境
()
會啟動一個獨立的子 Shell 進程,在其中執行括號內的命令,特點是:
- 子 Shell 中的變量、環境變量修改不影響當前 Shell(隔離作用域);
- 命令執行結果可通過
$()
捕獲(即“命令替換”,等價于反撇號`
)。
核心用法1:子 Shell 執行命令,隔離變量
# 當前 Shell 定義變量 a=10
a=10
echo "當前 Shell 初始 a:$a" # 輸出:當前 Shell 初始 a:10# 用 () 啟動子 Shell,修改 a 的值
(a=20; echo "子 Shell 中 a:$a") # 輸出:子 Shell 中 a:20# 回到當前 Shell,查看 a 的值(未被修改)
echo "當前 Shell 最終 a:$a" # 輸出:當前 Shell 最終 a:10
避坑點:若想讓子 Shell 的變量影響當前 Shell,需用管道或重定向(如 (a=20; echo $a) > temp.txt
,再從文件讀取),但直接賦值無效。
核心用法2:$()
捕獲命令輸出(命令替換)
這是 ()
最常用的場景,替代反撇號 `
實現“執行命令并獲取結果”,支持嵌套:
# 場景1:獲取當前目錄下的文件數(ls 結果通過 wc -l 統計)
file_count=$(ls -l | wc -l)
echo "當前目錄文件數:$file_count"# 場景2:嵌套命令替換(先找 python 路徑,再查看路徑屬性)
python_attr=$(ls -l $(which python))
echo "Python 命令屬性:$python_attr"
優勢:反撇號 `
不支持嵌套(如 `ls `which python`
報錯),而 $()
完全支持,可讀性更強。
2. (())
:算術擴展,專注數值計算與比較
(())
是 Bash 專屬的“算術擴展”語法,專門處理整數運算和算術條件判斷,特點是:
- 支持所有算術運算符(
+
、-
、*
、/
、%
等),無需轉義*
(區別于expr
); - 支持算術比較(
>
、<
、>=
、<=
、==
、!=
); - 變量引用可省略
$
(如((a + b))
而非(( $a + $b ))
)。
核心用法1:整數計算(替代 expr
)
a=23
b=24# 1. 基礎運算:直接計算并輸出
echo "a + b = $((a + b))" # 輸出:a + b = 47
echo "a * b = $((a * b))" # 輸出:a * b = 552(無需轉義 *)# 2. 運算結果賦值給變量
c=$((a * b + 10)) # 23*24 +10 = 552+10=562
echo "c = $c" # 輸出:c = 562# 3. 自增/自減(類似其他編程語言)
((a++)) # a 自增 1(從23變為24)
echo "a 自增后:$a" # 輸出:a 自增后:24
核心用法2:算術條件判斷(替代 [ ]
中的 -gt
/-lt
)
在 if
語句中,(())
可直接判斷數值大小,語法比 [ ]
更簡潔(無需記 -gt
/-lt
等符號):
age=25
if ((age >= 18 && age <= 30)); thenecho "年齡在 18-30 之間(青年)" # 輸出:年齡在 18-30 之間(青年)
fi# 對比 [ ] 的寫法(需用 -ge/-le 和 -a)
if [ $age -ge 18 -a $age -le 30 ]; thenecho "年齡在 18-30 之間(青年)"
fi
避坑點:(())
僅支持整數,不支持小數(如 ((3.14 > 3))
會報錯);且不支持字符串比較(如 (( "a" == "b" ))
報錯)。
3. []
:條件測試(等價于 test
命令)
[]
是 test
命令的簡寫形式,核心功能是條件測試,包括“字符串比較”“數值比較”“文件屬性判斷”三類場景,返回值為“0(成立)”或“非0(不成立)”,需結合 if
/&&
/||
使用。
先明確:[]
與 test
的等價性
# 以下兩條命令完全等價(判斷文件 /etc/passwd 是否存在)
test -f /etc/passwd && echo "文件存在"
[ -f /etc/passwd ] && echo "文件存在" # 更常用的寫法
核心用法1:數值比較(需用 -gt
/-lt
等符號)
[]
中不能直接用 >
/<
(會被當作重定向符號),必須用專用的數值比較運算符:
運算符 | 含義 | 示例(判斷 a=23,b=24) |
---|---|---|
-eq | 等于 | [ $a -eq $b ] → 不成立(23≠24) |
-ne | 不等于 | [ $a -ne $b ] → 成立(23≠24) |
-gt | 大于 | [ $a -gt $b ] → 不成立(23<24) |
-lt | 小于 | [ $a -lt $b ] → 成立(23<24) |
-ge | 大于等于 | [ $a -ge $b ] → 不成立 |
-le | 小于等于 | [ $a -le $b ] → 成立 |
示例:
a=23
b=24
if [ $a -lt $b ]; thenecho "$a 小于 $b" # 輸出:23 小于 24
fi
核心用法2:字符串比較
[]
中字符串比較需注意“是否加雙引號”(避免空值導致語法錯誤):
運算符 | 含義 | 示例(str1=“abc”,str2=“abd”) |
---|---|---|
== /= | 等于 | [ "$str1" == "$str2" ] → 不成立 |
!= | 不等于 | [ "$str1" != "$str2" ] → 成立 |
-z | 字符串長度為0(空) | [ -z "$str1" ] → 不成立(str1非空) |
-n | 字符串長度非0(非空) | [ -n "$str1" ] → 成立(str1非空) |
示例:
username="admin"
# 判斷用戶名是否為 admin(加雙引號避免 username 為空時報錯)
if [ "$username" == "admin" ]; thenecho "歡迎管理員登錄" # 輸出:歡迎管理員登錄
fi# 判斷字符串是否為空
empty_str=""
if [ -z "$empty_str" ]; thenecho "empty_str 是空字符串" # 輸出:empty_str 是空字符串
fi
核心用法3:文件屬性判斷(常用場景)
[]
支持判斷文件是否存在、是否為目錄/普通文件等,是腳本中“文件操作前校驗”的核心語法:
運算符 | 含義 | 示例(判斷 /etc/passwd) |
---|---|---|
-f | 是否為普通文件 | [ -f /etc/passwd ] → 成立(是普通文件) |
-d | 是否為目錄 | [ -d /etc/passwd ] → 不成立(非目錄) |
-e | 文件/目錄是否存在 | [ -e /etc/passwd ] → 成立(存在) |
-r | 是否有讀權限 | [ -r /etc/passwd ] → 成立(root有讀權限) |
-w | 是否有寫權限 | [ -w /etc/passwd ] → 成立(root有寫權限) |
-x | 是否有執行權限 | [ -x /etc/passwd ] → 不成立(無執行權限) |
示例:
file="/etc/passwd"
if [ -f "$file" ] && [ -r "$file" ]; thenecho "$file 是普通文件且有讀權限" # 輸出:/etc/passwd 是普通文件且有讀權限
fi
避坑點:[]
的語法嚴格性
- 括號內前后必須有空格(如
[ -f /etc/passwd ]
正確,[-f /etc/passwd]
報錯); - 變量必須加雙引號(如
[ "$username" == "admin" ]
,避免變量為空時變成[ == "admin" ]
語法錯誤); - 不能直接用
&&
/||
(需用-a
/-o
表示“與”/“或”,如[ -f "$file" -a -r "$file" ]
)。
4. {}
:變量范圍界定 + 代碼塊
{}
在 Shell 中有兩種核心用法,需根據場景區分:
核心用法1:變量范圍界定(最常用)
當變量名后緊跟其他字符(如數字、字母)時,用 {}
明確變量邊界,避免 Shell 誤判變量名:
product="Python"
# 需求:輸出 "Python3.11"(變量 product 后緊跟 3.11)
# 錯誤:Shell 會把 $product3.11 當作一個變量(未定義,輸出空)
echo "$product3.11" # 輸出:(空值)# 正確:用 {} 明確變量是 product,后續 3.11 是普通字符
echo "${product}3.11" # 輸出:Python3.11
延伸:結合字符串操作(你之前關注的內容),${}
還支持“子串提取”“長度計算”:
str="this is zjl"
echo "${#str}" # 計算長度:輸出 10
echo "${str:5:2}" # 提取子串:從索引5開始取2個字符,輸出 "is"
核心用法2:代碼塊(批量執行命令,不啟動子 Shell)
{}
可包裹多個命令,作為“代碼塊”執行,特點是:
- 不啟動子 Shell,命令在當前 Shell 執行(變量修改會影響當前 Shell);
- 語法嚴格:括號內最后一條命令必須加
;
,且{
后必須有空格。
示例:
# 用 {} 執行代碼塊,修改當前 Shell 的變量
a=10
{a=20echo "代碼塊中 a:$a" # 輸出:代碼塊中 a:20b=30 # 定義變量 b,在當前 Shell 生效
} # 注意:若代碼塊在一行,需寫成 { a=20; echo $a; }# 回到當前 Shell,查看變量(a 和 b 都被修改/定義)
echo "當前 Shell a:$a" # 輸出:當前 Shell a:20
echo "當前 Shell b:$b" # 輸出:當前 Shell b:30
對比 ()
:()
啟動子 Shell,變量修改不影響當前;{}
不啟動子 Shell,變量修改影響當前——這是兩者作為代碼塊的核心區別。
三、總結:四類符號的“場景選擇指南”
最后用一張表幫你快速決策“什么場景用什么符號”,避免再混淆:
需求場景 | 推薦符號 | 示例 |
---|---|---|
臨時執行命令,隔離變量 | () | (a=20; echo $a) (a 不影響當前 Shell) |
捕獲命令輸出(命令替換) | $() | `file_count=$(ls -l |
整數計算/算術比較 | (()) | if ((age >= 18)); then ... |
條件測試(字符串/數值/文件) | [] | [ -f /etc/passwd ] 、[ "$str" == "abc" ] |
明確變量邊界/字符串操作 | ${} | ${product}3.11 、${#str} |
批量執行命令,影響當前 Shell | {} | { a=20; echo $a; } (a 影響當前 Shell) |
掌握這四類符號后,你可以更靈活地處理 Shell 腳本中的“環境隔離”“數值計算”“條件判斷”和“變量操作”,結合之前的字符串和運算符知識,就能寫出邏輯清晰、不易出錯的腳本了。