Linux Shell 条件测试与 if 语句全解析
Linux Shell 条件测试与 if 语句全解析文档
一、条件测试基础
1. 条件测试的核心类型
条件测试是 Shell 脚本中判断逻辑的基础,主要分为三大类,覆盖不同场景的判断需求:
测试类型 | 核心用途 | 典型场景 |
---|---|---|
整数测试 | 比较两个整数的大小关系(如等于、大于、小于) | 判断用户输入的年龄是否达标、比较文件数量是否超过阈值 |
字符测试 | 验证字符串的内容、为空/非空状态 | 检查用户名是否正确、判断配置文件中的参数是否为空 |
文件测试 | 检测文件/目录的属性(如存在性、类型、权限、新旧) | 判断备份文件是否存在、检查脚本是否有执行权限、比较两个文件的修改时间 |
2. 条件测试的表达式格式
Shell 支持三种等价的条件测试表达式格式,需严格遵循语法规则(尤其空格),否则会导致语法错误:
表达式格式 | 语法示例 | 关键注意事项 |
---|---|---|
test 条件表达式 | test -e /tmp/file | 老牌格式,兼容性强,需注意表达式内的空格 |
[ 条件表达式 ] | [ -e /tmp/file ] | 最常用格式,方括号与表达式之间必须有空格(如[ -e /tmp/file ] 正确,[-e /tmp/file] 错误) |
[[ 条件表达式 ]] | [[ -e /tmp/file ]] | Bash 扩展格式,支持正则匹配、逻辑运算符短路求值,空格规则同[ ] |
基础示例:验证文件是否存在
三种格式的执行效果完全一致,均通过$?
(上一条命令的退出码)判断结果(0 为真,非 0 为假):
# 1. test 格式
test -e /tmp/file && echo "文件存在" || echo "文件不存在"
# 2. [ ] 格式
[ -e /tmp/file ] && echo "文件存在" || echo "文件不存在"
# 3. [[ ]] 格式
[[ -e /tmp/file ]] && echo "文件存在" || echo "文件不存在"
关键概念:退出码与逻辑判断
- 0:条件成立(真),如文件存在时,
[ -e /tmp/file ]
的退出码为 0。 - 1-255:条件不成立(假),如文件不存在时,退出码为 1。
- 可通过
echo $?
直接查看上一条测试命令的退出码:[ -e /tmp/file ] # 假设文件不存在 echo $? # 输出 1(条件不成立) touch /tmp/file # 创建文件 [ -e /tmp/file ] echo $? # 输出 0(条件成立)
二、整数测试:比较整数关系
整数测试通过特定运算符比较两个整数(仅支持整数,不支持浮点数),核心运算符及示例如下:
运算符 | 含义 | 语法示例 | 说明 |
---|---|---|---|
-eq | 等于(equal) | [ $a -eq $b ] | 判断变量a 是否等于变量b |
-ne | 不等于(not equal) | [ $a -ne $b ] | 判断变量a 是否不等于变量b |
-gt | 大于(greater than) | [ $a -gt $b ] | 判断变量a 是否大于变量b |
-lt | 小于(less than) | [ $a -lt $b ] | 判断变量a 是否小于变量b |
-ge | 大于等于(greater or equal) | [ $a -ge $b ] | 判断变量a 是否大于或等于变量b |
-le | 小于等于(less or equal) | [ $a -le $b ] | 判断变量a 是否小于或等于变量b |
实战案例:整数比较脚本
案例 1:通过命令行传递参数比较整数
需求:脚本接收两个命令行参数,先验证参数数量和是否为整数,再比较大小。
#!/bin/bash
# 脚本名:int_compare.sh
# 功能:比较两个命令行参数的大小# 1. 验证参数数量是否为 2
if [ $# -ne 2 ]; thenecho "错误:必须输入两个参数!"echo "用法:$0 <整数1> <整数2>"exit 1 # 退出码 1 表示参数错误
fi# 2. 验证参数是否为整数(通过 expr 计算,非整数会报错)
#2>&1:将标准错误输出(如参数不是数字时的错误信息)也重定向到标准输出,最终同样被丢弃
expr $1 + $2 > /dev/null 2>&1 # 重定向输出到空设备,避免错误信息干扰
if [ $? -ne 0 ]; thenecho "错误:输入的参数必须是整数!"exit 1
fi# 3. 比较两个整数的大小
a=$1
b=$2
if [ $a -gt $b ]; thenecho "$a 大于 $b"
elif [ $a -eq $b ]; thenecho "$a 等于 $b"
elseecho "$a 小于 $b"
fi
执行效果:
chmod +x int_compare.sh # 添加执行权限
./int_compare.sh # 无参数,输出"必须输入两个参数"
./int_compare.sh 10 a # 非整数参数,输出"必须是整数"
./int_compare.sh 25 18 # 正常执行,输出"25 大于 18"
./int_compare.sh 10 10 # 正常执行,输出"10 等于 10"
案例 2:通过 read 读取输入比较整数
需求:通过交互方式读取两个整数,验证合法性后比较大小。
#!/bin/bash
# 脚本名:int_compare_read.sh# 1. 读取用户输入
read -p "请输入第一个整数:" num1
read -p "请输入第二个整数:" num2# 2. 验证输入是否为整数
expr $num1 + $num2 > /dev/null 2>&1
if [ $? -ne 0 ]; thenecho "错误:输入的必须是整数!"exit 1
fi# 3. 比较大小
if [ $num1 -gt $num2 ]; thenecho "$num1 大于 $num2"
elif [ $num1 -eq $num2 ]; thenecho "$num1 等于 $num2"
elseecho "$num1 小于 $num2"
fi
三、字符测试:验证字符串属性
字符测试用于判断字符串的内容、为空/非空状态,核心运算符及示例如下:
运算符 | 含义 | 语法示例 | 说明 |
---|---|---|---|
= | 字符串内容相等 | [ "$str1" = "$str2" ] | 等号两侧必须有空格,变量需加双引号(避免空值导致语法错误) |
!= | 字符串内容不相等 | [ "$str1" != "$str2" ] | 与= 相反,判断两个字符串是否不同 |
-z "$str" | 字符串为空(长度为 0) | [ -z "$str" ] | 空字符串返回真,非空返回假 |
-n "$str" | 字符串非空(长度大于 0) | [ -n "$str" ] | 非空字符串返回真,空返回假 |
关键注意事项
-
变量必须加双引号:若变量为空(如
str=""
),不加引号会导致表达式语法错误。例如:- 错误:
[ $str = "test" ]
($str 为空时,表达式变为[ = "test" ]
,语法错误) - 正确:
[ "$str" = "test" ]
(即使 $str 为空,表达式仍为[ "" = "test" ]
,语法合法)
- 错误:
-
=
与==
的区别:在[ ]
中推荐使用=
(POSIX 标准),==
是 Bash 扩展,兼容性稍差;在[[ ]]
中两者均可。 -
避免无空格的错误:错误写法
[ "$a"="$b" ]
(等号两侧无空格),Shell 会将"$a"="$b"
视为一个整体字符串,永远返回真(非空),正确写法是[ "$a" = "$b" ]
。
实战案例:字符测试脚本
需求:验证用户输入的用户名是否为“admin”,且密码不为空。
#!/bin/bash
# 脚本名:user_verify.sh# 1. 读取用户名和密码
read -p "请输入用户名:" username
read -p "请输入密码:" password# 2. 验证用户名是否为 admin
if [ "$username" != "admin" ]; thenecho "错误:用户名不正确!"exit 1
fi# 3. 验证密码是否为空
if [ -z "$password" ]; thenecho "错误:密码不能为空!"exit 1
fi# 4. 验证通过
echo "验证成功!欢迎您,$username!"
执行效果:
chmod +x user_verify.sh
./user_verify.sh
# 输入用户名:user → 输出"用户名不正确"
# 输入用户名:admin,密码:(空)→ 输出"密码不能为空"
# 输入用户名:admin,密码:123456 → 输出"验证成功!欢迎您,admin!"
四、文件测试:检测文件/目录属性
文件测试是 Shell 脚本中最常用的测试类型,可检测文件的存在性、类型、权限、新旧等属性,核心运算符及示例如下:
1. 存在性与类型测试
运算符 | 含义 | 语法示例 | 适用场景 |
---|---|---|---|
-e | 测试文件/目录是否存在 | [ -e /tmp/file ] | 判断备份文件、配置文件是否存在 |
-f | 测试是否为普通文件(非目录、链接) | [ -f /tmp/file ] | 确认目标是文件而非目录,避免误操作 |
-d | 测试是否为目录 | [ -d /tmp/dir ] | 判断目标是目录,用于cd 前的验证 |
-L | 测试是否为符号链接文件 | [ -L /tmp/link ] | 区分真实文件和链接文件 |
-b | 测试是否为块设备文件(如硬盘、U盘) | [ -b /dev/sda ] | 硬件设备相关操作验证 |
-c | 测试是否为字符设备文件(如键盘、终端) | [ -c /dev/tty1 ] | 字符设备操作前验证 |
2. 权限测试
运算符 | 含义 | 语法示例 | 适用场景 |
---|---|---|---|
-r | 测试当前用户是否有读权限 | [ -r /etc/passwd ] | 读取文件前验证权限 |
-w | 测试当前用户是否有写权限 | [ -w /tmp/file ] | 修改文件前验证权限 |
-x | 测试当前用户是否有执行权限 | [ -x /usr/bin/ls ] | 执行脚本、命令前验证权限 |
3. 特殊权限与大小测试
运算符 | 含义 | 语法示例 | 适用场景 |
---|---|---|---|
-s | 测试文件是否非空(大小大于 0) | [ -s /tmp/log.txt ] | 判断日志文件是否有内容 |
-u | 测试文件是否有 SUID 权限(如passwd 命令) | [ -u /usr/bin/passwd ] | 检查特殊权限是否配置 |
-g | 测试文件是否有 SGID 权限 | [ -g /tmp/dir ] | 目录权限继承验证 |
-k | 测试文件是否有 Sticky 权限(如/tmp 目录) | [ -k /tmp ] | 防止目录下文件被随意删除的验证 |
4. 双目测试(文件间比较)
运算符 | 含义 | 语法示例 | 适用场景 |
---|---|---|---|
file1 -ef file2 | 测试两个文件是否为同一文件(硬链接,同 inode) | [ /tmp/file1 -ef /tmp/file2 ] | 判断是否为硬链接 |
file1 -nt file2 | 测试 file1 是否比 file2 新(修改时间) | [ /tmp/new.log -nt /tmp/old.log ] | 备份时判断是否需要更新文件 |
file1 -ot file2 | 测试 file1 是否比 file2 旧(修改时间) | [ /tmp/old.log -ot /tmp/new.log ] | 日志归档时判断新旧 |
实战案例:文件属性检测脚本
需求:创建脚本,检测指定路径的文件/目录属性(存在性、类型、权限、新旧)。
#!/bin/bash
# 脚本名:file_check.sh
# 用法:./file_check.sh <文件/目录路径># 1. 验证参数数量
if [ $# -ne 1 ]; thenecho "用法:$0 <文件/目录路径>"exit 1
fifile_path=$1# 2. 检测是否存在
if [ ! -e "$file_path" ]; then # ! 表示逻辑非echo "错误:$file_path 不存在!"exit 1
fi# 3. 检测文件类型
echo "=== $file_path 的属性检测结果 ==="
if [ -f "$file_path" ]; thenecho "文件类型:普通文件"
elif [ -d "$file_path" ]; thenecho "文件类型:目录"
elif [ -L "$file_path" ]; thenecho "文件类型:符号链接(指向:$(readlink "$file_path"))" # readlink 查看链接目标
elif [ -b "$file_path" ]; thenecho "文件类型:块设备文件"
elif [ -c "$file_path" ]; thenecho "文件类型:字符设备文件"
fi# 4. 检测当前用户权限
echo -n "当前用户权限:"
perm=""
[ -r "$file_path" ] && perm+="读(r) "
[ -w "$file_path" ] && perm+="写(w) "
[ -x "$file_path" ] && perm+="执行(x) "
echo "${perm:-无权限}" # 若 perm 为空,输出"无权限"# 5. 比较与 /tmp/test 文件的新旧(若 /tmp/test 存在)
if [ -e "/tmp/test" ]; thenif [ "$file_path" -nt "/tmp/test" ]; thenecho "与 /tmp/test 比较:$file_path 更新"elif [ "$file_path" -ot "/tmp/test" ]; thenecho "与 /tmp/test 比较:$file_path 更旧"elseecho "与 /tmp/test 比较:两者修改时间相同"fi
fi
执行效果:
chmod +x file_check.sh
# 检测普通文件
touch /tmp/test.txt && ./file_check.sh /tmp/test.txt
# 输出:
# === /tmp/test.txt 的属性检测结果 ===
# 文件类型:普通文件
# 当前用户权限:读(r) 写(w)
# (若 /tmp/test 存在,会显示新旧比较结果)# 检测目录
mkdir /tmp/test_dir && ./file_check.sh /tmp/test_dir
# 输出:
# === /tmp/test_dir 的属性检测结果 ===
# 文件类型:目录
# 当前用户权限:读(r) 写(w) 执行(x)
五、组合测试:多条件逻辑判断
当需要同时判断多个条件时,需使用逻辑运算符组合测试表达式,Shell 支持三种核心逻辑关系:
逻辑关系 | 运算符([ ] 中) | 运算符([[ ]] 或命令间) | 语法示例 | 短路特性 |
---|---|---|---|---|
逻辑与(AND) | -a | && | [ $a -gt 5 -a $a -lt 10 ] 或 [ $a -gt 5 ] && [ $a -lt 10 ] | 第一个条件为假时,后续条件不判断 |
逻辑或(OR) | -o | ` | ` | |
逻辑非(NOT) | ! | ! | [ ! -e /tmp/file ] 或 ! [ -e /tmp/file ] | 取反条件结果 |
关键建议:优先使用&&
/||
而非-a
/-o
在[ ]
中使用-a
/-o
时,若表达式中包含变量或复杂条件,容易因优先级问题导致判断错误。推荐将多个条件拆分为独立的[ ]
表达式,用&&
/||
连接,可读性和稳定性更强:
- 不推荐(易出错):
[ $num -gt 5 -a $num -lt 10 ]
- 推荐(清晰稳定):
[ $num -gt 5 ] && [ $num -lt 10 ]
实战案例:组合条件验证脚本
需求:验证用户输入的年龄是否在 18-60 岁之间(包含边界),且输入的职业不为空。
#!/bin/bash
# 脚本名:age_job_verify.sh# 1. 读取用户输入
read -p "请输入您的年龄:" age
read -p "请输入您的职业:" job# 2. 组合条件验证
# 条件1:年龄是整数;条件2:年龄在 18-60 之间;条件3:职业非空
if expr $age + 0 > /dev/null 2>&1 && [ $age -ge 18 ] && [ $age -le 60 ] && [ -n "$job" ]; thenecho "验证通过!您的年龄符合要求,职业为:$job"
elseecho "验证失败!请确保:1. 年龄是 18-60 之间的整数;2. 职业不为空"exit 1
fi
执行效果:
chmod +x age_job_verify.sh
# 输入年龄:25,职业:工程师 → 验证通过
# 输入年龄:17,职业:学生 → 验证失败(年龄不足)
# 输入年龄:65,职业:教师 → 验证失败(年龄超界)
# 输入年龄:abc,职业:医生 → 验证失败(年龄非整数)
# 输入年龄:30,职业:(空)→ 验证失败(职业为空)
六、if 语句:条件分支控制
if 语句是 Shell 脚本中实现“分支逻辑”的核心结构,根据条件测试结果执行不同命令,支持单分支、双分支、多分支三种形式。
1. 单分支 if 语句:满足条件才执行
语法格式:
if 条件测试表达式; then# 条件成立时执行的命令(可多行)
fi # 结束标志,必须写
示例:判断当前用户是否为 root
#!/bin/bash
# 脚本名:check_root.shif [ "$USER" = "root" ]; then # $USER 是系统变量,存储当前用户名echo "您当前是 root 用户,拥有最高权限。"
fi
执行效果:
- root 用户执行:输出“您当前是 root 用户…”
- 普通用户执行:无输出(条件不成立,不执行命令)
2. 双分支 if 语句:二选一执行
语法格式:
if 条件测试表达式; then# 条件成立时执行的命令
else# 条件不成立时执行的命令
fi
示例:判断文件是否存在,存在则删除,不存在则提示
#!/bin/bash
# 脚本名:file_delete.sh
file="/tmp/temp.txt"if [ -f "$file" ]; thenrm -f "$file" # 强制删除文件echo "已删除文件:$file"
elseecho "文件 $file 不存在,无需删除。"
fi
3. 多分支 if 语句:多选一执行
语法格式:
if 条件测试表达式1; then# 条件1成立时执行的命令
elif 条件测试表达式2; then# 条件2成立时执行的命令
elif 条件测试表达式3; then# 条件3成立时执行的命令
...
else# 所有条件都不成立时执行的命令(可选)
fi
示例:根据分数判断成绩等级
#!/bin/bash
# 脚本名:grade_judge.shread -p "请输入您的分数(0-100):" score# 验证分数是否为 0-100 之间的整数
if ! expr $score + 0 > /dev/null 2>&1 || [ $score -lt 0 ] || [ $score -gt 100 ]; thenecho "错误:分数必须是 0-100 之间的整数!"exit 1
fi# 多分支判断等级
if [ $score -ge 90 ]; thengrade="A(优秀)"
elif [ $score -ge 80 ]; thengrade="B(良好)"
elif [ $score -ge 60 ]; thengrade="C(及格)"
elsegrade="D(不及格)"
fiecho "您的成绩等级:$grade"
执行效果:
chmod +x grade_judge.sh
./grade_judge.sh
# 输入:95 → 输出"您的成绩等级:A(优秀)"
# 输入:75 → 输出"您的成绩等级:C(及格)"
# 输入:59 → 输出"您的成绩等级:D(不及格)"
# 输入:105 → 输出"错误:分数必须是 0-100 之间的整数!"
4. if 语句嵌套:复杂条件判断
当需要多层条件判断时,可在 if 语句内部嵌套另一个 if 语句,示例如下:
需求:判断用户输入的数字是否为偶数,若是则进一步判断是否为 4 的倍数
#!/bin/bash
# 脚本名:even_4_check.shread -p "请输入一个整数:" num# 第一层:验证是否为整数
if expr $num + 0 > /dev/null 2>&1; then# 第二层:判断是否为偶数if [ $((num % 2)) -eq 0 ]; then# 第三层:判断是否为 4 的倍数if [ $((num % 4)) -eq 0 ]; thenecho "$num 是偶数,且是 4 的倍数。"elseecho "$num 是偶数,但不是 4 的倍数。"fielseecho "$num 是奇数。"fi
elseecho "错误:输入的不是整数!"exit 1
fi
七、常见错误与避坑指南
-
空格缺失导致语法错误
- 错误:
[ -e/tmp/file ]
(-e
与路径间无空格)、[ $a-$b -eq 0 ]
(运算符两侧无空格) - 正确:
[ -e /tmp/file ]
、[ $a - $b -eq 0 ]
(所有运算符、括号两侧必须有空格)
- 错误:
-
变量未加双引号导致空值错误
- 错误:
[ $name = "admin" ]
(若$name
为空,表达式变为[ = "admin" ]
,语法错误) - 正确:
[ "$name" = "admin" ]
(变量加双引号,即使为空也合法)
- 错误:
-
整数与字符串比较混淆
- 错误:用
=
比较整数([ $a = $b ]
)、用-eq
比较字符串([ "$str1" -eq "$str2" ]
) - 正确:整数用
-eq
/-gt
等,字符串用=
/!=
/-z
/-n
- 错误:用
-
逻辑运算符优先级错误
- 错误:
[ $num -gt 5 -a $num -lt 10 -o $num -eq 0 ]
(-a
与-o
优先级易混淆) - 正确:拆分为独立条件,用
&&
/||
连接:[ $num -gt 5 ] && [ $num -lt 10 ] || [ $num -eq 0 ]
- 错误:
-
/dev/null 2>&1
的正确使用- 作用:将标准输出(1)和错误输出(2)都重定向到空设备,避免无关信息干扰脚本执行。
- 常见场景:
expr $num + 0 > /dev/null 2>&1
(验证整数时屏蔽错误输出) - 错误写法:
expr $num + 0 2>&1 > /dev/null
(顺序错误,错误输出会先重定向到标准输出,再被丢弃,但逻辑上无问题,推荐统一写为> /dev/null 2>&1
)
八、总结与实战脚本
总结:条件测试与 if 语句的核心流程
- 明确需求:判断是整数比较、字符串验证还是文件检测。
- 选择表达式格式:优先使用
[ ]
(兼容性强)或[[ ]]
(支持正则)。 - 编写条件测试:严格遵循空格规则,变量加双引号,逻辑清晰。
- 构建 if 分支:根据需求选择单分支、双分支或多分支,嵌套时注意缩进(4个空格)。
- 测试与优化:覆盖正常、异常场景,通过
echo $?
排查条件判断错误。
综合实战脚本:系统备份检查脚本
需求:编写脚本,检查备份目录是否存在、是否有写权限,若存在则执行备份,备份后验证备份文件是否生成。
#!/bin/bash
# 脚本名:system_backup.sh
# 功能:备份 /etc 目录到 /backup/etc_$(date +%Y%m%d).tar.gz# 1. 定义变量
backup_dir="/backup"
source_dir="/etc"
backup_file="${backup_dir}/etc_$(date +%Y%m%d).tar.gz" # 按日期命名备份文件# 2. 检查备份目录是否存在,不存在则创建
if [ ! -d "$backup_dir" ]; thenecho "备份目录 $backup_dir 不存在,正在创建..."mkdir -p "$backup_dir" # -p 确保父目录存在if [ $? -ne 0 ]; thenecho "错误:创建备份目录 $backup_dir 失败!"exit 1fi
fi# 3. 检查备份目录是否有写权限
if [ ! -w "$backup_dir" ]; thenecho "错误:备份目录 $backup_dir 无写权限!"exit 1
fi# 4. 执行备份(tar 打包压缩)
echo "正在备份 $source_dir 到 $backup_file..."
tar -zcf "$backup_file" "$source_dir" > /dev/null 2>&1 # -z 压缩,-c 创建,-f 指定文件
if [ $? -eq 0 ]; thenecho "备份执行完成!"
elseecho "错误:备份执行失败!"exit 1
fi# 5. 验证备份文件是否生成且非空
if [ -f "$backup_file" ] && [ -s "$backup_file" ]; thenecho "备份文件验证成功!文件路径:$backup_file"echo "文件大小:$(du -sh "$backup_file")" # 显示文件大小
elseecho "错误:备份文件 $backup_file 未生成或为空!"exit 1
fi
执行效果:
chmod +x system_backup.sh
sudo ./system_backup.sh # 需 root 权限(备份 /etc 目录)
# 输出:
# 备份目录 /backup 不存在,正在创建...
# 正在备份 /etc 到 /backup/etc_20240520.tar.gz...
# 备份执行完成!
# 备份文件验证成功!文件路径:/backup/etc_20240520.tar.gz
# 文件大小:128M /backup/etc_20240520.tar.gz
通过本脚本,可完整覆盖文件测试、整数测试(隐含在$?
判断)、if 分支逻辑,是典型的生产环境级脚本示例。