323 lines
11 KiB
Bash
323 lines
11 KiB
Bash
#!/bin/bash
|
||
|
||
file_selector() {
|
||
# 保存原始终端状态
|
||
local OLD_TERM
|
||
OLD_TERM=$(stty -g 2>/dev/null)
|
||
|
||
# 保存当前进程ID(用于本地化信号处理)
|
||
local TOP_PID=$$
|
||
|
||
# 清理函数 - 完全恢复原始状态
|
||
cleanup() {
|
||
# 恢复主屏幕缓冲区
|
||
echo -en "\033[?1049l" >&2
|
||
|
||
# 恢复光标
|
||
tput cnorm >&2
|
||
|
||
# 重置文本样式
|
||
tput sgr0 >&2
|
||
|
||
# 恢复终端设置(仅在OLD_TERM有值时)
|
||
if [[ -n "$OLD_TERM" ]]; then
|
||
stty "$OLD_TERM" >&2
|
||
fi
|
||
}
|
||
|
||
normalize_path() {
|
||
local path="$1"
|
||
# 替换任意数量连续斜杠为单斜杠
|
||
path=$(echo "$path" | sed 's|//\+|/|g')
|
||
# 特殊处理根目录的异常形式
|
||
[[ "$path" == "//" ]] && path="/"
|
||
echo "$path"
|
||
}
|
||
|
||
# 信号捕获
|
||
trap 'cleanup; exit 1' EXIT INT TERM
|
||
|
||
# 设置起始目录(支持可选参数)
|
||
local start_dir="${1:-$(pwd)}"
|
||
local restrict_mode=0
|
||
local custom_prompt
|
||
local file_exts=()
|
||
|
||
# 修复参数解析逻辑
|
||
if [[ "$2" == "-r" || "$2" == "--restrict" ]]; then
|
||
restrict_mode=1
|
||
custom_prompt="${3:-请选择一个文件:}"
|
||
if [[ -n "${4:-}" ]]; then
|
||
IFS=',' read -ra file_exts <<< "${4// /}" # 移除空格并分割
|
||
fi
|
||
else
|
||
# 当没有使用-r选项时,第二个参数是提示
|
||
custom_prompt="${2:-请选择一个文件:}"
|
||
if [[ -n "${3:-}" ]]; then
|
||
IFS=',' read -ra file_exts <<< "${3// /}" # 移除空格并分割
|
||
fi
|
||
fi
|
||
|
||
local current_dir="$start_dir"
|
||
current_dir=$(normalize_path "$current_dir")
|
||
local dir_stack=("$current_dir")
|
||
local selected=0 current_page=0 selected_file=""
|
||
|
||
# 定义颜色(局部变量)
|
||
local COLOR_NORMAL=$(tput sgr0)
|
||
local COLOR_HIGHLIGHT=$(tput rev)
|
||
local COLOR_TITLE=$(tput setaf 14)
|
||
local COLOR_SELECTION=$(tput setaf 2)
|
||
local COLOR_DIR=$(tput setaf 6)
|
||
local COLOR_BACK=$(tput setaf 3)
|
||
local COLOR_PAGE=$(tput setaf 5)
|
||
|
||
# 启用备用屏幕缓冲区
|
||
echo -en "\033[?1049h" >&2
|
||
|
||
# 隐藏光标
|
||
tput civis >&2
|
||
|
||
# 获取目录内容
|
||
get_directory_contents() {
|
||
local dir="$1"
|
||
local contents=()
|
||
|
||
# 添加上级目录选项
|
||
[[ "$dir" != "/" && ( "$restrict_mode" -eq 0 || "$current_dir" != "$start_dir" ) ]] && contents+=("..")
|
||
|
||
# 获取目录(始终显示)
|
||
while IFS= read -r -d $'\0' item; do
|
||
[[ -d "$item" ]] && contents+=("${item#$dir/}/")
|
||
done < <(find "$dir" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
|
||
|
||
# 获取文件(支持多种后缀过滤)
|
||
if (( ${#file_exts[@]} > 0 )); then
|
||
# 修复:构建正确的find查询条件
|
||
local find_args=()
|
||
for ext in "${file_exts[@]}"; do
|
||
# 移除可能的空格
|
||
ext="${ext// /}"
|
||
[[ -n "$ext" ]] && find_args+=(-name "*.${ext}" -o)
|
||
done
|
||
|
||
# 移除最后一个多余的 -o
|
||
if (( ${#find_args[@]} > 0 )); then
|
||
unset 'find_args[${#find_args[@]}-1]'
|
||
|
||
# 执行带后缀过滤的查询
|
||
while IFS= read -r -d $'\0' item; do
|
||
contents+=("${item#$dir/}")
|
||
done < <(find "$dir" -mindepth 1 -maxdepth 1 -type f \( "${find_args[@]}" \) -print0 2>/dev/null)
|
||
fi
|
||
else
|
||
# 无后缀过滤时获取所有文件
|
||
while IFS= read -r -d $'\0' item; do
|
||
[[ -f "$item" ]] && contents+=("${item#$dir/}")
|
||
done < <(find "$dir" -mindepth 1 -maxdepth 1 -type f -print0 2>/dev/null)
|
||
fi
|
||
|
||
echo "${contents[@]}"
|
||
}
|
||
|
||
# 显示菜单(输出到 STDERR)
|
||
show_menu() {
|
||
local terminal_rows=$(tput lines)
|
||
local terminal_cols=$(tput cols)
|
||
((items_per_page = terminal_rows - 6))
|
||
|
||
tput clear >&2
|
||
tput cup 0 0 >&2
|
||
|
||
# 显示标题
|
||
if [[ "$restrict_mode" -eq 0 ]]; then
|
||
echo -e "${COLOR_TITLE}当前目录: $current_dir" >&2
|
||
else
|
||
if [ "$current_dir" == "$start_dir" ]; then
|
||
echo -e "${COLOR_TITLE}当前目录 (锁定): $current_dir" >&2
|
||
else
|
||
echo -e "${COLOR_TITLE}当前目录: $current_dir" >&2
|
||
fi
|
||
fi
|
||
echo -e "${custom_prompt}${COLOR_NORMAL}" >&2
|
||
echo >&2
|
||
|
||
# 获取内容
|
||
local MENU_OPTIONS=($(get_directory_contents "$current_dir"))
|
||
local num_options=${#MENU_OPTIONS[@]}
|
||
((total_pages = (num_options + items_per_page - 1) / items_per_page))
|
||
((start_index = current_page * items_per_page))
|
||
((end_index = start_index + items_per_page))
|
||
((end_index > num_options)) && end_index=$num_options
|
||
|
||
# 显示选项
|
||
for ((i = start_index; i < end_index; i++)); do
|
||
local item="${MENU_OPTIONS[$i]}"
|
||
local display_item="$item"
|
||
local display_index=$((i - start_index))
|
||
|
||
if [[ "$item" == ".." ]]; then
|
||
display_item="${COLOR_BACK}[返回上级]${COLOR_NORMAL}"
|
||
elif [[ "$item" == */ ]]; then
|
||
display_item="${COLOR_DIR}[目录] ${item%/}${COLOR_NORMAL}"
|
||
fi
|
||
|
||
if [[ $display_index -eq $selected ]]; then
|
||
echo -e "${COLOR_HIGHLIGHT}${COLOR_SELECTION}➔ $display_item${COLOR_NORMAL}" >&2
|
||
else
|
||
echo -e " $display_item" >&2
|
||
fi
|
||
done
|
||
|
||
# 显示页码
|
||
tput cup $((terminal_rows - 2)) 0 >&2
|
||
if ((total_pages > 1)); then
|
||
echo -e "${COLOR_PAGE}页码: $((current_page + 1))/$total_pages" >&2
|
||
echo -en "↑/↓ 导航 ←/→ 翻页 Enter 确认 B 返回上级 Q 退出${COLOR_NORMAL}" >&2
|
||
else
|
||
echo -en "${COLOR_PAGE}↑/↓ 导航 Enter 确认 B 返回上级 Q 退出${COLOR_NORMAL}" >&2
|
||
fi
|
||
tput ed >&2
|
||
}
|
||
|
||
# 主循环
|
||
show_menu
|
||
local key
|
||
while true; do
|
||
# 读取单个键
|
||
IFS= read -rsn1 key
|
||
|
||
# 处理方向键
|
||
if [[ "$key" == $'\x1B' ]]; then
|
||
# 读取额外的2个字节(方向键)
|
||
read -rsn2 -t 0.01 key
|
||
case "$key" in
|
||
'[A') # 上箭头
|
||
if (( selected > 0 )); then
|
||
((selected--))
|
||
elif (( current_page > 0 )); then
|
||
((current_page--))
|
||
# 计算上一页的项目数
|
||
local prev_items=$(( (terminal_rows - 6) ))
|
||
((selected = prev_items - 1))
|
||
fi
|
||
show_menu
|
||
;;
|
||
'[B') # 下箭头
|
||
local terminal_rows=$(tput lines)
|
||
((items_per_page = terminal_rows - 6))
|
||
local MENU_OPTIONS=($(get_directory_contents "$current_dir"))
|
||
local num_options=${#MENU_OPTIONS[@]}
|
||
((items_on_page = items_per_page))
|
||
(( items_on_page > num_options - current_page * items_per_page )) && \
|
||
items_on_page=$(( num_options - current_page * items_per_page ))
|
||
|
||
if (( selected < items_on_page - 1 )); then
|
||
((selected++))
|
||
elif (( current_page < total_pages - 1 )); then
|
||
((current_page++))
|
||
selected=0
|
||
fi
|
||
show_menu
|
||
;;
|
||
'[D') # 左箭头
|
||
if (( current_page > 0 )); then
|
||
((current_page--))
|
||
selected=0
|
||
show_menu
|
||
fi
|
||
;;
|
||
'[C') # 右箭头
|
||
local terminal_rows=$(tput lines)
|
||
((items_per_page = terminal_rows - 6))
|
||
local MENU_OPTIONS=($(get_directory_contents "$current_dir"))
|
||
local num_options=${#MENU_OPTIONS[@]}
|
||
((total_pages = (num_options + items_per_page - 1) / items_per_page))
|
||
|
||
if (( current_page < total_pages - 1 )); then
|
||
((current_page++))
|
||
selected=0
|
||
show_menu
|
||
fi
|
||
;;
|
||
esac
|
||
else
|
||
# 处理普通键
|
||
case "$key" in
|
||
'') # 回车
|
||
local terminal_rows=$(tput lines)
|
||
((items_per_page = terminal_rows - 6))
|
||
local MENU_OPTIONS=($(get_directory_contents "$current_dir"))
|
||
local num_options=${#MENU_OPTIONS[@]}
|
||
((global_index = current_page * items_per_page + selected))
|
||
|
||
if (( global_index < num_options )); then
|
||
local selected_item="${MENU_OPTIONS[$global_index]}"
|
||
|
||
if [[ "$selected_item" == ".." ]]; then
|
||
if [[ "$restrict_mode" -eq 1 ]] && [ "$current_dir" == "$start_dir" ]; then
|
||
continue
|
||
fi
|
||
[[ "$current_dir" != "/" ]] && current_dir=$(dirname "$current_dir")
|
||
[[ "$current_dir" == "//" ]] && current_dir="/"
|
||
current_dir=$(normalize_path "$current_dir")
|
||
dir_stack+=("$current_dir")
|
||
current_page=0
|
||
selected=0
|
||
show_menu
|
||
elif [[ "$selected_item" == */ ]]; then
|
||
local new_dir=$(normalize_path "$current_dir/${selected_item%/}")
|
||
dir_stack+=("$new_dir")
|
||
current_dir="$new_dir"
|
||
current_page=0
|
||
selected=0
|
||
show_menu
|
||
else
|
||
selected_file=$(normalize_path "${current_dir%/}/$selected_item")
|
||
break
|
||
fi
|
||
fi
|
||
;;
|
||
[bB]) # 返回上级
|
||
if [[ "$restrict_mode" -eq 1 ]] && [ "$current_dir" == "$start_dir" ]; then
|
||
continue
|
||
fi
|
||
[[ "$current_dir" != "/" ]] && current_dir=$(dirname "$current_dir")
|
||
current_dir=$(normalize_path "$current_dir")
|
||
dir_stack+=("$current_dir")
|
||
current_page=0
|
||
selected=0
|
||
show_menu
|
||
;;
|
||
[qQ]) # 退出
|
||
selected_file=""
|
||
break
|
||
;;
|
||
esac
|
||
fi
|
||
done
|
||
|
||
cleanup
|
||
echo "$selected_file" # 输出结果到 STDOUT
|
||
[[ -n "$selected_file" ]] && return 0 || return 1
|
||
}
|
||
|
||
# 使用示例
|
||
# source ./file_selector.sh
|
||
# echo "==== 文件选择器演示 ===="
|
||
# echo "当前目录内容:"
|
||
# ls -l
|
||
#
|
||
# selected_path=$(file_selector "$HOME" -r "请选择文本文件:" "txt")
|
||
#
|
||
# if [[ -n "$selected_path" ]]; then
|
||
# echo "您选择了: $selected_path"
|
||
# echo "文件内容:"
|
||
# head -n 5 "$selected_path" 2>/dev/null || echo "(目录内容无法显示)"
|
||
# else
|
||
# echo "未选择文件"
|
||
# fi
|
||
#
|
||
# echo "==== 脚本继续执行 ===="
|
||
|
||
export -f file_selector |