🔍搜索⌘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 重写菜单逻辑,代码量能少一半:

#!/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 确认(想要那种效果还是得用 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. 脚本文档写清楚:开头把功能、用法、依赖说清楚,方便自己以后看,也方便别人接手。

用上面这套思路,从“简单的单层菜单”到“复杂的多级工具”你都能轻松写出来,日常运维、自动化管理里那些交互需求基本都覆盖了。