基于 Bash 脚本项目实战:构建一个交互式多选项菜单(系统状态检查器)
【免费下载链接】introduction-to-bash-scriptingFree Introduction to Bash Scripting eBook项目地址: https://gitcode.com/GitHub_Trending/in/introduction-to-bash-scripting
本文内容严格依据仓库中 ebook/es/content/016-creating-an-interactive-menu-in-bash.md 的教程主体,并结合仓库内英文原版章节、前序章节脚本及 scripts/shellcheck-ebook.sh 质量校验脚本进行补充与验证而成。
导读
在完成首个服务器状态检查脚本(第 15 章)之后,下一步自然是让脚本具备"人机交互"能力:用户输入一个数字,脚本执行对应动作,然后循环等待下一次选择,直到用户主动退出。这正是本文的核心主题——用 Bash 的case分支语句 +read读取输入,构建一个带彩色提示的交互式多选项菜单。
本篇指南将带领你完成:
- 规划菜单需要暴露的功能(内存、CPU、TCP 连接数、内核版本四项系统检查);
- 复用并改造上一章的检查函数;
- 使用 ANSI 转义码为菜单添加绿/蓝/红色高亮;
- 用
case ... esac结构编写菜单循环与默认错误分支; - 完整运行测试并了解仓库自带的 shellcheck 代码质量校验工具。
读完本文,你将掌握 Bash 交互式菜单的标准范式,并能把它套用到自己的运维、部署或工具类脚本中。
一、功能规划:让服务器状态检查脚本"可选"
回顾上一章(第 15 章:编写你的第一个 Bash 脚本),我们构建了一个一次性输出全部信息的status.sh。本文要在它基础上加入菜单,让用户选择要执行哪一项检查:
- 检查当前磁盘/内存使用情况;
- 检查当前 CPU 负载;
- 检查当前内存(RAM)使用情况;
- 检查内核精确版本。
如果你手头没有上一章的脚本,这里就是完整的基准脚本(对应英文原版 ebook/en/content/015-writing-your-first-bash-script.md 第 122-174 行的最终版本,也是 第 17 章远程执行 直接复用的同款脚本):
#!/bin/bash ## # BASH 菜单脚本,用于检查: # - 内存使用 # - CPU 负载 # - TCP 连接数 # - 内核版本 ## server_name=$(hostname) function memory_check() { echo "" echo "Memory usage on ${server_name} is: " free -h echo "" } function cpu_check() { echo "" echo "CPU load on ${server_name} is: " echo "" uptime echo "" } function tcp_check() { echo "" echo "TCP connections on ${server_name}: " echo "" wc -l < /proc/net/tcp echo "" } function kernel_check() { echo "" echo "Kernel version on ${server_name} is: " echo "" uname -r echo "" } function all_checks() { memory_check cpu_check tcp_check kernel_check }几个值得注意的细节:
server_name=$(hostname):$()是命令替换,Bash 会先执行括号内的hostname命令,再把输出赋给变量,之后所有函数都能引用${server_name};- 函数定义遵循 第 12 章《Bash 函数》 的规范:
function 函数名() { ... },调用时直接写函数名、不带括号; tcp_check用wc -l < /proc/net/tcp统计连接行数:/proc/net/tcp是 Linux 内核导出的 TCP 连接表,每一行代表一条连接,行数减去表头即连接数。西班牙语版此处写作cat /proc/net/tcp | wc -l,效果等价,但wc -l < 文件免去了管道和多余进程,是更简洁的写法;all_checks作为聚合函数,依次调用四个子函数,对应菜单里的 "Check All / 全部检查" 选项。
注意:
free -h、uptime、uname -r均为 Linux 自带命令,因此本脚本面向 Linux 环境;macOS 上部分输出格式会略有差异。
二、为菜单添加颜色:ANSI 转义码与颜色函数
纯文本菜单能看,但不够直观。为了让用户"一眼"看清选项编号与提示语,我们引入 ANSI 颜色转义码。Bash 脚本中颜色通常以\e[xxm(等价于\033[xxm)形式出现,\e即 ESC 转义字符。
在脚本开头添加如下颜色变量与颜色函数(英文原版见 ebook/en/content/016-creating-an-interactive-menu-in-bash.md 第 80-99 行,西班牙语版为verde/azul/clear三色,英文版额外定义了red红色):
## # 颜色变量 ## green='\e[32m' blue='\e[34m' red='\e[31m' clear='\e[0m' ## # 颜色函数 ## ColorGreen(){ echo -ne "${green}${1}${clear}" } ColorBlue(){ echo -ne "${blue}${1}${clear}" }2.1 逐个解释
| 元素 | 含义 |
|---|---|
\e[32m | 绿色前景色转义序列 |
\e[34m | 蓝色前景色转义序列 |
\e[31m | 红色前景色转义序列(用于错误提示) |
\e[0m | 重置所有颜色与样式,防止颜色"泄漏"到后续输出 |
ColorGreen(){ ... }与ColorBlue(){ ... }接收第一个参数$1(即要上色的文本),输出格式为颜色码 + 文本 + 重置码;echo -ne中的-n表示不追加换行,-e表示解析转义序列(\e、\n等)。这是\e[32m能生效的关键,普通echo会把它当字面文本输出。
2.2 颜色函数的使用方式
echo -ne "$(ColorBlue 'Some text here')"上面这条命令会把Some text here以蓝色打印到终端。其原理是命令替换$(...)先把ColorBlue 'Some text here'的输出(即\e[34mSome text here\e[0m)取回,再交给echo -ne输出。这个模式在菜单函数中会被反复使用,为每个选项编号染色。
三、添加菜单函数:case ... esac分支核心
有了检查函数和颜色函数,最后一步是编写menu()函数:先打印菜单,再读取用户输入,最后用case语句根据输入分派到对应函数。
menu(){ echo -ne " My First Menu $(ColorGreen '1)') Memory usage $(ColorGreen '2)') CPU load $(ColorGreen '3)') Number of TCP connections $(ColorGreen '4)') Kernel version $(ColorGreen '5)') Check All $(ColorGreen '0)') Exit $(ColorBlue 'Choose an option:') " read a case $a in 1) memory_check ; menu ;; 2) cpu_check ; menu ;; 3) tcp_check ; menu ;; 4) kernel_check ; menu ;; 5) all_checks ; menu ;; 0) exit 0 ;; *) echo -e "${red}Wrong option.${clear}"; menu ;; esac }3.1 逐段拆解
第一步:打印带颜色的菜单选项
echo -ne " My First Menu $(ColorGreen '1)') Memory usage ... $(ColorBlue 'Choose an option:') "- 外层字符串使用双引号包裹,因此内部的
$(ColorGreen '1)')命令替换会被执行,把选项编号染成绿色; - 选项文本保留为白色,形成"绿色编号 + 白色说明 + 蓝色提示"的层次;
-n保证打印完提示后光标停留在同一行,方便用户直接输入。
第二步:读取用户输入
read aread是 Bash 内置命令,读取一行标准输入并存入变量a(read的用法详见 第 5 章《Bash 用户输入》)。此处未加-p提示参数,因为提示语已经由上一行echo打印。
第三步:case分支分派
case $a in 1) memory_check ; menu ;; 2) cpu_check ; menu ;; ... 0) exit 0 ;; *) echo -e "${red}Wrong option.${clear}"; menu ;; esac这正是 第 10 章《Bash 条件语句》 中case语法(case $变量 in 模式) 命令 ;; ... esac)的实战应用。逐条解读:
- 每个数字模式匹配后,先执行对应检查函数,再递归调用
menu——这是让菜单"回到开头、循环显示"的关键设计(memory_check ; menu中的分号是顺序执行分隔符,先检查、后回菜单); 0) exit 0 ;;:选择 0 时调用exit 0干净退出脚本;*)是默认通配分支:任何不匹配 0-5 的输入都会落入此处,用红色打印Wrong option.后重新调用menu,让用户重新选择。这等价于if-else链中的else,是防止非法输入导致脚本崩溃的保险丝。
关于原文的一处笔误:西班牙语版在默认分支里写了
WrongCommand(一个未定义函数),英文原版为menu。本文采用修正后的写法——在出错后重新调用menu返回菜单,这才是该分支的预期行为。
第四步:调用菜单
函数定义之后必须显式调用才会执行:
# 调用菜单函数 menumenu是脚本最后一行,脚本一启动就进入菜单循环。
四、完整脚本:可直接运行的menu.sh
将以上内容组合,得到完整的交互式菜单脚本(与英文原版 ebook/en/content/016-creating-an-interactive-menu-in-bash.md 第 184-278 行的完整版一致):
#!/bin/bash ## # BASH 菜单脚本,用于检查: # - 内存使用 # - CPU 负载 # - TCP 连接数 # - 内核版本 ## server_name=$(hostname) function memory_check() { echo "" echo "Memory usage on ${server_name} is: " free -h echo "" } function cpu_check() { echo "" echo "CPU load on ${server_name} is: " echo "" uptime echo "" } function tcp_check() { echo "" echo "TCP connections on ${server_name}: " echo "" wc -l < /proc/net/tcp echo "" } function kernel_check() { echo "" echo "Kernel version on ${server_name} is: " echo "" uname -r echo "" } function all_checks() { memory_check cpu_check tcp_check kernel_check } ## # 颜色变量 ## green='\e[32m' blue='\e[34m' red='\e[31m' clear='\e[0m' ## # 颜色函数 ## ColorGreen(){ echo -ne "${green}${1}${clear}" } ColorBlue(){ echo -ne "${blue}${1}${clear}" } menu(){ echo -ne " My First Menu $(ColorGreen '1)') Memory usage $(ColorGreen '2)') CPU load $(ColorGreen '3)') Number of TCP connections $(ColorGreen '4)') Kernel version $(ColorGreen '5)') Check All $(ColorGreen '0)') Exit $(ColorBlue 'Choose an option:') " read a case $a in 1) memory_check ; menu ;; 2) cpu_check ; menu ;; 3) tcp_check ; menu ;; 4) kernel_check ; menu ;; 5) all_checks ; menu ;; 0) exit 0 ;; *) echo -e "${red}Wrong option.${clear}"; menu ;; esac } # 调用菜单函数 menu4.1 运行与预期输出
创建一个以.sh结尾的文件(例如menu.sh),将上述内容粘贴保存,然后运行:
bash menu.sh预期输出如下(颜色无法在文本中体现,实际终端中编号为绿色、提示语为蓝色):
My First Menu 1) Memory usage 2) CPU load 3) Number of TCP connections 4) Kernel version 5) Check All 0) Exit Choose an option:此时终端等待你输入。选择不同数字会触发不同的函数:
- 输入
1→ 执行memory_check,输出free -h的内存统计; - 输入
2→ 执行cpu_check,输出uptime的负载信息; - 输入
3→ 执行tcp_check,输出/proc/net/tcp的行数; - 输入
4→ 执行kernel_check,输出uname -r的内核版本; - 输入
5→ 执行all_checks,依次跑完上面四项; - 输入
0→exit 0退出脚本; - 输入其他任意内容 → 红色提示
Wrong option.,并重新显示菜单。
每次检查完成后菜单都会再次出现,形成"选择 → 执行 → 返回菜单"的循环,直到你输入0。
4.2 给脚本加执行权限(可选)
虽然bash menu.sh无需可执行权限即可运行,但若想直接以./menu.sh方式运行,需要两步:
chmod +x menu.sh ./menu.sh其中首行#!/bin/bash(shebang,详见 第 1 章《Bash 入门》 与 第 3 章《Hello World》)会告诉系统用/bin/bash解释器执行本脚本。
五、仓库自带的代码质量校验:shellcheck 如何守护这类脚本
这个开源电子书仓库不仅提供教程文字,还自带一个质量保障脚本 scripts/shellcheck-ebook.sh。它会自动提取英文版 ebook/en/content 目录下所有 Markdown 文件中的```bash代码块,逐个交给 shellcheck 静态检查,确保书中的每个脚本片段都可运行、无低级错误。
./scripts/shellcheck-ebook.sh [ebook_dir] # ebook_dir 默认为 ebook/en/content几个与本文菜单脚本直接相关的实现细节(scripts/shellcheck-ebook.sh 第 90-96 行):
- 若代码块第一行匹配
^[0-9]+\)(例如菜单的示例输出1) Memory usage),会被识别为"select-menu 输出/菜单渲染结果",判定为不可直接执行的展示文本而跳过检查; - 若某个代码块所在标题行含 "Incorrect"、"### Error"、"Output" 等字样,也会被跳过——说明仓库刻意收录了一些"错误示范"用于教学对比;
- 未跳过的真实脚本代码块则使用
shellcheck -S warning -e "SC2034,SC2154,..."严格校验,排除项如SC2034(变量看似未使用)正是考虑到"教程片段中定义的变量会在后续片段使用"的教学场景。
这套机制从侧面印证了本文菜单脚本的写法(case分支、颜色函数、exit 0退出)是符合 shellcheck 警告级别要求的规范代码。你写完自己的menu.sh后,也可以用同样的方式自检:
shellcheck menu.sh六、延伸:这个菜单还能怎么用
掌握交互式菜单范式后,你可以把它迁移到仓库后续章节的真实场景中:
- 第 17 章《在多台远程服务器上执行 Bash 脚本》 直接复用了本章的检查函数集(
memory_check、cpu_check、tcp_check、kernel_check),只是将菜单层替换为 SSH 批量循环——这说明"功能函数"与"交互界面"分离的设计让脚本极易复用; - 将
case的匹配值扩展为更友好的语义字符串(如quit、all),或通过read -p携带提示、用select内置命令生成编号菜单,都是自然的升级方向; - 颜色函数可以扩充更多色号(
\e[33m黄、\e[36m青等)和样式(\e[1m加粗),用于区分不同级别的输出。
结语
至此,你已掌握 Bash 交互式菜单的完整构建方法:先用函数封装业务逻辑,再用 ANSI 转义码优化视觉呈现,最后用case分支 +menu递归调用实现"选择-执行-循环-退出"的交互闭环。这个模式可以内嵌到任何需要用户做出多选一的脚本中——从系统监控工具到部署向导都适用。
建议在本地终端中亲手运行一遍menu.sh,故意输入几个非法字符观察默认分支的容错行为,然后按自己的需求增删菜单项。当你需要确认脚本写法符合规范时,仓库的 scripts/shellcheck-ebook.sh 就是现成的参考答案。
继续阅读下一章(第 17 章:远程批量执行),看看这套检查函数如何被改造成"一条命令跑遍所有服务器"的运维利器。
【免费下载链接】introduction-to-bash-scriptingFree Introduction to Bash Scripting eBook项目地址: https://gitcode.com/GitHub_Trending/in/introduction-to-bash-scripting
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考