🔍搜索⌘K

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

这种写法毛病很明显:

  1. 一直在重复判断同一个变量($USER),逻辑很啰嗦;
  2. 要加或删一个条件,得改一大堆 elif,维护起来费劲;
  3. 可读性差,想找个分支在哪都不好找。

而 case 语句 就是专门为了解决这种 “单变量多分支” 场景而生的 —— 它用 “模式匹配” 替代重复判断,代码结构更清晰,维护更高效。

一、case 语句基础:语法规则与核心概念

case 语句靠 “变量和模式去匹配” 来执行对应的命令,语法格式是固定的,括号、分号这些符号得按规矩来。

1. 标准语法格式

case 变量名 in
  模式1 | 模式2)  # 多个模式用 "|" 分隔(表示“或”)
    命令1
    命令2
    ;;  # 用 ";;" 结束当前分支(必须写,否则会继续执行下一分支)
  模式3)
    命令3
    ;;
  *)  # 通配模式,匹配所有未命中的情况(类似 else)
    默认命令
    ;;  # 最后一个分支的 ";;" 可省略,但建议保留以保持格式统一
esac  # case 的反向拼写,标志语句结束

关键符号说明

符号作用
in分隔 “变量名” 和 “模式列表”,表示 “变量在以下模式中匹配”
|分隔多个模式,相当于逻辑 “或”(如 rich|roger 表示 “变量是 rich 或 roger”)
)标志模式的结束,模式与命令之间必须用 ) 分隔
;;结束当前分支,防止 “分支穿透”(若不写,会继续执行下一分支的命令)
*通配符,匹配所有未被前面模式命中的情况(必须放在最后,作为默认分支)
esaccase 的反向拼写,必须作为 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 入门” 迈向 “脚本优化” 的关键一步。