当脚本开始变复杂,成百上千行的 if…elif…else 会让逻辑变得臃肿、难读、易错。
Shell 的 case 语句正是为这种“单变量多分支”场景而生 —— 语法简洁、模式匹配强劲、维护性高。
本文从基础语法出发,结合常见运维与自动化场景(交互菜单、服务管理、文件分类、参数解析等),逐步讲解通配符、并行匹配、Bash4 的 ;;& / ;& 等进阶用法,并通过可直接拿去运行的脚本示例,让你既能“看懂”也能“用得上”。
无论你是刚入门的脚本写作者,还是需要把脚本变得稳健可维护的运维工程师,这篇指南都会是你常驻书签的一篇实用教程。
01
什么是 case 语句?
case 语句是一种多分支选择结构,它允许你将一个变量或表达式的值与一系列模式进行匹配,并执行与第一个成功匹配的模式相关联的代码块。
它在功能上类似于 C 或 JavaScript 等语言中的 switch 语句,但语法和行为上具有 Shell 的独特之处。
使用 case 语句的主要优点在于,当处理多达三、四个或更多分支条件时,它能显著提升代码的可读性和可维护性。
1
case 语句的基本语法
case 语句的结构清晰且富有逻辑性。下面是其标准语法:
case <变量或表达式> in
模式1)
# 匹配模式1时执行的命令
;;
模式2|模式3)
# 匹配模式2或模式3时执行的命令
;;
模式N)
# 匹配模式N时执行的命令
;;
*)
# 所有模式都未匹配时执行的默认命令
;;
esac
让我们来分解这个语法的各个部分:
• case … in
语句的起始部分。
<变量或表达式> 是您希望进行判断的目标。强烈提议始终用双引号将其包裹(如 case “$var” in),以防止因变量为空或包含空格而引发的意外分词问题。
• 模式)
定义一个用于匹配的模式,后面必须紧跟一个右括号 ).
• 命令块
在模式匹配成功后需要执行的一系列命令。
• ;; (双分号)
这是 case 语句中至关重大的分隔符。它标志着一个命令块的结束,其作用类似于 C 语言中的 break,一旦执行到 ;;,整个 case 语句就会立即终止,后续的模式将不再被检测。
• | (竖线)
在模式中充当“或”操作符,允许您将多个模式组合在同一个分支中,满足其中任意一个即可。
• *)
这是一个特殊的通配符模式,可以匹配任何内容。它一般被放在 case 语句的末尾,作为“默认”分支,用于处理所有未被前面模式捕获的情况,从而增强脚本的健壮性。
• esac
case 语句的结束标记(即 case 的反写)。
2
case 语句的工作原理
case 语句的执行流程超级直接:
1)第一,Shell 会对 case 后面的 expression 进行求值。
2)然后,Shell 从上到下,依次将求得的值与每个 pattern 进行匹配。
3)一旦找到第一个匹配的 pattern,就会执行该模式对应的 commands,直到遇到 ;; 为止。
4)执行完毕后,整个 case 语句结束。
5)如果没有任何一个 pattern 能够匹配 expression 的值,并且存在 *) 默认分支,那么 *) 分支的命令将被执行。
6)如果没有任何模式匹配,也没有 *) 分支,那么 case 语句将不执行任何操作并静默退出。
02
实际应用模板
理论结合实践是最好的学习方式。
下面通过几个常见的应用场景来展示 case 语句的强劲功能。
示例 1:处理用户交互式输入
case 语句超级适合用于创建交互式菜单,根据用户的输入执行不同操作。
#!/bin/bash
echo "请选择您想安装的应用: (apache, mysql, php)"
read -r app
case $app in
"apache")
echo "正在准备安装 Apache..."
# 此处添加安装 Apache 的命令
;;
"mysql")
echo "正在准备安装 MySQL..."
# 此处添加安装 MySQL 的命令
;;
"php")
echo "正在准备安装 PHP..."
# 此处添加安装 PHP 的命令
;;
*)
echo "错误:无效的选择 '$app'。"
;;
esac
在这个例子中,脚本读取用户输入,并使用 case 语句判断用户的选择,然后执行相应的操作或给出错误提示。
示例 2:系统服务管理脚本
在 Linux 系统中,/etc/init.d 目录下的许多启动脚本都广泛使用 case 语句来处理 start、stop、restart 等参数。
#!/bin/bash
# 用法: ./service.sh [start|stop|restart]
case"$1" in
start)
echo "正在启动服务..."
# 启动服务的命令
;;
stop)
echo "正在停止服务..."
# 停止服务的命令
;;
restart)
echo "正在重启服务..."
# 重启服务的命令
;;
*)
echo "用法: $0 {start|stop|restart}"
exit 1
;;
esac
这里,$1 代表传递给脚本的第一个命令行参数。
case 语句判断这个参数的值,并执行相应的服务操作。
03
高级技巧与模式匹配
case 语句的真正威力体目前其灵活的模式匹配能力上。
1
使用 | 匹配多个模式
如果你希望多个不同的模式执行一样的操作,可以使用 | (管道符) 将它们连接起来,这相当于逻辑上的“或”。
#!/bin/bash
read -p "请输入一个月份的英文缩写 (例如: Jan, Feb): " month
case $month in
Feb)
echo "$month 有 28 或 29 天。"
;;
Apr|Jun|Sep|Nov)
echo "$month 有 30 天。"
;;
Jan|Mar|May|Jul|Aug|Oct|Dec)
echo "$month 有 31 天。"
;;
*)
echo "无效的月份: $month"
;;
esac
在这个例子中,多个具有一样天数的月份被归并在同一个分支中,使代码更加简洁。
2
使用通配符进行模式匹配
case 语句的真正威力在于其灵活的模式匹配,它支持标准的 Shell 通配符,让您能够编写出极其灵活的匹配规则。
* (星号)
匹配任意长度(包括零长度)的任意字符序列。
• 示例
检查文件名后缀。
filename="document.pdf"
case "$filename" in
*.txt) echo "这是一个文本文档。";;
*.pdf) echo "这是一个 PDF 文件。";;
*.jpg|*.png) echo "这是一个图像文件。";;
*) echo "未知文件类型。";;
esac
- ? (问号)
匹配任意单个字符。
• 示例
匹配固定长度的字符串。
code="A101"
case "$code" in
A???) echo "这是一个A开头的4位代码。";;
B???) echo "这是一个B开头的4位代码。";;
*) echo "不符合规范的代码。";;
esac
[…] (字符集)
匹配方括号中指定的任意一个字符。
可以使用连字符 – 来表明一个范围。
• 示例
判断输入的首字母类型。
read -p "输入一个单词: " word
case "$word" in
[aeiouAEIOU]*) echo "单词以元音字母开头。";;
[a-zA-Z]*) echo "单词以辅音字母开头。";;
[0-9]*) echo "输入以数字开头。";;
*) echo "输入以特殊符号开头。";;
esac
3
结合 for 循环进行文件分类
case 语句可以与 for 循环结合,实现强劲的批处理功能,例如根据文件扩展名对文件进行分类。
#!/bin/bash
for filename in *; do
case $filename in
*.jpg|*.jpeg|*.png|*.gif)
echo "$filename 是一个图片文件。"
;;
*.sh)
echo "$filename 是一个 Shell 脚本。"
;;
*.txt|*.md)
echo "$filename 是一个文本文档。"
;;
*)
echo "$filename 是一个未知类型的文件。"
;;
esac
done
此脚本会遍历当前目录下的所有文件,并根据其扩展名进行分类输出。
04
case vs. if-elif-else:如何选择?
这是一个常见的问题。
虽然两者都能实现条件分支,但它们的适用场景有所不同。

