🔍搜索⌘K

Linux Shell 文本菜单开发指南:从基础布局到 select 命令实战

概述

在编写 Shell 脚本时,“交互式” 是提升用户体验的核心 —— 尤其是针对运维脚本、工具类脚本,用户往往需要清晰的操作指引,而非记忆复杂的命令行参数。

文本菜单的核心价值在于:

  1. 降低使用门槛:通过可视化列表告知用户 “脚本能做什么”,避免用户因不熟悉参数导致操作失误;
  2. 统一操作逻辑:将多个功能(如磁盘查看、内存监控)聚合到同一入口,用户只需选择数字 / 选项即可触发;
  3. 提升专业性:标准化的菜单布局(如标题、选项、提示符)让脚本更像 “工具” 而非 “临时脚本”。

Shell 实现文本菜单的核心技术是 case 命令(处理用户选择)和 select 命令(快速生成菜单),下文将从基础到进阶逐步讲解。

一、基础文本菜单:手动构建布局与逻辑

手动构建菜单的优势是 “完全自定义”—— 可自由控制菜单样式、交互流程,适合对界面有个性化需求的场景。核心步骤分为:创建菜单布局、封装功能函数、编写选择逻辑。

1. 第一步:设计菜单布局

菜单布局的核心是 “清空屏幕 + 格式化输出”,需用到两个关键工具:

  • clear 命令:清空终端屏幕,避免历史输出干扰菜单显示;
  • echo -e 选项:支持转义字符(如 \t 制表符、\n 换行符),实现对齐排版。

示例:基础菜单布局代码

#!/bin/bash
# 脚本功能:演示基础菜单布局
clear  # 清空屏幕,准备显示菜单

# 用 echo -e 实现格式化输出(\t 缩进,\n 换行)
echo -e "\t\t\t=== 系统管理菜单 ===\n"  # 菜单标题(居中对齐)
echo -e "\t1. 查看磁盘空间(df -k)"
echo -e "\t2. 查看当前登录用户(who)"
echo -e "\t3. 查看内存使用(/proc/meminfo)"
echo -e "\t0. 退出菜单\n"

# -en 选项:不换行(让光标停在提示符后,提升交互感)
echo -en "\t请输入选项 [0-3]:"

# 读取用户输入(-n 1 限制只读1个字符,无需按Enter)
read -n 1 option

# 换行(避免后续输出与提示符重叠)
echo -e "\n"

执行效果(界面整洁,选项对齐):

                        === 系统管理菜单 ===

        1. 查看磁盘空间(df -k)
        2. 查看当前登录用户(who)
        3. 查看内存使用(/proc/meminfo)
        0. 退出菜单

        请输入选项 [0-3]:1

2. 第二步:封装菜单功能为函数

菜单的每个选项对应一个 “功能”(如查看磁盘、查看内存),将这些功能封装为 Shell 函数,可让代码更清晰、易维护 —— 后续修改功能只需调整函数内部,无需改动菜单逻辑。

最佳实践:先写 “桩函数”,再补实现

若功能暂未完成,可先写 “桩函数”(空函数或占位提示),保证菜单能正常运行。例如:

#!/bin/bash
# 1. 磁盘空间查看函数
function disk_space {
  clear  # 清空屏幕,单独显示功能结果
  echo -e "\t=== 磁盘空间信息(df -k)===\n"
  df -k  # 核心命令:查看磁盘空间(KB单位)
}

# 2. 登录用户查看函数
function logged_users {
  clear
  echo -e "\t=== 当前登录用户(who)===\n"
  who  # 核心命令:查看登录用户
}

# 3. 内存使用查看函数(桩函数示例,暂未实现)
function mem_usage {
  clear
  echo -e "\t=== 内存使用信息(待实现)===\n"
  echo "提示:后续将替换为 cat /proc/meminfo 或 free 命令"
}

# 4. 菜单显示函数(将布局封装为函数,方便重复调用)
function show_menu {
  clear
  echo -e "\t\t\t=== 系统管理菜单 ===\n"
  echo -e "\t1. 查看磁盘空间"
  echo -e "\t2. 查看当前登录用户"
  echo -e "\t3. 查看内存使用(待实现)"
  echo -e "\t0. 退出菜单\n"
  echo -en "\t请输入选项 [0-3]:"
  read -n 1 option
  echo -e "\n"
}

