Linux Shell case 语句完全指南:替代复杂 if-elif,让分支逻辑更清晰
概述
在 Shell 脚本中,当需要对同一个变量的多个值进行判断时(比如根据用户身份、命令参数、状态码执行不同逻辑),用 if-elif-else 会写出冗长且难维护的代码。
举个典型场景:根据当前登录用户($USER)输出不同欢迎语,用 if-elif 实现如下:
# 臃肿的 if-elif 写法
if [ $USER == "rich" ]; then
echo "Welcome $USER! Enjoy your visit."
elif [ $USER == "roger" ]; then
echo "Welcome $USER! Enjoy your visit."
elif [ $USER == "barbara" ]; then
echo "Hi $USER! Glad you're here."
elif [ $USER == "tim" ]; then
echo "Hi $USER! Glad you're here."
elif [ $USER == "testing" ]; then
echo "Please log out after testing."
else
echo "Sorry, you're not allowed."
fi
这种写法的问题很明显:
- 重复判断同一变量($USER),逻辑冗余;
- 新增 / 删除条件时需修改大量 elif,维护成本高;
- 代码可读性差,难以快速定位对应分支。
而 case 语句 正是为解决这类 “单变量多分支” 场景设计的 —— 它用 “模式匹配” 替代重复判断,代码结构更清晰,维护更高效。
一、case 语句基础:语法规则与核心概念
case 语句通过 “变量与模式匹配” 执行对应命令,语法格式固定,需严格遵循括号、分号等符号的使用规则。
1. 标准语法格式
case 变量名 in
模式1 | 模式2) # 多个模式用 "|" 分隔(表示“或”)
命令1
命令2
;; # 用 ";;" 结束当前分支(必须写,否则会继续执行下一分支)
模式3)
命令3
;;
*) # 通配模式,匹配所有未命中的情况(类似 else)
默认命令
;; # 最后一个分支的 ";;" 可省略,但建议保留以保持格式统一
esac # case 的反向拼写,标志语句结束
关键符号说明
符号 | 作用 |
---|---|
in | 分隔 “变量名” 和 “模式列表”,表示 “变量在以下模式中匹配” |
| | 分隔多个模式,相当于逻辑 “或”(如 rich|roger 表示 “变量是 rich 或 roger”) |
) | 标志模式的结束,模式与命令之间必须用 ) 分隔 |
;; | 结束当前分支,防止 “分支穿透”(若不写,会继续执行下一分支的命令) |
* | 通配符,匹配所有未被前面模式命中的情况(必须放在最后,作为默认分支) |
esac | case 的反向拼写,必须作为 case 语句的结尾 |
2. 模式匹配规则
case 支持通配符模式,不仅能匹配固定值,还能匹配模糊规则,灵活性远超 if-elif 的精确判断。常见模式类型:
模式类型 | 示例 | 匹配说明 |
---|---|---|
固定值模式 | rich、123 | 精确匹配变量的固定值(如变量为 “rich” 时命中) |
多模式 “或” | rich|roger | 变量匹配任意一个模式即命中(如变量为 “rich” 或 “roger” 都生效) |
通配符模式 | *.sh、[0-9] | 用 Shell 通配符匹配: - _ 匹配任意字符(如 _.sh 匹配所有 .sh 文件); - ? 匹配单个字符(如 a?c 匹配 abc、acc); - [] 匹配指定字符集(如 [0-9] 匹配单个数字) |
空模式 | "" | 匹配变量为空的情况(如 case $var in "" ) echo "var is empty" ;; esac) |
二、实战案例:从基础到进阶场景
通过 4 个递进案例,掌握 case 语句在不同场景下的使用方法,覆盖日常开发中 90% 以上的需求。
案例 1:简化用户欢迎语(替代开篇的 if-elif)
将 “根据 $USER 输出欢迎语” 的需求用 case 实现,代码更简洁:
#!/bin/bash
# 用 case 实现用户欢迎逻辑
case $USER in
# 模式1:匹配 rich 或 roger
rich | roger)
echo "Welcome $USER!"
echo "Please enjoy your visit."
;;
# 模式2:匹配 barbara 或 tim
barbara | tim)
echo "Hi there, $USER!"
echo "We're glad you could join us."
;;
# 模式3:匹配 testing
testing)
echo "Please log out when done with test."
;;
# 通配模式:匹配所有其他用户
*)
echo "Sorry, $USER. You are not allowed here."
;;
esac
执行效果(当前用户为 roger 时):
Welcome roger!
Please enjoy your visit.
相比 if-elif,case 把 “同逻辑的模式”(如 rich 和 roger)合并,代码行数减少 40%,且新增用户时只需在对应模式后加 | 新用户,维护更高效。
案例 2:处理脚本命令行参数(常见于工具脚本)
编写脚本时,常需要通过命令行参数(如 ./script.sh start、./script.sh stop)执行不同操作,case 是处理这类场景的最佳选择。
示例:实现一个 “服务控制脚本”,支持 start/stop/restart/status 4 个参数:
#!/bin/bash
# 服务控制脚本:./service.sh start/stop/restart/status
# 若未传参数,提示用法
if [ $# -eq 0 ]; then
echo "用法:$0 [start|stop|restart|status]"
exit 1
fi
# 用 case 处理参数($1 表示第一个命令行参数)
case $1 in
start)
echo "正在启动服务..."
# 实际场景中会执行服务启动命令(如 systemctl start nginx)
sleep 1
echo "服务启动完成!"
;;
stop)
echo "正在停止服务..."
# 实际场景中会执行服务停止命令(如 systemctl stop nginx)
sleep 1
echo "服务停止完成!"
;;
restart)
echo "正在重启服务..."
# 调用当前脚本的 stop 和 start 分支(复用逻辑)
$0 stop # $0 表示当前脚本路径
$0 start
echo "服务重启完成!"
;;
status)
echo "查询服务状态..."
# 实际场景中会执行状态查询命令(如 systemctl status nginx)
echo "服务正在运行中(模拟)"
;;
*)
# 匹配无效参数
echo "错误:无效参数 '$1'"
echo "用法:$0 [start|stop|restart|status]"
exit 1
;;
esac
执行效果:
# 启动服务
./service.sh start
# 输出:
# 正在启动服务...
# 服务启动完成!
# 无效参数
./service.sh hello
# 输出:
# 错误:无效参数 'hello'
# 用法:./service.sh [start|stop|restart|status]
案例 3:匹配模糊模式(通配符的灵活使用)
case 的模式支持 Shell 通配符,可实现 “模糊匹配” 场景。例如:判断输入的文件名是否为 .sh(Shell 脚本)、.txt(文本文件)或其他类型。
示例:
#!/bin/bash
# 判断文件类型:输入文件名,输出对应的文件类型
read -p "请输入文件名:" filename
case $filename in
# 模式1:匹配所有 .sh 结尾的文件
*.sh)
echo "$filename 是 Shell 脚本文件"
;;
# 模式2:匹配所有 .txt 结尾的文件
*.txt)
echo "$filename 是文本文件"
;;
# 模式3:匹配单个数字(如 1、5、9)
[0-9])
echo "$filename 是单个数字,不是文件"
;;
# 通配模式:其他情况
*)
echo "$filename 是未知类型文件"
;;
esac
执行效果:
请输入文件名:test.sh
test.sh 是 Shell 脚本文件
请输入文件名:note.txt
note.txt 是文本文件
请输入文件名:5
5 是单个数字,不是文件
案例 4:分支穿透(不写;;实现多分支共享逻辑)
默认情况下,case 的每个分支必须用 ;; 结束,否则会 “穿透” 到下一分支(继续执行下一分支的命令)。这种 “穿透” 特性可用于 “多模式共享部分命令” 的场景。
示例:实现一个 “等级判断” 脚本,VIP1 和 VIP2 共享 “基础特权”,VIP2 额外拥有 “高级特权”:
#!/bin/bash
# 会员等级特权判断
read -p "请输入你的会员等级(VIP1/VIP2/普通用户):" level
case $level in
VIP2)
echo "✅ 你拥有高级特权:专属客服+免费提现"
# 不写 ;;,继续执行下一分支(VIP1 的逻辑)
VIP1)
echo "✅ 你拥有基础特权:积分翻倍+免广告"
;; # 结束分支,避免继续穿透
普通用户)
echo "ℹ️ 你拥有普通特权:基础功能使用"
;;
*)
echo "❌ 无效的会员等级"
;;
esac
执行效果(输入 VIP2 时):
请输入你的会员等级(VIP1/VIP2/普通用户):VIP2
✅ 你拥有高级特权:专属客服+免费提现
✅ 你拥有基础特权:积分翻倍+免广告
输入 VIP1 时,仅执行 VIP1 分支的命令;输入 VIP2 时,先执行 VIP2 分支,再穿透到 VIP1 分支,实现 “高级特权包含基础特权” 的逻辑,无需重复写代码。
三、常见问题与避坑指南
case 语句的语法细节较多,新手容易因符号遗漏或模式顺序错误导致脚本报错,以下是高频问题的解决方案:
问题现象 | 可能原因 | 解决方案 |
---|---|---|
执行脚本提示 “syntax error near unexpected token `esac'” | 1. 分支未写 ;;; 2. 缺少 esac 结尾 | 1. 检查每个分支末尾是否有 ;;(除了刻意穿透的分支); 2. 确保 case 语句以 esac 结束,且无拼写错误。 |
多个模式匹配时,只执行第一个分支 | 模式顺序错误(通配模式 * 放在了前面) | * 必须作为最后一个模式(通配所有未命中的情况),若放在前面,会优先匹配所有变量,导致后面的模式失效。 |
变量含空格时,模式匹配失败 | 变量未加双引号,被拆分为多个字段 | 在 case 语句中,变量名建议加双引号(如 case "$USER" in),避免变量含空格时被 Shell 解析为多个参数。 |
分支穿透后无法停止,执行所有分支 | 忘记在需要停止的分支末尾加 ;; | 除了刻意穿透的分支(如案例 4 的 VIP2),其他分支必须用 ;; 结束,防止无限制穿透。 |
通配符模式(如 *.sh)不生效 | 变量值不是预期的格式(如不含 .sh 后缀) | 先通过 echo "变量值:$filename" 打印变量,确认变量格式是否符合模式要求;若变量是路径(如 ./test.sh),*.sh 模式仍能匹配,无需额外处理。 |
四、总结:case 与 if-elif 的选择建议
case 和 if-elif 都是 Shell 分支语句,但适用场景不同,需根据需求选择:
对比维度 | case 语句 | if-elif 语句 |
---|---|---|
核心优势 | 1. 单变量多分支,代码清晰; 2. 支持通配符模式; 3. 分支穿透实现共享逻辑 | 1. 支持多变量判断(如同时判断 $a 和 $b); 2. 支持复杂条件(如 >、<、&&、 ) |
适用场景 | - 处理命令行参数(如 start/stop); - 单变量的多值匹配; - 模糊模式匹配(如文件后缀) | - 多变量组合判断(如 if [ $a -gt 10 ] && [ $b -lt 20 ]); - 复杂比较逻辑(如数值大小、字符串长度) |
代码可读性 | 变量与模式对应明确,适合 3 个以上分支 | 分支少(1-2 个)时更简洁,多分支时冗余 |
一句话总结:
- 当需要判断同一个变量的多个值或模糊模式时,优先用 case 语句;
- 当需要判断多个变量或复杂条件(如数值比较、逻辑与 / 或)时,用 if-elif 语句。
掌握 case 语句,能让你在编写 Shell 脚本时,轻松处理多分支场景,代码更易读、更易维护,是从 “Shell 入门” 到 “脚本优化” 的关键一步。