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 入门” 迈向 “脚本优化” 的关键一步。