Shell 中 ()、(())、[]、{} 的用法详解
文章目录
- 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 脚本中的“环境隔离”“数值计算”“条件判断”和“变量操作”,结合之前的字符串和运算符知识,就能写出逻辑清晰、不易出错的脚本了。