first commit

This commit is contained in:
wangdong0
2025-07-26 03:48:37 +08:00
commit eb45c7ab14
13 changed files with 2026 additions and 0 deletions
+388
View File
@@ -0,0 +1,388 @@
#!/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<num_options; i++)); do
if ((i == selected)); then
printf "${COLOR_HIGHLIGHT}${COLOR_SELECTION}➔ %s${COLOR_NORMAL}\n" "${options_ref[$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<padding; j++)); do
formatted+=" "
done
# 添加列间隔,最后一列除外
if (( i < ${#fields[@]} - 1 )); then
for ((j=0; j<spacing; j++)); do
formatted+=" "
done
fi
done
echo "$formatted"
}
# 格式化表头
IFS=',' read -r -a header_fields <<< "$header"
formatted_header=$(format_row "${header_fields[@]}")
# 格式化选项
local -a formatted_options
for line in "${options[@]}"; do
IFS=',' read -r -a fields <<< "$line"
formatted_options+=("$(format_row "${fields[@]}")")
done
# 初始化终端
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_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<num_options; i++)); do
if ((i == selected)); then
printf "%s\n" "$(highlight_line "${formatted_options[$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