Shell 中 ()、(())、[]、{} 的用法详解

文章目录

  • [Shell 中 ()、(())、[]、{} 的用法详解](#Shell 中 ()、(())、[]、{} 的用法详解)
    • 一、先明确:四类符号的核心功能定位
    • 二、逐个拆解:用法、示例与避坑点
      • [1. `()`:子 Shell 执行,隔离环境](#1. ():子 Shell 执行,隔离环境)
        • [核心用法1:子 Shell 执行命令,隔离变量](#核心用法1:子 Shell 执行命令,隔离变量)
        • [核心用法2:`()\` 捕获命令输出(命令替换)](#核心用法2:`()` 捕获命令输出(命令替换))
      • [2. `(())`:算术扩展,专注数值计算与比较](#2. (()):算术扩展,专注数值计算与比较)
        • [核心用法1:整数计算(替代 `expr`)](#核心用法1:整数计算(替代 expr))
        • [核心用法2:算术条件判断(替代 `[ ]` 中的 `-gt`/`-lt`)](#核心用法2:算术条件判断(替代 [ ] 中的 -gt/-lt))
      • [3. `[]`:条件测试(等价于 `test` 命令)](#3. []:条件测试(等价于 test 命令))
      • [4. `{}`:变量范围界定 + 代码块](#4. {}:变量范围界定 + 代码块)
    • 三、总结:四类符号的"场景选择指南"

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 执行命令,隔离变量
bash 复制代码
# 当前 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:$() 捕获命令输出(命令替换)

这是 () 最常用的场景,替代反撇号 ````` 实现"执行命令并获取结果",支持嵌套:

bash 复制代码
# 场景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
bash 复制代码
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 等符号):

bash 复制代码
age=25
if ((age >= 18 && age <= 30)); then
    echo "年龄在 18-30 之间(青年)"  # 输出:年龄在 18-30 之间(青年)
fi

# 对比 [ ] 的写法(需用 -ge/-le 和 -a)
if [ $age -ge 18 -a $age -le 30 ]; then
    echo "年龄在 18-30 之间(青年)"
fi

避坑点(()) 仅支持整数,不支持小数(如 ((3.14 > 3)) 会报错);且不支持字符串比较(如 (( "a" == "b" )) 报错)。

3. []:条件测试(等价于 test 命令)

[]test 命令的简写形式,核心功能是条件测试 ,包括"字符串比较""数值比较""文件属性判断"三类场景,返回值为"0(成立)"或"非0(不成立)",需结合 if/&&/|| 使用。

先明确:[]test 的等价性
bash 复制代码
# 以下两条命令完全等价(判断文件 /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 ] → 成立

示例:

bash 复制代码
a=23
b=24
if [ $a -lt $b ]; then
    echo "$a 小于 $b"  # 输出:23 小于 24
fi
核心用法2:字符串比较

[] 中字符串比较需注意"是否加双引号"(避免空值导致语法错误):

运算符 含义 示例(str1="abc",str2="abd")
==/= 等于 [ "$str1" == "$str2" ] → 不成立
!= 不等于 [ "$str1" != "$str2" ] → 成立
-z 字符串长度为0(空) [ -z "$str1" ] → 不成立(str1非空)
-n 字符串长度非0(非空) [ -n "$str1" ] → 成立(str1非空)

示例:

bash 复制代码
username="admin"
# 判断用户名是否为 admin(加双引号避免 username 为空时报错)
if [ "$username" == "admin" ]; then
    echo "欢迎管理员登录"  # 输出:欢迎管理员登录
fi

# 判断字符串是否为空
empty_str=""
if [ -z "$empty_str" ]; then
    echo "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 ] → 不成立(无执行权限)

示例:

bash 复制代码
file="/etc/passwd"
if [ -f "$file" ] && [ -r "$file" ]; then
    echo "$file 是普通文件且有读权限"  # 输出:/etc/passwd 是普通文件且有读权限
fi
避坑点:[] 的语法严格性
  • 括号内前后必须有空格 (如 [ -f /etc/passwd ] 正确,[-f /etc/passwd] 报错);
  • 变量必须加双引号 (如 [ "$username" == "admin" ],避免变量为空时变成 [ == "admin" ] 语法错误);
  • 不能直接用 &&/||(需用 -a/-o 表示"与"/"或",如 [ -f "$file" -a -r "$file" ])。

4. {}:变量范围界定 + 代码块

{} 在 Shell 中有两种核心用法,需根据场景区分:

核心用法1:变量范围界定(最常用)

当变量名后紧跟其他字符(如数字、字母)时,用 {} 明确变量边界,避免 Shell 误判变量名:

bash 复制代码
product="Python"
# 需求:输出 "Python3.11"(变量 product 后紧跟 3.11)
# 错误:Shell 会把 $product3.11 当作一个变量(未定义,输出空)
echo "$product3.11"  # 输出:(空值)

# 正确:用 {} 明确变量是 product,后续 3.11 是普通字符
echo "${product}3.11"  # 输出:Python3.11

延伸 :结合字符串操作(你之前关注的内容),${} 还支持"子串提取""长度计算":

bash 复制代码
str="this is zjl"
echo "${#str}"        # 计算长度:输出 10
echo "${str:5:2}"     # 提取子串:从索引5开始取2个字符,输出 "is"
核心用法2:代码块(批量执行命令,不启动子 Shell)

{} 可包裹多个命令,作为"代码块"执行,特点是:

  • 不启动子 Shell,命令在当前 Shell 执行(变量修改会影响当前 Shell);
  • 语法严格:括号内最后一条命令必须加 ;,且 { 后必须有空格。

示例:

bash 复制代码
# 用 {} 执行代码块,修改当前 Shell 的变量
a=10
{
    a=20
    echo "代码块中 a:$a"  # 输出:代码块中 a:20
    b=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 脚本中的"环境隔离""数值计算""条件判断"和"变量操作",结合之前的字符串和运算符知识,就能写出逻辑清晰、不易出错的脚本了。