第 9 章 · 02 条件判断:test、[ 与 [[ ]]


文档摘要

第 9 章 · 02 条件判断:test、[ 与 [[ ]] 本节摘要:脚本光会顺序执行还不够,它得学会「看情况办事」。本节讲 Shell 的条件判断三件套: 、 、 。前两者其实是同一个东西的两种写法( 就是 命令,只是末尾要求一个 ),而 是 Bash 内建关键字,比 更强更安全。你会学到三类判断:字符串比较( / )、数值比较( / / )、文件判定( / / / )。最关键的认知升级是: 是一条命令,不是语法括号——这意味着 后面和 前面必须有空格,变量不加引号就可能因为「空值导致参数消失」而报错。理解了 是命令、 是关键字,你就能解释它们所有的行为差异。 内容来源:综合 Shell 脚本知识与原项目「Linux 命令大全」整理,套用体系化模板。

第 9 章 · 02 条件判断:test、[ 与 [[ ]]

本节摘要:脚本光会顺序执行还不够,它得学会「看情况办事」。本节讲 Shell 的条件判断三件套:test[[[ ]]。前两者其实是同一个东西的两种写法([ 就是 test 命令,只是末尾要求一个 ]),而 [[ ]] 是 Bash 内建关键字,比 [ 更强更安全。你会学到三类判断:字符串比较(=/!=)、数值比较(-eq/-lt/-gt)、文件判定(-f/-d/-e/-r)。最关键的认知升级是:[ ] 是一条命令,不是语法括号——这意味着 [ 后面和 ] 前面必须有空格,变量不加引号就可能因为「空值导致参数消失」而报错。理解了 [ 是命令、[[ ]] 是关键字,你就能解释它们所有的行为差异。

内容来源:综合 Shell 脚本知识与原项目「Linux 命令大全」整理,套用体系化模板。

学习目标

阅读完本节,你应当能够:

  1. 解释 test[[[ ]] 三者的关系:test[ 是同一命令的两种写法,[[ ]] 是 Bash 内建关键字。
  2. 用字符串比较(=!=-z-n)判断相等、不等、空、非空。
  3. 用数值比较(-eq-ne-lt-gt-le-ge)做数值判断,并说清它和字符串比较的区别。
  4. 用文件判定(-f-d-e-r-w-s)检查文件类型与权限。
  5. &&||! 组合多个条件,区分 [ ] 中要用 -a/-o[[ ]] 中直接用 &&/||
  6. if/while/until 里正确嵌套条件,写出能跑的分支逻辑。

一、设计动机(为什么这样设计)

test[ 是同一个东西

很多人以为 [ 是「语法括号」,其实不是。[ 是一条货真价实的命令,它的真名是 test

test -f file.txt # 用 test 命令判断 [ -f file.txt ] # 用 [ 命令判断,效果完全一样

[ 就是 test 命令的一个别名,唯一的区别是:用 [ 时必须在末尾加一个 ] 作为最后一个参数(纯粹是为了视觉上配对,test 不需要)。两者返回同样的退出码:判断为真返回 0,为假返回非 0

关键概念:在 Shell 里,「真」是退出码 0,「假」是非 0。这和 C/Python 的「0 是假」正好相反。原因在第一章讲过:退出码 0 表示「成功」,命令成功了就被当成「条件成立」。

既然 [ 是命令,它就遵守命令的规则:参数之间必须用空格分开。所以 [ -f file.txt ][ 后、] 前都必须有空格,否则 Shell 不认。

[[ ]] 是 Bash 内建关键字,更强更安全

Bash 引入了 [[ ]],它不是命令,而是 Shell 解析阶段的「关键字」。这个区别带来三个优势:

# 优势 1:变量为空也不会报错([ ] 会) x="" [ $x = "" ] # 错!展开后变成 [ = "" ],参数个数不对,报错 [ "$x" = "" ] # 对!加引号才能用 [[ $x = "" ]] # 对![[ ]] 自动处理,变量不加引号也安全 # 优势 2:直接用 && || 组合([ ] 要用 -a -o) [ -f a -a -f b ] # 传统写法,-a 表示逻辑与 [[ -f a && -f b ]] # 现代写法,&& 更直观 # 优势 3:支持模式匹配(通配符) [[ $name == a* ]] # name 以 a 开头则为真(== 在 [[ ]] 里是模式匹配)

💡 技巧写 Bash 脚本时,条件判断一律用 [[ ]]。它比 [ 安全(变量为空不报错)、强大(支持模式匹配和正则)、可读性好(直接 &&/||)。只有写必须兼容 POSIX sh 的脚本时才退回 [

if 的本质:判断退出码

if 命令; then ... fi

if 后面跟的是一条命令,Shell 根据它的退出码决定走哪个分支。所以 if [ -f file ] 其实是 if 后面跟着 [ -f file ] 这条命令。同理:

if grep -q error log; then echo "有错"; fi # grep 找到匹配返回 0,if 就走 then if cd /tmp; then echo "进来了"; fi # 任何命令都能放进 if

理解了「if 判断的是命令的退出码」,你就掌握了 Shell 条件判断的全部精髓。

二、高频组合与实战

1. 字符串比较

a="hello" b="world" [[ $a == "hello" ]] # 相等 [[ $a != $b ]] # 不等 [[ -z "$a" ]] # 长度为 0(空字符串) [[ -n "$a" ]] # 长度非 0(非空)

⚠️ 注意:字符串比较用 = 还是 ==?在 [[ ]] 里两者都行,且 == 是模式匹配;在 [ ] 里 POSIX 只保证 === 是扩展。为了通用,[ ] 里用 =[[ ]] 里用 ==

2. 数值比较(一套独立的运算符)

数值比较不能用 ><==(那是字符串比较,而且 < > 在 Shell 里是重定向符!),要用专门的运算符:

运算符 含义 示例
-eq 等于 (equal) [[ $n -eq 5 ]]
-ne 不等 (not equal) [[ $n -ne 5 ]]
-lt 小于 (less than) [[ $n -lt 10 ]]
-gt 大于 (greater than) [[ $n -gt 10 ]]
-le 小于等于 [[ $n -le 10 ]]
-ge 大于等于 [[ $n -ge 10 ]]
count=5 if [[ $count -lt 10 ]]; then echo "还不到 10" fi # C 风格数值比较([[ ]] 独有,更直观) if (( count < 10 )); then echo "还不到 10" fi

💡 技巧(( )) 是算术上下文(注意不是 $(()),那是展开),里面可以直接用 < > ==,更像主流语言。if (( n > 10 )); thenif [[ $n -gt 10 ]]; then 更易读。纯数值比较优先用 (( ))

3. 文件判定

这是 Shell 脚本最常用的判断——文件存不存在、是什么类型、能不能读写。

运算符 含义
-e 存在(任何类型)
-f 存在且是普通文件
-d 存在且是目录
-r 存在且可读
-w 存在且可写
-x 存在且可执行
-s 存在且大小非 0
-L 存在且是符号链接
if [[ -f /etc/hosts ]]; then echo "配置文件存在" fi if [[ ! -d /var/log/myapp ]]; then mkdir -p /var/log/myapp fi # 经典:脚本必须有配置文件才能跑 config=/etc/myapp.conf if [[ ! -f $config ]]; then echo "找不到配置文件 $config" >&2 exit 1 fi

4. 组合条件:&&||!

# [[ ]] 里直接用 && || ! if [[ -f file && -r file ]]; then # 是文件且可读 cat file fi if [[ ! -e lockfile ]]; then # 锁文件不存在 echo $$ > lockfile fi # 逻辑运算的短路特性,单行写法(不用 if) [[ -f file ]] && echo "存在" # 条件真才执行后面 [[ -d dir ]] || mkdir dir # 条件假才执行后面(目录不存在就建)

cmd1 && cmd2 || cmd3 是常见的「三目」写法:cmd1 成功则跑 cmd2,否则跑 cmd3。但要小心 cmd2 失败会误触发 cmd3,复杂逻辑还是用 if

5. 模式匹配与正则([[ ]] 独有)

# 模式匹配(用 == 或 !=,右边不加引号才生效) [[ $file == *.txt ]] && echo "文本文件" [[ $name == a* ]] && echo "以 a 开头" # 正则匹配(用 =~) [[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "像 IP" [[ $email =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+$ ]] && echo "像邮箱"

⚠️ 注意:模式匹配时,右边的模式不能加引号[[ $x == "a*" ]] 会变成字面匹配字符 a*)。只有想匹配字面字符串时才加引号。

三、踩坑与排错

坑 1:[ 后面和 ] 前面忘加空格

if [-f file]; then # 错![-f 被当成一个命令名 if [ -f file ]; then # 对![ 后 ] 前都有空格

记住:[ 是命令,命令和参数之间必须有空格。

坑 2:[ ] 里变量不加引号,空值导致报错

x="" [ $x = "y" ] # 错!展开成 [ = y ],参数缺失,报错 "unary operator expected" [ "$x" = "y" ] # 对!展开成 [ "" = "y" ],正常 [[ $x = "y" ]] # 最对![[ ]] 不需要引号也安全

这是 [ ] 最大的坑。[ ] 时所有变量必须加双引号,或者干脆改用 [[ ]]

坑 3:数值比较误用 < >

[ 5 > 3 ] # 错!> 是重定向,会创建一个名为 3 的文件 [ 5 -gt 3 ] # 对!用 -gt [[ 5 -gt 3 ]] # 对 (( 5 > 3 )) # 对!算术上下文里 > 才是大于

坑 4:把赋值 = 和比较 = 搞混

if [ $a = $b ] # 这是字符串比较,不是赋值 if (( a == b )) # 数值比较用 == a=5 # 这才是赋值(if 外面)

=[]/[[]] 里是比较,在别处(不带空格)是赋值。语境不同,含义不同。

坑 5:if 后面忘了 then,或 then 没换行

if [[ -f file ]] then # 错!then 前要有 ; 或换行 if [[ -f file ]]; then # 对 if [[ -f file ]] then # 对 ... fi

本节要点回顾

  1. test[ 是同一个命令的两种写法[ 就是 test 加一个配对的 ],是外部命令,遵守命令规则(参数间必须空格)。
  2. [[ ]] 是 Bash 内建关键字,比 [ 更强:变量为空不报错、支持 &&/||、支持模式匹配与正则。写 Bash 脚本优先用 [[ ]]
  3. if 判断的是命令的退出码,0 为真、非 0 为假。if grep -q error log 这种写法很常见。
  4. 三类判断:字符串(=/!=/-z/-n)、数值(-eq/-lt/-gt 等)、文件(-f/-d/-e/-r 等)。
  5. 数值比较用 (( )) 更直观(( n > 10 ))[[ $n -gt 10 ]] 更像主流语言。
  6. 常见坑[ ] 内侧忘空格、[ ] 里变量不加引号致空值报错、数值比较误用 </>(被当重定向)。
  7. 模式匹配时右边的模式不能加引号[[ $x == a* ]] 匹配以 a 开头,[[ $x == "a*" ]] 则匹配字面 a*

条件判断让脚本有了「分支」。下一节我们给它加上「重复」——forwhileuntil 三种循环。for 用来遍历列表或文件,while 用来「只要条件成立就一直做」,untilwhile 的反面。循环配上 break/continue,脚本能处理的任务复杂度就上了一个台阶。


发布者: 作者: 灏天文库 转发
评论区 (0)
U