3. 第三步:编写菜单核心逻辑(case 命令)

菜单的 “交互逻辑” 由 case 命令实现:根据用户输入的 option(如 1、2、0),调用对应的函数;若输入无效(如 4、a),则提示错误。

完整示例:布局 + 函数 + 逻辑整合

#!/bin/bash
# 系统管理菜单脚本(基础版)

# -------------------------- 功能函数 --------------------------
# 1. 查看磁盘空间
function disk_space {
  clear
  echo -e "\t=== 磁盘空间信息(df -k)===\n"
  df -k
}

# 2. 查看登录用户
function logged_users {
  clear
  echo -e "\t=== 当前登录用户(who)===\n"
  who
}

# 3. 查看内存使用
function mem_usage {
  clear
  echo -e "\t=== 内存使用信息(cat /proc/meminfo)===\n"
  cat /proc/meminfo | head -10  # 只显示前10行,避免输出过长
}

# 4. 显示菜单
function show_menu {
  clear
  echo -e "\t\t\t=== 系统管理菜单 ===\n"
  echo -e "\t1. 查看磁盘空间"
  echo -e "\t2. 查看当前登录用户"
  echo -e "\t3. 查看内存使用"
  echo -e "\t0. 退出菜单\n"
  echo -en "\t请输入选项 [0-3]:"
  read -n 1 option
  echo -e "\n"
}

# -------------------------- 核心逻辑 --------------------------
# 无限循环(直到用户选择0退出)
while true; do
  # 1. 显示菜单,获取用户选择
  show_menu

  # 2. 根据选择执行对应功能
  case $option in
    0)  # 退出菜单
      clear
      echo -e "\t=== 感谢使用,再见!===\n"
      break  # 跳出循环,结束脚本
      ;;
    1)  # 查看磁盘空间
      disk_space
      ;;
    2)  # 查看登录用户
      logged_users
      ;;
    3)  # 查看内存使用
      mem_usage
      ;;
    *)  # 无效输入
      clear
      echo -e "\t=== 错误:请输入 0-3 之间的选项!===\n"
      ;;
  esac

  # 3. 功能执行后,等待用户按任意键返回菜单
  echo -en "\t按任意键返回菜单..."
  read -n 1  # 无需存储输入,仅用于暂停
  echo -e "\n"
done

执行流程解析:

  1. 脚本启动后进入 while true 无限循环,持续显示菜单;
  2. 用户选择选项后,case 命令调用对应函数,显示功能结果;
  3. 功能执行完毕后,等待用户按任意键,再次显示菜单;
  4. 选择 “0” 时,break 跳出循环,脚本结束。

执行效果(选择 “1” 查看磁盘空间):

                        === 系统管理菜单 ===

        1. 查看磁盘空间
        2. 查看当前登录用户
        3. 查看内存使用
        0. 退出菜单

        请输入选项 [0-3]:1

        === 磁盘空间信息(df -k)===

Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda1      515023616 8934528 480445664   2% /
tmpfs            8129440       0   8129440   0% /dev/shm
/dev/sdb1     1048575968 1234560 1047341408   1% /data

        按任意键返回菜单...

二、进阶:用 select 命令快速生成菜单

手动构建菜单需要写 “布局 + 读取输入 + 循环”,步骤较繁琐。Bash 提供 select 命令—— 只需一行代码即可自动生成带编号的菜单,自动处理用户输入,大幅简化开发。

1. select 命令核心语法

select 命令会自动完成 “菜单显示 + 输入读取 + 输入验证”,语法格式如下:

# 1. (可选)设置菜单提示符(默认是 #? ,用 PS3 变量自定义)
PS3="请输入选项编号:"

# 2. select 命令主体
select 变量名 in 选项1 选项2 选项3...
do
  # 根据变量值执行对应逻辑(通常用 case 命令)
  case $变量名 in
    选项1) 命令1 ;;
    选项2) 命令2 ;;
    选项3) 命令3 ;;
    *) 提示无效输入 ;;
  esac
done

