first commit
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user