Shell 脚本判断
一、条件测试基础
在 Shell 脚本中,“判断” 的核心是通过条件测试判断表达式真假,进而控制程序流程。条件测试的核心要素包括:测试类型、表达式格式、结果判断逻辑。
1. 条件测试的 3 种类型
Shell 支持三类常见的条件测试,覆盖整数、字符、文件场景:
测试类型 |
作用 |
核心场景 |
整数测试 |
比较两个整数的大小关系(相等、大于、小于等) |
数值比较(如参数数量判断、年龄 / 分数比较) |
字符测试 |
检查字符串的内容、长度或等值关系 |
字符串是否为空、输入内容匹配(如用户名验证) |
文件测试 |
检测文件 / 目录的存在性、类型、权限、大小等 |
文件是否存在、是否为目录、是否有执行权限 |
2. 条件测试的 3 种表达式格式
所有条件测试均需通过固定格式的表达式实现,三种格式功能等价,可根据场景选择:
表达式格式 |
语法示例 |
说明 |
test 条件 |
test -e /tmp/file |
最原始的格式,兼容性好 |
[ 条件 ] |
[ -e /tmp/file ] |
简化格式,需注意条件前后有空格(否则语法错误) |
[[ 条件 ]] |
[[ -e /tmp/file ]] |
Bash 扩展格式,支持正则匹配(如 [[ $str =~ ^[0-9]+$ ]]),推荐使用 |
3. 结果判断逻辑
条件测试的结果通过退出状态码(0 为真,非 0 为假)表示,通常结合逻辑运算符 &&(真则执行)和 ||(假则执行)输出结果:
# 示例:测试 /tmp/file 是否存在
test -e /tmp/file && echo "文件存在" || echo "文件不存在"
[ -e /tmp/file ] && echo "文件存在" || echo "文件不存在"
[[ -e /tmp/file ]] && echo "文件存在" || echo "文件不存在"
- 若 /tmp/file 存在,输出 文件存在;若不存在,输出 文件不存在。
二、整数测试(数值比较)
整数测试用于比较两个整数的大小关系,需使用专用的比较运算符(不可用 > < 等符号,避免与字符比较混淆)。
1. 整数测试运算符
运算符 |
含义 |
示例(判断 $a 和 $b 的关系) |
-eq |
等于(equal) |
[[ $a -eq $b ]] → 判断 $a 是否等于 $b |
-ne |
不等于(not equal) |
[[ $a -ne $b ]] → 判断 $a 是否不等于 $b |
-gt |
大于(greater than) |
[[ $a -gt $b ]] → 判断 $a 是否大于 $b |
-ge |
大于等于(greater or equal) |
[[ $a -ge $b ]] → 判断 $a 是否大于等于 $b |
-lt |
小于(less than) |
[[ $a -lt $b ]] → 判断 $a 是否小于 $b |
-le |
小于等于(less or equal) |
[[ $a -le $b ]] → 判断 $a 是否小于等于 $b |
2. 整数测试实战案例
以下三个案例分别实现 “命令行传参比较”“交互式输入比较”“if 多分支比较”,覆盖整数测试的常见场景。
案例 1:命令行传递参数比较
需求:接收两个命令行参数,先判断参数数量是否为 2,再判断是否为整数,最后比较大小。
#!/bin/bash
# 脚本名:a.sh
# 1. 判断参数数量是否为 2
if [[ $# -ne 2 ]]; then
echo "错误:必须输入两个参数"
exit 1 # 非 0 退出码表示执行失败
fi
# 2. 判断参数是否为整数(利用 expr 计算,非整数会报错)
expr $1 + $2 > /dev/null 2>&1 # 重定向错误输出,避免干扰
if [[ $? -ne 0 ]]; then # $? 是上一条命令的退出码(0 为真,非 0 为假)
echo "错误:输入的参数必须是整数"
exit 1
fi
# 3. 比较两个整数的大小
if [[ $1 -gt $2 ]]; then
echo "$1 大于 $2"
elif [[ $1 -lt $2 ]]; then
echo "$1 小于 $2"
else
echo "$1 等于 $2"
fi
执行示例:
sh a.sh 10 20 → 10 小于 20
sh a.sh 20 10 → 20 大于 10
sh a.sh 15 15 → 15 等于 15
sh a.sh 10 → 错误:必须输入两个参数
sh a.sh 10 abc → 错误:输入的参数必须是整数
案例 2:交互式输入参数比较
需求:通过 read 命令让用户交互式输入两个整数,再比较大小。
#!/bin/bash
# 脚本名:c.sh
# 1. 交互式输入两个参数
read -p "请输入要比较的第一个整数:" num1
read -p "请输入要比较的第二个整数:" num2
# 2. 判断是否为整数
expr $num1 + $num2 > /dev/null 2>&1
if [[ $? -ne 0 ]]; then
echo "错误:输入的必须是整数"
exit 1
fi
# 3. 比较大小
[[ $num1 -gt $num2 ]] && echo "$num1 大于 $num2"
[[ $num1 -lt $num2 ]] && echo "$num1 小于 $num2"
[[ $num1 -eq $num2 ]] && echo "$num1 等于 $num2"
- 执行示例:
sh c.sh
请输入要比较的第一个整数:5
请输入要比较的第二个整数:8
5 小于 8
案例 3:多分支选择(英雄选择)
需求:让用户选择英雄编号(1-3),根据选择输出对应结果,输入其他编号提示 “选择无效”。
#!/bin/bash
# 脚本名:d.sh
# 1. 显示英雄列表
echo "===== 选择你的英雄 ====="
cat <<EOF
1. 全民偶像:鹿晗
2. 综艺之神:陈赫
3. 魔童:邓超
EOF
# 2. 接收用户输入
read -p "请输入英雄编号(1-3):" num
# 3. 多分支判断(结合整数测试和逻辑或 `-o`)
if [[ $num -eq 1 -o $num -eq 2 -o $num -eq 3 ]]; then
if [[ $num -eq 1 ]]; then
echo "已选择:全民偶像 · 鹿晗!"
elif [[ $num -eq 2 ]]; then
echo "已选择:综艺之神 · 陈赫!"
else
echo "已选择:魔童 · 邓超!"
fi
else
echo "错误:你选择了无效的英雄编号!"
fi
- 执行示例:
sh d.sh
===== 选择你的英雄 =====
1. 全民偶像:鹿晗
2. 综艺之神:陈赫
3. 魔童:邓超
请输入英雄编号(1-3):2
已选择:综艺之神 · 陈赫!
三、字符测试(字符串比较)
字符测试用于检查字符串的内容等值、长度是否为空,需注意字符串需加引号(避免空格或特殊字符导致解析错误)。
1. 字符测试运算符
运算符 |
含义 |
示例(判断字符串 $str 或 $str1 与 $str2 的关系) |
== |
等值比较(内容完全一致) |
[[ "$str1" == "$str2" ]] → 判断两个字符串是否相同 |
!= |
不等值比较(内容不同) |
[[ "$str1" != "$str2" ]] → 判断两个字符串是否不同 |
-z |
字符串为空(长度为 0) |
[[ -z "$str" ]] → 判断 $str 是否为空 |
-n |
字符串非空(长度 > 0) |
[[ -n "$str" ]] → 判断 $str 是否非空 |
2. 字符测试实战案例
案例:用户名验证
需求:让用户输入用户名,判断是否为空、是否为指定用户名(如 admin)。
#!/bin/bash
# 脚本名:e.sh
# 1. 接收用户名输入
read -p "请输入用户名:" username
# 2. 判断用户名是否为空
if [[ -z "$username" ]]; then
echo "错误:用户名不能为空!"
exit 1
fi
# 3. 判断用户名是否为 admin
if [[ "$username" == "admin" ]]; then
echo "欢迎你,管理员 $username!"
else
echo "欢迎你,普通用户 $username!"
fi
- 执行示例:
sh e.sh
请输入用户名:admin → 欢迎你,管理员 admin!
sh e.sh
请输入用户名:zhangsan → 欢迎你,普通用户 zhangsan!
sh e.sh
请输入用户名:(直接回车)→ 错误:用户名不能为空!
四、文件测试(文件 / 目录属性判断)
文件测试是 Shell 脚本中最常用的判断场景,用于检测文件 / 目录的存在性、类型、权限、大小等属性,需使用专用的文件测试运算符。
1. 文件测试运算符分类
根据测试目标,文件测试运算符可分为 6 类,覆盖所有常见文件属性:
(1)存在性测试
运算符 |
含义 |
示例 |
-e |
测试文件 / 目录是否存在 |
[[ -e /tmp/file ]] → 判断 /tmp/file 是否存在 |
(2)类型测试(存在且为指定类型)
运算符 |
含义 |
示例 |
-f |
普通文件(非目录 / 链接) |
[[ -f /tmp/file ]] → 判断是否为普通文件 |
-d |
目录 |
[[ -d /tmp/dir ]] → 判断是否为目录 |
-L |
符号链接文件 |
[[ -L /tmp/link ]] → 判断是否为软链接 |
-b |
块设备文件(如磁盘 /dev/sda) |
[[ -b /dev/sda ]] → 判断是否为块设备 |
-c |
字符设备文件(如键盘 /dev/tty1) |
[[ -c /dev/tty1 ]] → 判断是否为字符设备 |
-S |
套接字文件(如 /var/run/mysql.sock) |
[[ -S /var/run/mysql.sock ]] → 判断是否为套接字 |
(3)权限测试(当前用户是否有对应权限)
运算符 |
含义 |
示例 |
-r |
读权限 |
[[ -r /tmp/file ]] → 当前用户是否能读该文件 |
-w |
写权限 |
[[ -w /tmp/file ]] → 当前用户是否能写该文件 |
-x |
执行权限(文件)/ 进入权限(目录) |
[[ -x /tmp/script.sh ]] → 当前用户是否能执行该脚本 |
(4)特殊权限测试
运算符 |
含义 |
示例 |
-u |
拥有 SUID 权限(执行时继承文件所有者权限) |
[[ -u /usr/bin/passwd ]] → 判断 passwd 是否有 SUID |
-g |
拥有 SGID 权限(执行时继承文件所属组权限) |
[[ -g /tmp/dir ]] → 判断目录是否有 SGID |
-k |
拥有 Sticky 权限(目录中仅所有者可删除自己的文件) |
[[ -k /tmp ]] → 判断 /tmp 是否有 Sticky 权限 |
(5)大小与修改测试
运算符 |
含义 |
示例 |
-s |
文件非空(大小 > 0) |
[[ -s /tmp/file ]] → 判断文件是否有内容 |
-N |
文件自上次读取后是否被修改过 |
[[ -N /tmp/file ]] → 判断文件是否被修改 |
(6)双目测试(两个文件的关系)
运算符 |
含义 |
示例 |
-ef |
两个文件是否为同一文件(相同 inode) |
[[ /tmp/file1 -ef /tmp/file2 ]] → 判断是否为硬链接 |
-nt |
文件 1 是否比文件 2 新(修改时间) |
[[ /tmp/file1 -nt /tmp/file2 ]] → file1 比 file2 新? |
-ot |
文件 1 是否比文件 2 旧(修改时间) |
[[ /tmp/file1 -ot /tmp/file2 ]] → file1 比 file2 旧? |
2. 文件测试实战案例
需求:先创建一批测试文件 / 目录,再编写脚本检测它们的属性(存在性、类型、特殊权限等)。
步骤 1:创建测试文件 / 目录
#!/bin/bash
# 脚本名:test.sh
# 创建普通文件
touch /tmp/file1 /tmp/file2
# 创建目录
mkdir /tmp/dir1 /tmp/dir3
# 创建符号链接(指向 dir1)
ln -s /tmp/dir1 /tmp/dir2
# 给 dir3 添加 SGID 权限
chmod g+s /tmp/dir3
echo "测试文件/目录创建完成!"
步骤 2:编写文件测试脚本
#!/bin/bash
# 脚本名:file.sh
# 定义测试文件/目录路径
file1="/tmp/file1"
file2="/tmp/file2"
dir1="/tmp/dir1"
link2="/tmp/dir2"
dir3="/tmp/dir3"
file4="/tmp/file4" # 不存在的文件
# 1. 测试存在性
echo "===== 存在性测试 ====="
[[ -e $file1 ]] && echo "$file1 → 存在" || echo "$file1 → 不存在"
[[ -e $file4 ]] && echo "$file4 → 存在" || echo "$file4 → 不存在"
# 2. 测试文件类型
echo -e "\n===== 类型测试 ====="
[[ -f $file2 ]] && echo "$file2 → 普通文件" || echo "$file2 → 非普通文件"
[[ -d $dir1 ]] && echo "$dir1 → 目录" || echo "$dir1 → 非目录"
[[ -L $link2 ]] && echo "$link2 → 符号链接" || echo "$link2 → 非符号链接"
# 3. 测试特殊权限(SGID)
echo -e "\n===== 特殊权限测试 ====="
[[ -g $dir3 ]] && echo "$dir3 → 拥有 SGID 权限" || echo "$dir3 → 无 SGID 权限"
# 4. 测试权限(当前用户是否有写权限)
echo -e "\n===== 权限测试 ====="
[[ -w $file1 ]] && echo "$file1 → 当前用户有写权限" || echo "$file1 → 当前用户无写权限"
步骤 3:执行与输出结果
# 先创建测试文件
sh test.sh → 测试文件/目录创建完成!
# 执行测试脚本
sh file.sh
输出结果:
===== 存在性测试 =====
/tmp/file1 → 存在
/tmp/file4 → 不存在
===== 类型测试 =====
/tmp/file2 → 普通文件
/tmp/dir1 → 目录
/tmp/dir2 → 链接文件
===== 特殊权限测试 =====
/tmp/dir3 → 拥有 SGID 权限
===== 权限测试 =====
/tmp/file1 → 当前用户有写权限
五、组合测试(多条件判断)
当需要同时判断多个条件时,需使用组合测试运算符(与、或、非),将多个简单条件组合成复杂逻辑。
1. 组合测试运算符
运算符 |
逻辑关系 |
说明 |
示例(判断 $a>10 且 $a<20) |
-a |
逻辑与(AND) |
所有条件都为真,结果才为真 |
[[ $a -gt 10 -a $a -lt 20 ]] |
&& |
逻辑与(AND) |
Bash 扩展,功能与 -a 一致,优先级更高 |
[[ $a -gt 10 && $a -lt 20 ]] |
-o |
逻辑或(OR) |
任意一个条件为真,结果就为真 |
[[ $a -lt 10 -o $a -gt 20 ]] |
` |
` |
逻辑或(OR) | |
! |
逻辑非(NOT) |
条件为真时结果为假,条件为假时结果为真 |
[[ ! -e /tmp/file ]](判断文件不存在) |
2. 组合测试实战案例
案例:年龄合法性判断
需求:让用户输入年龄,判断是否在 “18~60” 之间(合法),否则提示 “年龄不合法”。
#!/bin/bash
# 脚本名:age.sh
read -p "请输入你的年龄:" age
# 组合条件:年龄是整数 + 年龄 >=18 + 年龄 <=60
# 步骤1:判断是否为整数
expr $age + 0 > /dev/null 2>&1
if [[ $? -ne 0 ]]; then
echo "错误:年龄必须是整数!"
exit 1
fi
# 步骤2:组合判断年龄范围(18<=age<=60)
if [[ $age -ge 18 && $age -le 60 ]]; then
echo "年龄合法(18~60岁)"
else
echo "年龄不合法(需在18~60岁之间)"
fi
- 执行示例:
sh age.sh
请输入你的年龄:25 → 年龄合法(18~60岁)
sh age.sh
请输入你的年龄:15 → 年龄不合法(需在18~60岁之间)
sh age.sh
请输入你的年龄:abc → 错误:年龄必须是整数!
六、if 语句(流程控制核心)
if 语句是 Shell 脚本中最基础的流程控制结构,根据条件的真假执行不同的命令块,分为单分支、双分支、多分支三种形式。
1. 单分支 if 语句(满足条件才执行)
语法格式
if 条件表达式; then
# 条件为真时执行的命令
命令1
命令2
fi # 结束 if 语句(必须闭合)
案例:检查文件是否存在
需求:若 /tmp/file1 存在,输出 “文件已存在”。
#!/bin/bash
# 脚本名:test.sh
file="/tmp/file1"
if [[ -e $file ]]; then
echo "文件已存在:$file"
echo "文件大小:$(du -sh $file)" # 额外输出文件大小
fi
2. 双分支 if 语句(二选一执行)
语法格式
if 条件表达式; then
# 条件为真时执行的命令块
命令1
else
# 条件为假时执行的命令块
命令2
fi
3. 多分支 if 语句(多选一执行)
语法格式
if 条件1; then
# 条件1为真时执行
命令1
elif 条件2; then # else if 的缩写,可多个
# 条件2为真时执行
命令2
elif 条件3; then
# 条件3为真时执行
命令3
else
# 所有条件都为假时执行
命令4
fi
案例:成绩等级判断
需求:根据输入的分数(0~100),判断等级(A:90~100,B:80~89,C:60~79,D:<60)。
#!/bin/bash
# 脚本名:cj.sh
read -p "请输入你的分数(0~100):" score
# 先判断是否为整数且在 0~100 范围内
expr $score + 0 > /dev/null 2>&1
if [[ $? -ne 0 || $score -lt 0 || $score -gt 100 ]]; then
echo "错误:分数必须是 0~100 之间的整数!"
exit 1
fi
# 多分支判断等级
if [[ $score -ge 90 ]]; then
echo "你的等级:A(优秀)"
elif [[ $score -ge 80 ]]; then
echo "你的等级:B(良好)"
elif [[ $score -ge 60 ]]; then
echo "你的等级:C(及格)"
else
echo "你的等级:D(不及格)"
fi
- 执行示例:
sh cj.sh
请输入你的分数(0~100):85 → 你的等级:B(良好)
sh cj.sh
请输入你的分数(0~100):59 → 你的等级:D(不及格)
sh cj.sh
请输入你的分数(0~100):105 → 错误:分数必须是 0~100 之间的整数!
七、判断逻辑的注意事项
- 空格问题:[ ] 或 [[ ]] 前后必须有空格,运算符(如 -eq、==)前后也必须有空格,否则会报语法错误。 .错误示例:[ $a==$b ](缺少空格);正确示例:[[ $a == $b ]]。
- 字符串引号:字符测试时,字符串变量必须加双引号("$str"),避免因字符串为空或含空格导致解析错误。 错误示例:[[ -z $username ]](空字符串会变成 [[ -z ]],语法错误);正确示例:[[ -z "$username" ]]。
- 整数与字符区分:整数测试用 -eq/-gt 等运算符,字符测试用 ==/!= 等运算符,不可混用。 错误示例:[[ "10" -gt "5" ]](字符串用整数运算符,虽可能生效,但不规范);正确示例:[[ 10 -gt 5 ]](整数直接比较)。
- 优先级问题:组合测试中,!(非)优先级最高,&&(与)次之,||(或)最低;若有复杂逻辑,建议用 () 包裹(需转义为 \(\) 或用 [[ ]] 自动支持)。 .示例:[[ $age -ge 18 && ($score -ge 60 || $level == "A") ]](先判断分数或等级,再与年龄组合)。
- 退出码理解:所有条件测试的结果都通过退出码($?)表示,$?=0 为真,$?!=0 为假;避免直接用 echo $? 输出,应通过 if 或 &&/|| 判断。
总结
Shell 脚本的判断逻辑是流程控制的核心,需掌握以下关键点:
- 三类测试:整数测试(比较大小)、字符测试(字符串属性)、文件测试(文件属性),覆盖所有常见判断场景;
- 两种格式:推荐使用 [[ 条件 ]] 格式(支持正则和扩展逻辑运算符),避免 [ ] 的兼容性问题;
- 三种 if 结构:单分支(满足才执行)、双分支(二选一)、多分支(多选一),根据需求选择合适的结构;
- 组合逻辑:用 &&/||/! 实现多条件组合,注意优先级和括号的使用。
通过本文的案例练习,可快速掌握判断逻辑的实际应用,为编写复杂的自动化脚本打下基础。