目录
一、引言:当if-else不够用了
二、if-else 语法完整回顾
2.1 三种基本形态
2.2 单行写法(命令式风格)
2.3 嵌套if:多层条件判断
2.4 if-else的适用场景
三、case语句:多分支匹配的利器
3.1 基本语法
3.2 用case重写服务管理脚本
3.3 case支持通配符模式
3.4 用 | 合并多个匹配项
3.5 case运用场景总结
四、实战:编写完整的服务管理脚本
脚本设计要点
五、case与if-else的选择指南
六、本篇小结
动手练习
七、下篇预告
一、引言:当if-else不够用了
先看一个需求:写一个脚本,根据用户输入的第一个参数执行不同操作:
bash
./script.sh start → 启动服务 ./script.sh stop → 停止服务 ./script.sh restart → 重启服务 ./script.sh status → 查看状态
用if-elif-else实现:
bash
#!/bin/bash if [[ "$1" = "start" ]]; then echo "启动服务..." elif [[ "$1" = "stop" ]]; then echo "停止服务..." elif [[ "$1" = "restart" ]]; then echo "重启服务..." elif [[ "$1" = "status" ]]; then echo "查看状态..." else echo "用法: $0 {start|stop|restart|status}" fi功能没问题,但每增加一个选项就要加一段elif,代码冗长。这时就该case语句登场了。
二、if-else 语法完整回顾
2.1 三种基本形态
bash
# 形态一:单分支 if 条件; then 命令 fi # 形态二:双分支 if 条件; then 命令 else 命令 fi # 形态三:多分支 if 条件1; then 命令1 elif 条件2; then 命令2 elif 条件3; then 命令3 else 命令4 fi
2.2 单行写法(命令式风格)
bash
# 只关心"条件成立时执行" [[ -f /etc/nginx/nginx.conf ]] && echo "配置文件存在" # 只关心"条件不成立时执行" [[ -f /etc/nginx/nginx.conf ]] || { echo "配置文件缺失"; exit 1; } # 三元表达式模拟 [[ $USER = "root" ]] && prefix="#" || prefix="$" echo "${prefix} 当前用户是 ${USER}"2.3 嵌套if:多层条件判断
bash
#!/bin/bash if [[ -d "$1" ]]; then if [[ -r "$1" ]] && [[ -x "$1" ]]; then echo "目录可访问" else echo "目录存在但权限不足" fi else echo "目录不存在" fi
2.4 if-else的适用场景
条件之间有层级关系(先判断A,再判断B,且B是A的子集)
条件逻辑是非此即彼(成功/失败、存在/不存在)
分支数量不超过3个时可用
if-elif
三、case语句:多分支匹配的利器
3.1 基本语法
bash
case 变量 in 模式1) 命令1 ;; 模式2) 命令2 ;; 模式3|模式4|模式5) 命令3 # 匹配多个模式,任一个成立都执行 ;; *) 默认命令 ;; esac
语法要点:
case和esac成对(esac是case的反写)每个分支必须以
;;结束(部分Bash也支持;;&和;&)*是默认分支(类似else),必须放在最后|用来在一个分支中匹配多个模式
3.2 用case重写服务管理脚本
bash
#!/bin/bash case "$1" in start) echo "启动服务..." ;; stop) echo "停止服务..." ;; restart) echo "重启服务..." ;; status) echo "查看状态..." ;; *) echo "用法: $0 {start|stop|restart|status}" exit 1 ;; esac相比if-elif的版本,case的优势很明显:
不需要反复写
"$1" = "xxx"视觉上更清晰,一目了然四个分支
多个匹配项可以用
|合并(见3.4节)
3.3 case支持通配符模式
case的匹配不是简单的字符串比较,而是模式匹配(通配符风格):
bash
#!/bin/bash read -p "请输入一个字符: " char case "$char" in [0-9]) echo "你输入了一个数字" ;; [a-z]) echo "你输入了一个小写字母" ;; [A-Z]) echo "你输入了一个大写字母" ;; ?) echo "你输入了一个其他单个字符" ;; *) echo "你输入了多个字符" ;; esac
这个特性在需要根据模式匹配做分支时特别有用。
3.4 用 | 合并多个匹配项
bash
#!/bin/bash read -p "是否继续?(y/n/yes/no): " answer case "${answer,,}" in # ${var,,} 把变量值转成小写 y|yes) echo "继续执行..." ;; n|no) echo "已取消"; exit 0 ;; *) echo "请输入 y 或 n" ;; esac3.5 case运用场景总结
| 场景 | 推荐 | 理由 |
|---|---|---|
| 服务管理脚本(start/stop/restart) | case | 经典用法,可读性最好 |
| 用户交互选择(y/n、1/2/3/4) | case | 简洁明了 |
| 按模式匹配(数字/字母范围) | case | if-else不方便处理模式 |
| 条件有优先级或依赖关系 | if-else | 例如必须先判断目录存在才检查权限 |
| 只判断true/false | if | 更直观 |
四、实战:编写完整的服务管理脚本
把前面学到的变量、条件判断、case整合起来,写一个生产级简单的服务管理脚本:
bash
#!/bin/bash # 服务管理脚本 demo-daemon.sh SERVICE_NAME="demo" PID_FILE="/var/run/${SERVICE_NAME}.pid" LOG_FILE="/var/log/${SERVICE_NAME}.log" # 启动函数 start_service() { # 检查是否已在运行 if [[ -f "$PID_FILE" ]]; then pid=$(cat "$PID_FILE") if kill -0 "$pid" 2>/dev/null; then echo "服务已在运行 (PID: $pid)" return 1 else # PID文件存在但进程不存在,清理残留 rm -f "$PID_FILE" fi fi # 启动服务(以sleep 3600模拟一个常驻程序) echo "正在启动 ${SERVICE_NAME}..." nohup sleep 3600 >> "$LOG_FILE" 2>&1 & echo $! > "$PID_FILE" echo "服务已启动 (PID: $!)" } # 停止函数 stop_service() { if [[ ! -f "$PID_FILE" ]]; then echo "服务未在运行" return 1 fi pid=$(cat "$PID_FILE") if kill "$pid" 2>/dev/null; then echo "正在停止服务 (PID: $pid)..." sleep 1 # 如果还没死,强制杀死 kill -9 "$pid" 2>/dev/null rm -f "$PID_FILE" echo "服务已停止" else echo "无法停止服务,可能已经退出" rm -f "$PID_FILE" fi } # 状态检查函数 check_status() { if [[ -f "$PID_FILE" ]]; then pid=$(cat "$PID_FILE") if kill -0 "$pid" 2>/dev/null; then echo "服务正在运行 (PID: $pid)" return 0 fi fi echo "服务已停止" return 1 } # 主流程:根据参数分发 case "$1" in start) start_service ;; stop) stop_service ;; restart) echo "正在重启 ${SERVICE_NAME}..." stop_service sleep 2 start_service ;; status) check_status ;; *) echo "用法: $0 {start|stop|restart|status}" exit 1 ;; esac演示:
bash
chmod +x demo-daemon.sh sudo ./demo-daemon.sh start # 启动服务 sudo ./demo-daemon.sh status # 查看状态 sudo ./demo-daemon.sh restart # 重启服务 sudo ./demo-daemon.sh stop # 停止服务 ./demo-daemon.sh # 不带参数,显示用法
脚本设计要点
(1)PID文件的作用
用/var/run/demo.pid文件记录服务进程的PID。这是Linux守护进程的标准做法:
启动时把
$!(最后一个后台进程的PID)写入文件停止时读取PID文件,kill对应进程
状态检查用
kill -0判断进程是否存在(不发信号,仅检查)
(2)返回值约定
它符合Linux命令行惯例:成功返回0,失败返回非0。check_status返回0表示正在运行,1表示已停止。
(3)分支设计
start分支先检查是否已运行(幂等性),stop分支处理PID文件残留的情况,restart简单复用前两个函数避免重复代码。
五、case与if-else的选择指南
| 特征 | 选case | 选if-else |
|---|---|---|
| 分支基于精确值匹配 | ✅ | ❌ |
| 分支基于范围或条件 | ❌ | ✅ |
| 分支数量3个以上 | ✅ | ❌ |
| 条件之间有依赖关系 | ❌ | ✅ |
| 模式匹配(通配符) | ✅ | ❌ |
总结:如果变量有2-3个明确取值,优先用case;如果需要复杂的逻辑判断(文件是否存在 + 是否可写 + 大小是否超过阈值),用if。
六、本篇小结
if-else:适合条件判断,特别是条件之间有依赖关系(先判断存在再判断权限)。
case语句:
语法:
case 变量 in 模式) 命令 ;; esac支持通配符模式匹配(
*、?、[0-9])多个模式用
|合并最适合上述服务管理脚本的子命令分发
核心差异:if判断条件是否为真,case判断变量匹配哪个模式。
动手练习
bash
#!/bin/bash # 练习:编写一个文件类型识别脚本 FILE="$1" # 检查是否传入参数 if [[ -z "$FILE" ]]; then echo "用法: $0 <文件路径>" exit 1 fi # 检查文件是否存在 if [[ ! -e "$FILE" ]]; then echo "错误: $FILE 不存在" exit 1 fi # 根据文件扩展名判断类型 case "${FILE##*.}" in sh|bash) echo "Shell脚本" ;; py) echo "Python脚本" ;; js) echo "JavaScript文件" ;; html|htm) echo "HTML文件" ;; css) echo "样式表文件" ;; json) echo "JSON数据文件" ;; yaml|yml) echo "YAML配置文件" ;; conf|cfg) echo "配置文件" ;; log) echo "日志文件" ;; gz|bz2|xz) echo "压缩包" ;; *) echo "未知文件类型: .${FILE##*.}" ;; esac # 输出文件大小信息 echo "文件大小: $(du -h "$FILE" | cut -f1)"保存为filetype.sh,测试:
bash
chmod +x filetype.sh ./filetype.sh test.py → Python脚本 ./filetype.sh /etc/nginx/nginx.conf → 配置文件
七、下篇预告
掌握了判断和分支,脚本已经能做出“智能决策”。但很多任务需要重复执行——比如遍历所有.log文件、等待某个条件成立、循环处理直到完成。
下一篇我们将进入循环结构的世界,学习:
for循环遍历列表和文件while循环读取文件和等待条件break和continue控制循环流程批量重命名、批量处理日志等实战案例
延伸思考:case中的;;、;;&和;&有什么区别?;;匹配后退出case块,;;&继续匹配下一个模式(Bash 4.0+),;&无条件继续执行下一个分支(几乎不用)。这种设计让case具有了“贯穿”能力,但也增加了行为复杂度。日常使用保持;;即可,高级场景再查阅文档。