#!/bin/bash # 交互式菜单函数(关键修复) interactive_menu() { trap 'cleanup' EXIT local -n options_ref=$1 local title=${2:-"请选择一个选项:"} local selected=0 local num_options=${#options_ref[@]} # 初始化终端 local OLD_TERM=$(stty -g) stty -echo -icanon time 0 min 0 tput smcup >/dev/tty tput civis >/dev/tty clear >/dev/tty # 颜色定义 local COLOR_NORMAL=$(tput sgr0) local COLOR_HIGHLIGHT=$(tput rev) local COLOR_TITLE=$(tput setaf 4) local COLOR_SELECTION=$(tput setaf 2) # 显示菜单(修复污染源) show_menu() { tput cup 0 0 >/dev/tty printf "${COLOR_TITLE}%s${COLOR_NORMAL}\n\n" "$title" >/dev/tty for ((i=0; i/dev/tty else printf " %s\n" "${options_ref[$i]}" | expand -t4 >/dev/tty fi done tput ed >/dev/tty } # 事件处理 local buffer="" local return_index=-1 while true; do show_menu read -rsn1 input buffer+="$input" case "$buffer" in $'\x1B[A') # 上箭头 ((selected = (selected - 1 + num_options) % num_options)) buffer="" ;; $'\x1B[B') # 下箭头 ((selected = (selected + 1) % num_options)) buffer="" ;; "") # 回车 return_index=$selected break ;; [qQ]) # 退出 break ;; *) [[ ! "$buffer" =~ ^$'\x1B' ]] && buffer="" ;; esac done # 清理终端 cleanup() { tput sgr0 2>/dev/null >/dev/tty stty "$OLD_TERM" 2>/dev/null tput cnorm >/dev/tty tput rmcup >/dev/tty } cleanup # 关键修复:确保纯净数字输出 if (( return_index >= 0 )); then printf "%d" "$return_index" # 使用printf避免换行符[4](@ref) return 0 else return 1 fi } # # ============== 调用示例 ============== # # source ./interactive_menu.sh # menu_items=("查看CPU信息" "检查磁盘空间" "监控网络状态" "返回上级菜单") # # # 捕获纯净数字索引 # choice_index=$(interactive_menu menu_items "系统管理主菜单") # interactive_menu_exit=$? # # # 安全获取选项(避免空值) # if [[ -n "$choice_index" ]] && [[ "$choice_index" =~ ^[0-9]+$ ]]; then # selected_item="${menu_items[$choice_index]}" # else # selected_item="无效选择" # fi # # case $interactive_menu_exit in # 0) echo "用户选择: $selected_item (索引: $choice_index)" ;; # 1) echo "用户取消选择" ;; # esac export -f interactive_menu interactive_menu_csv() { trap 'cleanup' EXIT local csv_data="$1" local select_tip=${2:-"请选择一个选项:"} local selected=0 local return_index=-1 # 解析CSV数据 IFS=$'\n' read -d '' -r -a lines <<< "$csv_data" local header="${lines[0]}" local -a options=("${lines[@]:1}") local num_options=${#options[@]} # 字符宽度映射表 - 针对常见中文字符优化 declare -A char_width_map # 计算字符串的显示宽度(考虑全角字符) str_display_width() { local str="$1" local width=0 local char full_char hex_bytes codepoint # 按字符遍历字符串 while IFS= read -r -n1 char; do [[ -z "$char" ]] && continue # 跳过空字符 # 读取完整UTF-8字符 full_char="$char" local first_byte=$(printf "%d" "'$char") # 1字节字符(ASCII) if (( first_byte < 128 )); then ((width++)) continue fi # 多字节字符处理 if (( first_byte >= 194 && first_byte <= 223 )); then # 2字节 read -r -n1 char; full_char+="$char" elif (( first_byte >= 224 && first_byte <= 239 )); then # 3字节 read -r -n1 char; full_char+="$char" read -r -n1 char; full_char+="$char" elif (( first_byte >= 240 && first_byte <= 244 )); then # 4字节 read -r -n1 char; full_char+="$char" read -r -n1 char; full_char+="$char" read -r -n1 char; full_char+="$char" fi # 获取UTF-8字节的十六进制表示 hex_bytes=$(echo -n "$full_char" | xxd -p) # 转换UTF-8到Unicode码点 case ${#hex_bytes} in 4) # 2字节 codepoint=$(((0x${hex_bytes:0:2} & 0x1F) << 6 | (0x${hex_bytes:2:2} & 0x3F))) ;; 6) # 3字节 codepoint=$(((0x${hex_bytes:0:2} & 0x0F) << 12 | (0x${hex_bytes:2:2} & 0x3F) << 6 | (0x${hex_bytes:4:2} & 0x3F))) ;; 8) # 4字节 codepoint=$(((0x${hex_bytes:0:2} & 0x07) << 18 | (0x${hex_bytes:2:2} & 0x3F) << 12 | (0x${hex_bytes:4:2} & 0x3F) << 6 | (0x${hex_bytes:6:2} & 0x3F))) ;; *) # 其他情况 codepoint=0 ;; esac # 判断宽字符(添加韩文字母范围 U+3130–U+318F) if (( codepoint >= 0x4E00 && codepoint <= 0x9FFF || codepoint >= 0x3400 && codepoint <= 0x4DBF || codepoint >= 0x3040 && codepoint <= 0x309F || codepoint >= 0x30A0 && codepoint <= 0x30FF || codepoint >= 0x3130 && codepoint <= 0x318F || codepoint >= 0xAC00 && codepoint <= 0xD7AF || codepoint >= 0xFF00 && codepoint <= 0xFFEF || codepoint >= 0x3000 && codepoint <= 0x303F )); then ((width += 2)) else ((width++)) fi done <<< "$str" echo "$width" } # 确定每列的最大宽度 local -a max_widths IFS=',' read -r -a headers <<< "$header" for ((i=0; i<${#headers[@]}; i++)); do max_widths[$i]=$(str_display_width "${headers[$i]}") done # 计算每列的最大宽度(包括选项) for line in "${options[@]}"; do IFS=',' read -r -a fields <<< "$line" for ((i=0; i<${#fields[@]}; i++)); do local width=$(str_display_width "${fields[$i]}") if (( width > max_widths[i] )); then max_widths[$i]=$width fi done done # 列间隔设置 local MIN_SPACING=3 local MAX_SPACING=5 local num_columns=${#max_widths[@]} # 计算总内容宽度 local total_content_width=0 for width in "${max_widths[@]}"; do ((total_content_width += width)) done # 获取终端宽度 local term_width=$(tput cols) local available_space=$((term_width - total_content_width)) # 计算最佳间隔 if (( num_columns > 1 )); then local ideal_spacing=$((available_space / (num_columns - 1))) if (( ideal_spacing < MIN_SPACING )); then spacing=$MIN_SPACING elif (( ideal_spacing > MAX_SPACING )); then spacing=$MAX_SPACING else spacing=$ideal_spacing fi else spacing=$MIN_SPACING fi # 格式化行数据 format_row() { local -a fields=("$@") local formatted="" for ((i=0; i<${#fields[@]}; i++)); do local field="${fields[$i]}" local field_width=$(str_display_width "$field") local padding=$((max_widths[i] - field_width)) formatted+="${field}" # 添加填充空格 for ((j=0; j/dev/tty tput civis >/dev/tty clear >/dev/tty # 颜色定义 local COLOR_NORMAL=$(tput sgr0) local COLOR_HIGHLIGHT=$(tput rev) local COLOR_select_tip=$(tput setaf 4) # 蓝色标题 local COLOR_HEADER=$(tput setaf 6) # 青色表头 local COLOR_SELECTION=$(tput setaf 2) # 绿色选中项 local COLOR_PROMPT=$(tput setaf 3) # 黄色提示 # 高亮整行(包括间隔) highlight_line() { local line="$1" local highlighted="${COLOR_HIGHLIGHT}${COLOR_SELECTION}➔ ${line}${COLOR_NORMAL}" echo "$highlighted" } # 显示菜单 show_menu() { tput cup 0 0 >/dev/tty # 显示表头 printf " ${COLOR_HEADER}%s${COLOR_NORMAL}\n\n" "$formatted_header" >/dev/tty # 显示选项 for ((i=0; i/dev/tty else printf " %s\n" "${formatted_options[$i]}" >/dev/tty fi done # 计算提示信息应该显示的位置 local prompt_row=$((num_options + 3)) tput cup $prompt_row 0 >/dev/tty tput el >/dev/tty printf "${COLOR_PROMPT}%s${COLOR_NORMAL}\n" "$select_tip" >/dev/tty printf "${COLOR_PROMPT}↑↓: 选择 Enter: 确认 Q: 退出${COLOR_NORMAL}\n" >/dev/tty # 清除屏幕剩余部分 tput ed >/dev/tty } # 事件处理 local buffer="" while true; do show_menu read -rsn1 input buffer+="$input" case "$buffer" in $'\x1B[A') # 上箭头 ((selected = (selected - 1 + num_options) % num_options)) buffer="" ;; $'\x1B[B') # 下箭头 ((selected = (selected + 1) % num_options)) buffer="" ;; "") # 回车 return_index=$selected break ;; [qQ]) # 退出 break ;; *) [[ ! "$buffer" =~ ^$'\x1B' ]] && buffer="" ;; esac done # 清理终端 cleanup() { tput sgr0 2>/dev/null >/dev/tty stty "$OLD_TERM" 2>/dev/null tput cnorm >/dev/tty tput rmcup >/dev/tty } cleanup # 返回选择结果 if (( return_index >= 0 )); then printf "%d" "$return_index" return 0 else return 1 fi } # # ============== 调用示例 ============== # # source ./interactive_menu_csv.sh # csv="SSID名称,MAC地址,加密类型,握手信息 # 好日子-WiFi-5G,11:22:33:AA:BB:CC,WPA2,2 handshake # 303-4g,22:55:44:22:EE:DD,WPA2,1 handshake # 303,55:66:77:88:99:AA,WPA2,1 handshake" # # selected=$(interactive_menu_csv "$csv" "请选择一个WiFi网络:") # if [ $? -eq 0 ]; then # echo "你选择了选项 $selected" # else # echo "已取消选择" # fi export -f interactive_menu_csv