Shell 脚本判断
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. 判断参数数量是否为 2if [[ $# -ne 2 ]]; thenecho "错误:必须输入两个参数"exit 1 # 非 0 退出码表示执行失败fi# 2. 判断参数是否为整数(利用 expr 计算,非整数会报错)expr $1 + $2 > /dev/null 2>&1 # 重定向错误输出,避免干扰if [[ $? -ne 0 ]]; then # $? 是上一条命令的退出码(0 为真,非 0 为假)echo "错误:输入的参数必须是整数"exit 1fi# 3. 比较两个整数的大小if [[ $1 -gt $2 ]]; thenecho "$1 大于 $2"elif [[ $1 -lt $2 ]]; thenecho "$1 小于 $2"elseecho "$1 等于 $2"fi执行示例:sh a.sh 10 20 → 10 小于 20sh a.sh 20 10 → 20 大于 10sh a.sh 15 15 → 15 等于 15sh a.sh 10 → 错误:必须输入两个参数sh a.sh 10 abc → 错误:输入的参数必须是整数
案例 2:交互式输入参数比较
需求:通过 read 命令让用户交互式输入两个整数,再比较大小。
#!/bin/bash# 脚本名:c.sh# 1. 交互式输入两个参数read -p "请输入要比较的第一个整数:" num1read -p "请输入要比较的第二个整数:" num2# 2. 判断是否为整数expr $num1 + $num2 > /dev/null 2>&1if [[ $? -ne 0 ]]; thenecho "错误:输入的必须是整数"exit 1fi# 3. 比较大小[[ $num1 -gt $num2 ]] && echo "$num1 大于 $num2"[[ $num1 -lt $num2 ]] && echo "$num1 小于 $num2"[[ $num1 -eq $num2 ]] && echo "$num1 等于 $num2"
- 执行示例:
sh c.sh请输入要比较的第一个整数:5请输入要比较的第二个整数:85 小于 8
案例 3:多分支选择(英雄选择)
需求:让用户选择英雄编号(1-3),根据选择输出对应结果,输入其他编号提示 “选择无效”。
#!/bin/bash# 脚本名:d.sh# 1. 显示英雄列表echo "===== 选择你的英雄 ====="cat <<EOF1. 全民偶像:鹿晗2. 综艺之神:陈赫3. 魔童:邓超EOF# 2. 接收用户输入read -p "请输入英雄编号(1-3):" num# 3. 多分支判断(结合整数测试和逻辑或 `-o`)if [[ $num -eq 1 -o $num -eq 2 -o $num -eq 3 ]]; thenif [[ $num -eq 1 ]]; thenecho "已选择:全民偶像 · 鹿晗!"elif [[ $num -eq 2 ]]; thenecho "已选择:综艺之神 · 陈赫!"elseecho "已选择:魔童 · 邓超!"fielseecho "错误:你选择了无效的英雄编号!"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" ]]; thenecho "错误:用户名不能为空!"exit 1fi# 3. 判断用户名是否为 adminif [[ "$username" == "admin" ]]; thenecho "欢迎你,管理员 $username!"elseecho "欢迎你,普通用户 $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/dir3echo "测试文件/目录创建完成!"
步骤 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.shread -p "请输入你的年龄:" age# 组合条件:年龄是整数 + 年龄 >=18 + 年龄 <=60# 步骤1:判断是否为整数expr $age + 0 > /dev/null 2>&1if [[ $? -ne 0 ]]; thenecho "错误:年龄必须是整数!"exit 1fi# 步骤2:组合判断年龄范围(18<=age<=60)if [[ $age -ge 18 && $age -le 60 ]]; thenecho "年龄合法(18~60岁)"elseecho "年龄不合法(需在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命令2fi # 结束 if 语句(必须闭合)
案例:检查文件是否存在
需求:若 /tmp/file1 存在,输出 “文件已存在”。
#!/bin/bash# 脚本名:test.shfile="/tmp/file1"if [[ -e $file ]]; thenecho "文件已存在:$file"echo "文件大小:$(du -sh $file)" # 额外输出文件大小fi
2. 双分支 if 语句(二选一执行)
语法格式
if 条件表达式; then# 条件为真时执行的命令块命令1else# 条件为假时执行的命令块命令2fi
3. 多分支 if 语句(多选一执行)
语法格式
if 条件1; then# 条件1为真时执行命令1elif 条件2; then # else if 的缩写,可多个# 条件2为真时执行命令2elif 条件3; then# 条件3为真时执行命令3else# 所有条件都为假时执行命令4fi
案例:成绩等级判断
需求:根据输入的分数(0~100),判断等级(A:90~100,B:80~89,C:60~79,D:<60)。
#!/bin/bash# 脚本名:cj.shread -p "请输入你的分数(0~100):" score# 先判断是否为整数且在 0~100 范围内expr $score + 0 > /dev/null 2>&1if [[ $? -ne 0 || $score -lt 0 || $score -gt 100 ]]; thenecho "错误:分数必须是 0~100 之间的整数!"exit 1fi# 多分支判断等级if [[ $score -ge 90 ]]; thenecho "你的等级:A(优秀)"elif [[ $score -ge 80 ]]; thenecho "你的等级:B(良好)"elif [[ $score -ge 60 ]]; thenecho "你的等级:C(及格)"elseecho "你的等级: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 结构:单分支(满足才执行)、双分支(二选一)、多分支(多选一),根据需求选择合适的结构;
- 组合逻辑:用 &&/||/! 实现多条件组合,注意优先级和括号的使用。
通过本文的案例练习,可快速掌握判断逻辑的实际应用,为编写复杂的自动化脚本打下基础。