代码对比: 当根据同一变量的值进行多路选择时,case 的优势超级明显。
# 使用 if-elif-else
if [ "$action" = "start" ]; then
echo "Starting..."
elif [ "$action" = "stop" ]; then
echo "Stopping..."
elif [ "$action" = "restart" ]; then
echo "Restarting..."
else
echo "Invalid action."
fi
# 使用 case,结构更清晰
case"$action" in
start) echo "Starting..." ;;
stop) echo "Stopping..." ;;
restart) echo "Restarting..." ;;
*) echo "Invalid action." ;;
esac
05
黄金组合:while 循环与
case 语句
当 case 语句与 while 循环结合使用时,它们能发挥出 1+1>2 的效果,轻松创建出可以反复操作的交互式菜单。
while true 会创建一个无限循环,确保菜单在用户选择退出前一直显示;而 case 则完美地承担了处理用户每一次选择的任务。
下面,我们将通过两个详细的实验来实践这一强劲组合。
1
动手实验一:构建带有功能菜单的跳板机
这是 VACUUM 中的“核武器”,威力巨大,但副作用也同样致命。
核心作用
创建一个 Shell 脚本,该脚本显示一个包含多个服务器选项的菜单。
用户选择一个数字后,脚本能自动通过 SSH 登录到对应的服务器。
准备工作
为了获得最佳体验,提议配置好本地到目标服务器的 SSH 密钥认证,这样登录时就无需手动输入密码。
脚本代码 (jumpserver.sh)
#!/bin/bash
# 简单的跳板机脚本
# 使用一个无限循环来持续显示菜单,直到用户选择退出
whiletrue; do
# 清屏,让菜单更清晰
clear
echo "========================================"
echo " 跳板机登录菜单"
echo "========================================"
echo " 1. 登录 Web 服务器 (192.168.1.10)"
echo " 2. 登录 数据库服务器 (192.168.1.20)"
echo " 3. 登录 测试服务器 (192.168.1.30)"
echo "----------------------------------------"
echo " q/Q. 退出"
echo "========================================"
# 读取用户输入
read -p "请输入您的选择: " choice
# 使用 case 语句处理用户输入
case"$choice" in
1)
echo "正在连接 Web 服务器..."
ssh user@192.168.1.10
read -p "按 [Enter] 键返回菜单..."
;;
2)
echo "正在连接 数据库服务器..."
ssh user@192.168.1.20
read -p "按 [Enter] 键返回菜单..."
;;
3)
echo "正在连接 测试服务器..."
ssh user@192.168.1.30
read -p "按 [Enter] 键返回菜单..."
;;
q|Q)
echo "正在退出,感谢使用!"
break # 中断 while 循环,退出脚本
;;
*)
echo "无效输入,请输入正确的选项!"
sleep 2 # 暂停2秒,让用户看到提示
;;
esac
done
代码解析
- while true; do … done
创建了一个无限循环,使得菜单在每次操作后都能重新显示。
- read -p “…” choice
提示用户输入,并将输入内容存入 choice 变量。
- case “$choice” in … esac
case 语句开始,判断 choice 变量的值。
- 1)、2)、3)
分别匹配用户输入的数字,并执行相应的 ssh 命令。
登录会话结束后,使用 read 暂停脚本,等待用户按回车返回主菜单。
- q|Q)
同时匹配小写的 q 和大写的 Q。当用户想退出时,执行 break 命令跳出 while 循环,从而结束脚本。
- 6. *)
如果用户的输入不是以上任何一个模式,* 通配符会捕获它,并打印错误提示。
2
动手实验二:制作系统性能监控菜单
目标
创建一个多功能菜单,让用户可以方便地查看系统的 CPU、内存、磁盘 I/O 和网络状态。
准备工作
某些监控命令(如 iostat)可能不是系统默认安装的。
在基于 Debian/Ubuntu 的系统中,可以通过 sudo apt-get install sysstat 来安装;在 CentOS/RHEL 中,使用 sudo yum install sysstat。
脚本代码 (sys_monitor.sh)
#!/bin/bash
# 系统性能监控菜单脚本
# 定义一些颜色,让输出更好看
RED='33[0;31m'
GREEN='33[0;32m'
YELLOW='33[1;33m'
NC='33[0m' # No Color
# 函数:显示CPU信息
check_cpu() {
echo -e "
${YELLOW}======= CPU 使用情况 =======${NC}"
top -b -n 1 | head -n 5
echo -e "${YELLOW}============================${NC}
"
}
# 函数:显示内存信息
check_mem() {
echo -e "
${YELLOW}======= 内存使用情况 =======${NC}"
free -h
echo -e "${YELLOW}============================${NC}
"
}
# 函数:显示磁盘I/O信息
check_io() {
if ! command -v iostat &> /dev/null; then
echo -e "
${RED}错误: iostat 命令未找到。请先安装 sysstat 包。${NC}
"
return
fi
echo -e "
${YELLOW}======= 磁盘 I/O 性能 =======${NC}"
iostat -dx 12
echo -e "${YELLOW}=============================${NC}
"
}
# 函数:显示网络统计
check_net() {
echo -e "
${YELLOW}======= 网络连接统计 =======${NC}"
ss -s
echo -e "${YELLOW}============================${NC}
"
}
# 主循环
whiletrue; do
clear
echo "========================================"
echo -e " ${GREEN}系统性能监控菜单${NC}"
echo "========================================"
echo " 1. 查看 CPU 性能"
echo " 2. 查看 内存 使用情况"
echo " 3. 查看 磁盘 I/O"
echo " 4. 查看 网络 统计"
echo "----------------------------------------"
echo " q. 退出"
echo "========================================"
read -p "请输入您的选择: " choice
case"$choice" in
1) check_cpu ;;
2) check_mem ;;
3) check_io ;;
4) check_net ;;
q|Q)
echo "正在退出..."
exit 0
;;
*)
echo -e "
${RED}无效输入,请重试。${NC}"
sleep 1
continue # 直接进入下一次循环
;;
esac
read -p "按 [Enter] 键返回菜单..."
done
分析
- 函数化
为了代码的整洁和复用,我们将每个监控功能都封装在独立的函数中。
- 命令详解
1)top -b -n 1 | head -n 5
以非交互模式运行 top,并截取包含CPU负载的核心信息。
2)free -h
以对人类友善的格式(GB, MB)显示内存使用情况。
3)iostat -dx 1 2
提供磁盘的读写速率、I/O 等待时间等关键指标,是诊断磁盘性能瓶颈的利器。
4)ss -s
快速显示当前系统的 TCP 连接总数等摘要信息,是 netstat 的现代高效替代品。
- 健壮性
在 check_io 函数中,我们第一通过 command -v iostat 检查 iostat 命令是否存在,如果不存在则给出友善提示,避免了脚本因命令缺失而报错退出。
- 用户体验
脚本使用了颜色来区分标题和错误信息,并通过 sleep 和 read 提供了适当的暂停,让用户有时间阅读输出。
06
高级技巧与最佳实践
除了构建交互式菜单,case 语句在其他场景下同样大放异彩。
1
高级分支控制 (Bash 4+)
;&
执行完当前命令块后,无条件地继续执行下一个分支的命令块(fall-through)。
;;&
执行完当前命令块后,继续向下测试后续的模式,如果匹配则执行对应的命令块。
# Bash 4+ 示例
val="apple"
case"$val" in
a*)
echo "Starts with 'a'."
;;& # 继续测试下一个模式
*e)
echo "Ends with 'e'."
;;
esac
# 输出:
# Starts with 'a'.
# Ends with 'e'.
2
在函数中使用 case
将 case 逻辑封装在函数中,可以极大地提高代码的模块化和复用性。
# 示例:一个简单的日志函数
log() {
local level="$1"
local message="$2"
case"$level" in
INFO) echo "[INFO] $message";;
WARN) echo "[WARN] $message";;
ERROR) echo "[ERROR] $message";;
*) echo "[UNKNOWN] $message";;
esac
}
log "INFO""User logged in."
log "ERROR""Database connection failed."
3
脚本参数解析
case 是解析命令行参数(如 -v, –help)的理想工具。
while 循环遍历所有参数,case 负责匹配和处理每一个参数。
#!/bin/bash
# 用法: ./myscript.sh -v --file /tmp/data
while [ "$1" != "" ]; do
case"$1" in
-f | --file)
shift # 移动到下一个参数(即文件名)
FILE_PATH="$1"
echo "文件路径设置为: $FILE_PATH"
;;
-v | --verbose)
VERBOSE=true
echo "详细模式已开启"
;;
-h | --help)
echo "用法: $0 [-f FILE] [-v] [-h]"
exit 0
;;
*)
echo "错误: 未知参数 $1"
exit 1
;;
esac
shift # 移动到下一个参数,准备下一次循环
done
写在最后
case 语句并非花哨的语法糖,而是实战中提升可读性、减少漏洞、加速开发的利器。
掌握它,等于给你的脚本装上了“决策引擎”:从参数解析到交互菜单、从文件批处理到日志分级,case 都能让逻辑更清晰、扩展更容易。
同样地,要玩转 Oracle,离不开对 Linux 的熟悉与掌控。
毕竟,90% 的数据库性能问题,根源都在操作系统层面。
如果你还想系统补齐 Linux 的实战短板,推荐看看刘峰老师的 Linux 系列课程,循序渐进,从命令行到运维部署,一步到位。
文章来源:
https://mp.weixin.qq.com/s/AELgcG35HtVom61lQewNjw