关键特性:

  • 自动为每个选项分配 “数字编号”(1、2、3…);
  • 自动显示 PS3 定义的提示符(如 “请输入选项编号:”);
  • 用户输入编号后,变量会存储 “选项的完整字符串”(而非编号);
  • 支持按 Ctrl+C 退出菜单。

2. 示例:用 select 实现系统管理菜单

基于前文的功能函数,用 select 重构菜单逻辑,代码量减少 50%:

#!/bin/bash
# 系统管理菜单脚本(select 进阶版)

# -------------------------- 功能函数 --------------------------
function disk_space {
  clear
  echo -e "\t=== 磁盘空间信息(df -k)===\n"
  df -k
}

function logged_users {
  clear
  echo -e "\t=== 当前登录用户(who)===\n"
  who
}

function mem_usage {
  clear
  echo -e "\t=== 内存使用信息(free -h)===\n"  # -h 人类可读格式
  free -h
}

# -------------------------- select 菜单 --------------------------
# 1. 自定义菜单提示符(默认是 #?)
PS3="请输入选项编号(按Ctrl+C退出):"

# 2. 生成菜单(选项列表用空格分隔)
select menu_option in "查看磁盘空间" "查看当前登录用户" "查看内存使用" "退出菜单"
do
  # 根据选项字符串执行逻辑(注意:变量存的是完整字符串,不是编号)
  case $menu_option in
    "查看磁盘空间")
      disk_space
      ;;
    "查看当前登录用户")
      logged_users
      ;;
    "查看内存使用")
      mem_usage
      ;;
    "退出菜单")
      clear
      echo -e "\t=== 感谢使用,再见!===\n"
      exit 0  # 退出脚本
      ;;
    *)  # 无效输入(如输入非数字)
      clear
      echo -e "\t=== 错误:请输入正确的选项编号!===\n"
      ;;
  esac

  # 功能执行后,等待用户按任意键返回菜单
  echo -en "\t按任意键返回菜单..."
  read -n 1
  echo -e "\n"
done

执行效果(自动生成编号,交互更简洁):

1) 查看磁盘空间   3) 查看内存使用
2) 查看当前登录用户  4) 退出菜单
请输入选项编号(按Ctrl+C退出):2

        === 当前登录用户(who)===

root     pts/0        2024-06-13 10:30 (192.168.1.100)
user1    pts/1        2024-06-13 11:15 (192.168.1.101)

        按任意键返回菜单...

3. select 命令的优势与注意事项

优势:

  • 开发效率高:无需手动写布局、读取输入,一行 select 搞定菜单生成;
  • 用户体验好:自动编号,用户只需记编号(如 1、2),无需记选项字符串;
  • 兼容性强:所有 Bash 环境均支持,无需依赖外部工具。 注意事项:
  • 变量存储的是 “选项字符串”:case 命令中必须用完整的选项字符串(如 “查看磁盘空间”)匹配,而非编号;
  • 默认不支持 “单字符输入无需 Enter”:用户输入编号后需按 Enter 确认(若需此功能,仍需用 read -n 1 手动实现);
  • 退出方式:需在选项中显式添加 “退出” 逻辑(如 exit 0),或让用户按 Ctrl+C 强制退出。

三、文本菜单的优化技巧

无论用手动布局还是 select 命令,以下优化技巧能让菜单脚本更专业、更易用:

1. 菜单标题与版本信息

在菜单顶部添加 “脚本名称、版本、功能说明”,方便用户了解脚本用途。例如:

function show_menu {
  clear
  echo -e "\t\t=== 系统管理工具 v1.0 ===\n"
  echo -e "\t功能:快速查看系统磁盘、用户、内存信息\n"
  echo -e "\t1. 查看磁盘空间"
  echo -e "\t2. 查看当前登录用户"
  echo -e "\t3. 查看内存使用"
  echo -e "\t0. 退出菜单\n"
}

2. 输入验证:限制选项范围

手动布局时,可通过 if 语句提前验证输入是否在有效范围内(如 0-3),避免无效输入进入 case 命令:

function show_menu {
  clear
  # ... 菜单显示代码 ...
  read -n 1 option
  echo -e "\n"

  # 验证输入是否为 0-3 的数字
  if ! [[ $option =~ ^[0-3]$ ]]; then
    option="*"  # 设为无效值,让 case 命令处理错误
  fi
}

