【Day 40】Shell脚本-条件判断
一、条件判断
(一)条件判断的核心类型
Shell 中最常用的两种条件判断结构为:
- if 条件判断:适用于 “范围判断”和 “多分支逻辑”
- case 条件判断:适用于 “等值判断”
(二)if 条件判断
if 判断通过 “条件是否成立” 决定执行分支,支持 “单分支”“双分支”“多分支” 三种结构。
1、 基础语法
1.1 单分支 if
//(满足条件才执行)适用于 “仅当条件为真时执行操作,条件为假时无动作” 的场景,语法:
if 条件; then # 分号“;”用于分隔“条件”和“then”(也可将 then 换行写)执行操作1 # 条件为真时执行执行操作2
fi # 结束 if 判断(必须闭合)
1.2 双分支 if
//(满足 / 不满足条件分别执行)适用于 “条件为真执行 A 操作,条件为假执行 B 操作” 的场景
if 条件; then执行操作1 # 条件为真
else执行操作2 # 条件为假
fi
1.3 多分支 if
//(多个条件依次判断一直到成立为止)适用于 “有 3 个及以上分支” 的场景
if 条件1; then执行操作1 # 条件1为真
elif 条件2; then # else if 的缩写,可多个执行操作2 # 条件1假、条件2真
elif 条件3; then执行操作3 # 条件1、2假,条件3真
else执行操作4 # 所有条件均假
fi
2、 if 条件的写法
if 中的 “条件” 是判断的核心,本质是一个 “命令”,if 会根据该命令的 “退出状态码” 判断分支。
- 若命令退出状态码为 0(表示成功),则进入 then 分支;
- 若状态码为非 0(表示失败),则跳过 then 分支(或进入 else 分支)。
因此,if 后面可以接任何 “能产生退出状态码的内容”,包括:
- 用 [] 或 [[ ]] 包裹的条件表达式;
- 直接执行的系统命令(如 id、grep 等);
- 当条件是 “表达式”(如 a > b、“文件是否存在”)时,需要用 [] 或 [[ ]] 这类 “表达式解析工具” 来识别,否则 Shell 无法理解;
- 当条件是 “命令”(如 id、grep)时,命令本身就能产生退出状态码,无需额外工具解析,直接写命令即可。
2.1 基于 “命令执行结果”
Shell 中,命令执行成功(退出状态码 $?=0)则条件为真,失败则为假,无需额外判断符号,直接写命令即可。
场景:判断命令是否执行成功(如文件是否能读取、服务是否能连接)。
示例:判断 MySQL 是否能正常连接
#!/bin/bash
# 用 mysql 命令测试连接(-e "select 1" 执行简单查询,2> /dev/null 隐藏错误)
if mysql -uroot -p123456 -e "select 1" 2> /dev/null; thenecho "MySQL 连接正常"
elseecho "MySQL 连接失败,请检查密码或服务状态"
fi
2.2 基于 “数值”
用于 “数值大小比较”(如磁盘使用率是否超过阈值、年龄是否达标)。需用 [ 数值表达式 ] 包裹,表达式需严格遵循 “空格规则”(括号内外、运算符前后必须有空格)。
数值表达式 | 含义 | 示例(判断 $a 是否大于 10) |
---|---|---|
$a -eq $b | 等于(==) | [ $a -eq 10 ] |
$a -ne $b | 不等于(!=) | [ $a -ne 10 ] |
$a -gt $b | 大于(>) | [ $a -gt 10 ] |
$a -ge $b | 大于等于(>=) | [ $a -ge 10 ] |
$a -lt $b | 小于(<) | [ $a -lt 10 ] |
$a -le $b | 小于等于(<=) | [ $a -le 10 ] |
2.3 基于 “字符”
用于 “字符串是否相等、是否为空” 的判断,同样需用 [ 字符表达式 ]
包裹,且字符串建议加双引号(避免空格导致的语法错误)。
字符表达式 | 含义 | 示例(判断 $str 是否为空) |
---|---|---|
"$str1" == "$str2" | 字符串相等(==) | "$name" == "root" |
"$str1" != "$str2" | 字符串不相等(!=) | "$name" != "root" |
-z "$str" | 字符串为空(长度为 0) | [ -z "$input" ] |
-n "$str" | 字符串非空(长度 > 0) | [ -n "$input" ] |
注意:==
前后必须有空格,且字符串变量必须加双引号(如输入包含空格时,"$str"
会视为一个整体,不加引号会被拆分为多个参数)。
2.4 基于 “文件 / 目录”
用于 “判断文件是否存在、是文件还是目录”,是脚本中 “处理文件” 的常用条件,语法为 [ 文件表达式 文件路径 ]
。
文件表达式 | 含义 | 示例(判断 /tmp/test.txt) |
---|---|---|
-e | 文件 / 目录是否存在 | [ -e /tmp/test.txt ] |
-f | 是否为普通文件(非目录) | [ -f /tmp/test.txt ] |
-d | 是否为目录 | [ -d /tmp/test_dir ] |
-r | 是否有读权限 | [ -r /tmp/test.txt ] |
-w | 是否有写权限 | [ -w /tmp/test.txt ] |
-x | 是否有执行权限 | [ -x /tmp/script.sh ] |
3. 多条件组合(and/or)
当需要 “同时满足多个条件” 或 “满足任一条件” 时,可使用 逻辑运算符 组合条件:
- and(并且):两个条件都为真,整体才为真,两种写法:
[ 条件1 -a 条件2 ]
(-a 是 and 的缩写,适用于单括号内);[ 条件1 ] && [ 条件2 ]
(&& 是 Shell 通用的 “逻辑与”,更推荐,可读性高)。
- or(或者):两个条件有一个为真,整体就为真,两种写法:
[ 条件1 -o 条件2 ]
(-o 是 or 的缩写);[ 条件1 ] || [ 条件2 ]
(|| 是 Shell 通用的 “逻辑或”)。
4、[ ] 和[[ ]]
特性 |
|
|
---|---|---|
变量引用 | 必须用双引号包裹变量(避免空值 / 特殊字符问题) | 可省略双引号(Bash 会自动处理空值) |
空格 | 括号内外、运算符前后必须有空格 | 相对宽松(但仍建议按规范加空格) |
命令执行结果 | 需通过命令退出状态码间接判断1. 先执行命令(如 id "$user" &> /dev/null); | 支持两种方式: 1. 直接引用命令输出(如 [[ $(id -u "$user") -eq 0 ]],判断用户是否为 root); 2. 直接判断命令状态(如 [[ id "$user" &> /dev/null ]]) |
数值比较 | 仅支持 POSIX 专用运算符: | 支持两种运算符: |
字符串比较 | 1. 相等 / 不等:需用 ==/!=,且变量必须加双引号(避免空值、空格或通配符导致解析错误); | 1. 相等 / 不等支持 ==/!=,变量可省略双引号(Bash 自动处理空值、空格); |
字符/目录 | 支持所有文件属性运算符-e 、-f 、-d 、-r 、-w 、-x 、-s | 支持与单括号完全相同的文件属性运算符; |
正则匹配 | 不支持原生正则,需借助外部命令 | 支持 |
通配符匹配 | 不支持原生通配符:通配符(*/?/[])会被 Shell 全局展开 | 原生支持通配符匹配:通配符不会全局展开,直接作为模式匹配(如 [[ "$filename" == *.sh ]] 直接判断 “文件名是否以 .sh 结尾”;[[ "$str" == ??? ]] 判断 “字符串是否为 3 个字符”) |
逻辑运算符 | 仅支持-a、-o、! | 支持&&、||、! |
Shell 中,单目表达式指 仅需要一个操作数(如文件路径、字符串变量)
所有文件属性类(-e
/-f
/-d
等)、字符串类(-z
/-n
)单目表达式,以及命令执行结果,均支持 !
取反;!
必须放在条件表达式最前面,且前后需空格([ ! 表达式 ]
或 ! command
);
5、if嵌套
练习:
实战案例1:判断用户是否为 root 且当前目录是否为 /root
#!/bin/bash
username=$(whoami) # 获取当前登录用户
current_dir=$(pwd) # 获取当前目录if [ "$username" == "root" ] && [ "$current_dir" == "/root" ]; thenecho "当前用户是 root,且当前目录是 /root(符合预期)"
elseecho "当前用户:$username,当前目录:$current_dir(不符合 root + /root 的条件)"
fi
实战案例2:创建用户(存在则提示,不存在则创建)
#!/bin/bash
# 脚本路径:/opt/work/userCreate.sh
read -p "请输入要创建的用户名:" name# 核心逻辑:用 id 命令的退出状态码判断用户是否存在
id $name &> /dev/null # 执行 id 命令,隐藏输出
if [ $? -eq 0 ]; then # $? 为 0 表示 id 命令成功(用户存在)echo "用户 ${name} 已存在,无需重复创建"
else # $? 非 0 表示用户不存在,执行创建useradd $name # 创建用户echo "123456" | passwd --stdin $name &> /dev/null # 批量设置密码(隐藏输出)echo "用户 ${name} 创建完成,初始密码:123456(建议首次登录修改)"
fi
实战案例3: 密码确认(两次输入一致则通过
#!/bin/bash
read -p "请输入密码:" pwd1
read -p "请再次输入密码:" pwd2# 判断两次密码是否一致(变量加双引号,避免空格问题)
if [ "$pwd2" == "$pwd1" ]; thenecho "密码设置成功:两次输入一致"
elseecho "密码设置失败:两次输入不一致,请重新操作"
fi
实战案例4:判断文件是否有空行(显示空行行号)
#!/bin/bash
# 脚本路径:/opt/work/check_null_file.sh
read -p "请输入要检测的文件路径(如 /tmp/info.txt):" file_name# 第一步:判断文件是否存在且为普通文件
if [ -f "$file_name" ]; then# 第二步:判断文件是否有空行(^$ 匹配空行,&> /dev/null 隐藏输出)if grep "^$" "$file_name" &> /dev/null; thenecho "文件 ${file_name} 中存在空行,空行行号如下:"grep -n "^$" "$file_name" # -n 显示行号elseecho "文件 ${file_name} 中无空行"fi
elseecho "错误:文件 ${file_name} 不存在,或不是普通文件"
fi
实战案例5:根据当前小时判断时间段
#!/bin/bash
# 脚本路径:/opt/work/check_date.sh
hour=$(date +%H) # 获取当前小时(24小时制,如 09、13、18)# 多分支判断时间段
if [ $hour -ge 8 -a $hour -le 12 ]; then # -a 表示“并且”echo "当前时间段:上午(8:00-12:00)"
elif [ $hour -gt 12 -a $hour -le 14 ]; thenecho "当前时间段:中午(12:00-14:00)"
elif [ $hour -gt 14 -a $hour -le 18 ]; thenecho "当前时间段:下午(14:00-18:00)"
elseecho "当前时间段:晚上(18:00-次日8:00)"
fi
实战案例6:判断用户是否存在(仅提示存在,不存在不处理)
(1)id $用户名
#!/bin/bash
read -p "请输入要检查的用户名:" username
# 用 id 命令判断用户是否存在(&> /dev/null 隐藏命令输出)
if id $username &> /dev/null; thenecho "用户 $username 已存在"
fi
- id $username 命令的作用是查询用户 $username 的信息,用户存在状态码为 0。
- &> /dev/null 的作用是隐藏命令的输出(无论成功或失败的信息都不显示在屏幕上),只保留退出状态码供 if 判断。
- if 语句会根据命令的退出状态码判断:状态码为 0 则进入 then 分支,非 0 则跳过(或进入 else 分支)。
(2)grep ^用户名: /etc/passwd
/etc/passwd 文件的格式是每行对应一个用户,字段用冒号分隔。若直接用 grep "用户名" /etc/passwd 而不细化,可能匹配到非用户名字段包含该字符串的行。
实战案例7:比较uid和gid
#!/bin/bash# 检查是否提供了用户名参数
if [ $# -ne 1 ]; thenecho "请输入用户名作为参数: $0 <用户名>"exit 1
fiusername="$1"# 检查用户是否存在
if ! id -u "$username" >/dev/null 2>&1; thenecho "错误: 用户 '$username' 不存在"exit 1
fi# 获取UID和GID
uid=$(id -u "$username")
gid=$(id -g "$username")# 比较UID和GID
if [ "$uid" -eq "$gid" ]; thenecho "gooduser"
elseecho "baduser"
fi
实战案例8:
- 备份所有.repo 文件到 backup 目录
- 挂载光盘到 /mnt/cdrom
- 创建本地源配置文件
实战案例9:提示用户输入数据,判断是否输入LInux(linux)显示红帽,输入windows(Windows)显示微软,输入macos(Macos)显示苹果,其他
实战案例10:检测磁盘。用户输入磁盘的挂载点,判断硬盘占用容量70%>显示警告,否则正常
实战案例11:检测磁盘使用率(超过 70% 警告)
#!/bin/bash
# 脚本路径:/opt/work/check_disk.sh
read -p "请输入要检测的磁盘(如 /dev/sda1):" disk_name# 第一步:先判断磁盘是否存在(用 df -hT 过滤磁盘名)
df -hT | grep -w "${disk_name}$" &> /dev/null # -w 匹配完整单词,避免误判
if [ $? -ne 0 ]; thenecho "错误:磁盘 ${disk_name} 不存在,请检查输入(可用 df -hT 查看所有磁盘)"exit 1 # 退出脚本,状态码 1 表示错误
fi# 第二步:提取磁盘使用率(去掉 % 符号,转为纯数字)
# df 输出示例:/dev/sda1 ext4 50G 30G 20G 60% /
usage=$(df -hT | grep -w "${disk_name}$" | awk '{print $6}' | awk -F% '{print $1}')# 第三步:判断使用率是否超过 70%
if [ $usage -gt 70 ]; thenecho "警告:磁盘 ${disk_name} 使用率已达 ${usage}%,超过阈值 70%,请及时清理!"
elseecho "正常:磁盘 ${disk_name} 使用率为 ${usage}%,低于阈值 70%"
fi
实战案例12:检测mysqlio线程的状态,若正常则输出1,否则显示0
实战案例13:输入服务名称,判断是否运行
服务名称;pid 3,端口,输出服务启动时间
(三)case 条件判断:高效处理等值匹配
当需要 “判断变量是否等于多个固定值” 时(如根据参数执行不同操作、根据输入选择功能),case 判断比 if 更简洁(避免多个 elif 嵌套),核心是# “等值匹配”。
1. case 判断的基础语法
case $变量 in # 变量可以是参数、用户输入等值1) # 若变量等于“值1”执行操作1;; # 结束当前分支(必须写,两个分号)值2|值3) # 若变量等于“值2”或“值3”(用 | 分隔多个值)执行操作2;;*) # 默认分支(所有值都不匹配时执行,类似 if 的 else)执行操作3;;
esac # 结束 case 判断(case 的反向拼写)
2. 实战案例:基于参数的多功能脚本
案例 1:根据输入的操作系统,输出对应厂商
#!/bin/bash
read -p "请输入操作系统名称(如 Linux/Windows/Mac):" oscase $os inLinux|linux|LINUX) # 支持大小写(用 | 分隔多个等值)echo "操作系统厂商:红帽、Ubuntu 等(基于 Linux 内核)";;Windows|windows|WINDOWS)echo "操作系统厂商:微软(Microsoft)";;Mac|mac|macOS)echo "操作系统厂商:苹果(Apple)";;*) # 不匹配任何值时的默认处理echo "暂不支持该操作系统:$os(仅支持 Linux/Windows/Mac)";;
esac
案例 2:用位置变量实现 “脚本参数控制”
Shell 中的 位置变量 用于获取脚本执行时的参数(如 ./script.sh arg1 arg2
中,$1=arg1
,$2=arg2
),结合 case 可实现 “多参数功能”:
$0
:脚本自身名称(如./check_process.sh
中,$0=check_process.sh
);$1~${n}
:第 1 到第 n 个参数(${10}
需加大括号,避免与$1
混淆);$#
:参数的总个数(判断是否传入参数)。
实战案例:检测进程状态(支持参数指定进程名)
#!/bin/bash
# 脚本路径:/opt/work/check_process.sh
# 用法:./check_process.sh 进程名(如 ./check_process.sh nginx)# 第一步:判断是否传入参数($# 为 0 表示无参数)
if [ $# -eq 0 ]; thenecho "用法错误:请指定要检测的进程名"echo "示例:$0 nginx(检测 nginx 进程)、$0 mysql(检测 mysql 进程)"exit 1
fiprocess=$1 # 将第一个参数赋值给 process 变量# 第二步:用 case 判断进程状态(先通过 ps 检查进程是否存在)
ps -elf | grep -w "$process" | grep -v "grep" &> /dev/null # 排除 grep 自身
if [ $? -ne 0 ]; thenecho "状态:进程 ${process} 未启动"
else# 提取进程详情:PID、启动时间、端口(需安装 netstat)proc_id=$(pidof $process) # 获取进程 PID(多个 PID 用空格分隔)start_time=$(ps -p $proc_id -o lstart=) # 获取启动时间(如 Wed Jul 10 09:30:00 2024)# 提取端口(仅监听端口,