🔍搜索⌘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 入门” 到 “脚本优化” 的关键一步。