3. 功能结果分页显示

若功能输出过长(如 cat /proc/meminfo),可通过 | less 实现分页,避免信息刷屏:

function mem_usage {
  clear
  echo -e "\t=== 内存使用信息(分页显示)===\n"
  cat /proc/meminfo | less  # 分页显示,按 q 退出分页
}

4. 脚本注释与帮助信息

在脚本开头添加注释,说明脚本功能、使用方法、依赖命令,方便后续维护:

#!/bin/bash
# 脚本名称:sys_admin_menu.sh
# 功能描述:系统管理菜单脚本,支持查看磁盘、用户、内存信息
# 使用方法:
#   1. 赋予执行权限:chmod +x sys_admin_menu.sh
#   2. 运行脚本:./sys_admin_menu.sh
# 依赖命令:df、who、free、cat(Linux 系统默认自带)
# 版本信息:v1.0(2024-06-13)

四、总结:两种菜单实现方式的对比与选择

手动布局和 select 命令各有适用场景,需根据需求选择:

对比维度手动布局(echo + case)select 命令
灵活性高(可自定义样式、交互逻辑)中(样式固定,自动编号)
开发效率低(需写布局、读取输入、循环)高(一行命令生成菜单)
交互体验可实现 “单字符无需 Enter”需按 Enter 确认输入
适用场景1. 对界面样式有定制需求(如居中标题、特殊符号装饰)
2. 需要 “单字符快速选择”(无需按 Enter)
3. 菜单逻辑复杂(如多级菜单、条件显示选项)
1. 快速开发工具类脚本(无需纠结样式)
2. 菜单选项简单、无复杂交互需求
3. 追求开发效率,优先实现功能而非界面

五、实战案例:多级文本菜单(进阶需求)

在实际场景中,单一菜单可能无法满足需求(如 “系统管理” 包含 “硬件监控”“用户管理” 等子功能),此时需要实现 多级菜单—— 通过 “主菜单 → 子菜单 → 功能” 的层级结构,聚合更多功能。

案例需求

实现一个 “Linux 系统工具箱”,包含两级菜单:

  • 主菜单:硬件监控、用户管理、退出工具
  • 硬件监控子菜单:查看磁盘、查看内存、返回主菜单
  • 用户管理子菜单:查看登录用户、创建用户(需 root 权限)、返回主菜单

完整代码实现

#!/bin/bash
# 脚本名称:linux_system_toolbox.sh
# 功能描述:Linux 系统工具箱(多级菜单示例)
# 使用方法:chmod +x linux_system_toolbox.sh && ./linux_system_toolbox.sh
# 依赖命令:df、free、who、useradd(创建用户需root权限)
# 版本信息:v1.0(2024-06-13)

# -------------------------- 工具函数 --------------------------
# 1. 暂停并等待用户按任意键
function pause {
  echo -en "\n\t按任意键返回上一级菜单..."
  read -n 1
  echo -e "\n"
}

# -------------------------- 硬件监控功能 --------------------------
function check_disk {
  clear
  echo -e "\t=== 硬件监控 → 磁盘空间(df -h)===\n"
  df -h  # 人类可读格式(GB/MB)
  pause
}

function check_mem {
  clear
  echo -e "\t=== 硬件监控 → 内存使用(free -h)===\n"
  free -h
  pause
}

# 硬件监控子菜单
function hardware_menu {
  while true; do
    clear
    echo -e "\t\t=== 系统工具箱 → 硬件监控子菜单 ===\n"
    echo -e "\t1. 查看磁盘空间(df -h)"
    echo -e "\t2. 查看内存使用(free -h)"
    echo -e "\t0. 返回主菜单\n"
    echo -en "\t请输入选项 [0-2]:"
    read -n 1 option
    echo -e "\n"

    # 验证输入(仅允许 0-2)
    if ! [[ $option =~ ^[0-2]$ ]]; then
      option="*"
    fi

    # 子菜单逻辑
    case $option in
      0) break ;;  # 返回主菜单(跳出子菜单循环)
      1) check_disk ;;
      2) check_mem ;;
      *)
        clear
        echo -e "\t=== 错误:请输入 0-2 之间的选项!===\n"
        pause
        ;;
    esac
  done
}

# -------------------------- 用户管理功能 --------------------------
function list_users {
  clear
  echo -e "\t=== 用户管理 → 当前登录用户(who)===\n"
  who
  pause
}

function create_user {
  clear
  # 检查是否为 root 用户(创建用户需 root 权限)
  if [ "$(id -u)" -ne 0 ]; then
    echo -e "\t=== 错误:创建用户需 root 权限,请用 sudo 运行!===\n"
    pause
    return
  fi

  # 读取用户名并创建
  echo -en "\t请输入要创建的用户名:"
  read username
  if id "$username" &>/dev/null; then
    echo -e "\n\t=== 提示:用户 $username 已存在!===\n"
  else
    useradd -m "$username"  # -m 自动创建用户家目录
    if [ $? -eq 0 ]; then
      echo -e "\n\t=== 成功:用户 $username 创建完成!===\n"
      echo -e "\t提示:请用 passwd $username 设置密码\n"
    else
      echo -e "\n\t=== 错误:用户 $username 创建失败!===\n"
    fi
  fi
  pause
}

# 用户管理子菜单
function user_menu {
  while true; do
    clear
    echo -e "\t\t=== 系统工具箱 → 用户管理子菜单 ===\n"
    echo -e "\t1. 查看当前登录用户"
    echo -e "\t2. 创建新用户(需root权限)"
    echo -e "\t0. 返回主菜单\n"
    echo -en "\t请输入选项 [0-2]:"
    read -n 1 option
    echo -e "\n"

    if ! [[ $option =~ ^[0-2]$ ]]; then
      option="*"
    fi

    case $option in
      0) break ;;
      1) list_users ;;
      2) create_user ;;
      *)
        clear
        echo -e "\t=== 错误:请输入 0-2 之间的选项!===\n"
        pause
        ;;
    esac
  done
}

# -------------------------- 主菜单 --------------------------
function main_menu {
  while true; do
    clear
    echo -e "\t\t\t=== Linux 系统工具箱 v1.0 ===\n"
    echo -e "\t1. 硬件监控(磁盘、内存)"
    echo -e "\t2. 用户管理(查看、创建)"
    echo -e "\t0. 退出工具箱\n"
    echo -en "\t请输入选项 [0-2]:"
    read -n 1 option
    echo -e "\n"

    if ! [[ $option =~ ^[0-2]$ ]]; then
      option="*"
    fi

    case $option in
      0)
        clear
        echo -e "\t=== 感谢使用,再见!===\n"
        exit 0
        ;;
      1) hardware_menu ;;  # 进入硬件监控子菜单
      2) user_menu ;;      # 进入用户管理子菜单
      *)
        clear
        echo -e "\t=== 错误:请输入 0-2 之间的选项!===\n"
        pause
        ;;
    esac
  done
}

# -------------------------- 脚本入口 --------------------------
main_menu

核心设计思路

  1. 层级循环嵌套:主菜单用 while true 循环,子菜单也用独立 while true 循环 —— 选择 “返回主菜单” 时,用 break 跳出子菜单循环,回到主菜单;
  2. 公共函数复用:将 “暂停等待” 封装为 pause 函数,避免重复代码;
  3. 权限与合法性检查:创建用户前检查是否为 root 权限,创建时检查用户是否已存在,提升脚本健壮性;
  4. 输入验证前置:每个菜单都先验证输入是否在有效范围,避免无效值进入 case 命令。

六、最终建议

  1. 优先用 select 快速验证功能:开发初期若只需 “功能可用”,用 select 命令快速搭建菜单框架,后续再优化界面;
  2. 复杂场景用手动布局 + 多级循环:需自定义样式、多级菜单时,采用 “函数封装 + 循环嵌套”,保证代码清晰;
  3. 重视用户体验细节:添加暂停等待、输入验证、权限提示,让脚本从 “能用” 升级为 “好用”;
  4. 规范脚本文档:开头添加功能、用法、依赖说明,方便自己和他人维护。

通过本文的方法,你可以轻松实现从 “简单单级菜单” 到 “复杂多级工具” 的 Shell 脚本,满足日常运维、自动化管理的大部分交互需求。