随机
Enter 搜索 ↑↓ 切换 Esc 清空

linux_file_transfer

脚本

基于 Zmodem 协议实现服务器与本地间 文件 / 文件夹上传、下载、批量文件传输、压缩解压、功能的交互式文件传输管理脚本。

linux_file_transfer

基于 Zmodem 协议实现服务器与本地间 文件 / 文件夹上传、下载、批量文件传输、压缩解压、功能的交互式文件传输管理脚本。

一键脚本

bash <(curl -sL gitee.com/meimolihan/linux-command_sh/raw/master/linux_file_transfer.sh)

效果预览

执行脚本效果预览

脚本源码

#!/bin/bash
set -uo pipefail

list_color_init() {
    export gl_hui=$'\033[38;5;59m'
    export gl_hong=$'\033[38;5;9m'
    export gl_lv=$'\033[38;5;10m'
    export gl_huang=$'\033[38;5;11m'
    export gl_lan=$'\033[38;5;32m'
    export gl_bai=$'\033[38;5;15m'
    export gl_zi=$'\033[38;5;13m'
    export gl_bufan=$'\033[38;5;14m'
    export gl_qing=$'\033[38;5;14m'
    export reset=$'\033[0m'
}
list_color_init

log_info()  { echo -e "${gl_lan}[信息]${gl_bai} $*"; }
log_ok()    { echo -e "${gl_lv}[成功]${gl_bai} $*"; }
log_warn()  { echo -e "${gl_huang}[警告]${gl_bai} $*"; }
log_error() { echo -e "${gl_hong}[错误]${gl_bai} $*" >&2; }

break_end() {
    echo -e "${gl_lv}操作完成${gl_bai}"
    echo -e "${gl_bai}按任意‮继键‬续${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai} \c"
    read -r -n 1 -s -p ""
    echo ""
    clear
}

sleep_fractional() {
    local seconds=$1
    if sleep "$seconds" 2>/dev/null; then return 0; fi
    if command -v perl >/dev/null 2>&1; then perl -e "select(undef, undef, undef, $seconds)"; return 0; fi
    if command -v python3 >/dev/null 2>&1; then python3 -c "import time; time.sleep($seconds)"; return 0; fi
    if command -v python >/dev/null 2>&1; then python -c "import time; time.sleep($seconds)"; return 0; fi
    local int_seconds=$(echo "$seconds" | awk '{print int($1+0.999)}')
    sleep "$int_seconds"
}

exit_animation() {
    echo -ne "${gl_lv}即‮退将‬出 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.5
    echo -ne "${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.6
    echo ""
    clear
}

cancel_return() {
    local menu_name="${1:-上一级选单}"
    echo -ne "${gl_lv}即将返回 ${gl_huang}${menu_name}${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.5
    echo -ne "${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.6
    echo ""
    clear
}

exit_script() {
    echo ""
    echo -ne "${gl_hong}感谢使用,再见!${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.5
    echo -ne "${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.6
    clear
    exit 0
}

handle_y_n() {
    echo -ne "\r${gl_hong}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}${gl_hong}N${gl_bai})${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.3
    echo -ne "\r${gl_huang}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}${gl_hong}N${gl_bai})${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.3
    echo -ne "\r${gl_lv}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}${gl_hong}N${gl_bai})${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.6
    echo ""
    return 2
}

handle_invalid_input() {
    echo -ne "\r${gl_hong}无效的输入,请重新输入 ${gl_zi} 2 ${gl_hong}秒后返回 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.3
    echo -ne "\r${gl_huang}无效的输入,请重新输入 ${gl_zi} 1 ${gl_huang}秒后返回 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.3
    echo -e "\r${gl_lv}无效的输入,请重新输入 ${gl_zi} 0 ${gl_lv}秒后返回 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.6
    return 2
}

install() {
    [[ $# -eq 0 ]] && {
        log_error "未提供软件包参数!"
        return 1
    }

    local pkg mgr ver cmd_ver pkg_ver installed=false
    for pkg in "$@"; do
        installed=false
        ver=""
        
        if command -v "$pkg" &>/dev/null; then
            cmd_ver=$("$pkg" --version 2>/dev/null | head -n1 | tr -cd '[:print:]' | grep -oE '[0-9]+(\.[0-9]+)+' | head -n1 || echo "")
            [[ -n "$cmd_ver" ]] && ver="$cmd_ver"
            installed=true
        fi
        
        if [[ "$pkg" == "7zip" || "$pkg" == "7z" ]]; then
            if command -v 7z &>/dev/null; then
                ver=$(7z 2>&1 | grep -oE '[0-9]+(\.[0-9]+)+' | head -n1 || echo "")
                [[ -n "$ver" ]] && installed=true
            fi
        fi
        
        if [[ "$installed" == false ]]; then
            if command -v opkg &>/dev/null; then
                if opkg list-installed | grep -q "^${pkg} "; then
                    installed=true
                    ver=$(opkg list-installed | grep "^${pkg} " | awk '{print $3}' 2>/dev/null || echo "")
                fi
            elif command -v dpkg-query &>/dev/null; then
                if dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "install ok installed"; then
                    installed=true
                    ver=$(dpkg-query -W -f='${Version}' "$pkg" 2>/dev/null || echo "")
                fi
            elif command -v rpm &>/dev/null; then
                if rpm -q "$pkg" &>/dev/null; then
                    installed=true
                    ver=$(rpm -q --qf '%{VERSION}' "$pkg" 2>/dev/null || echo "")
                fi
            elif command -v apk &>/dev/null; then
                if apk info "$pkg" 2>/dev/null | grep -q "^installed"; then
                    installed=true
                    ver=$(apk info -a "$pkg" 2>/dev/null | grep -oE '[0-9]+(\.[0-9]+)+' | head -n1 || echo "")
                fi
            elif command -v pacman &>/dev/null; then
                if pacman -Qi "$pkg" &>/dev/null; then
                    installed=true
                    ver=$(pacman -Qi "$pkg" 2>/dev/null | grep -i "version" | grep -oE '[0-9]+(\.[0-9]+)+' | head -n1 || echo "")
                fi
            fi
        fi
        
        if [[ "$installed" == true ]]; then
            echo -e "${gl_huang}${pkg}${gl_bai} ${gl_lv}已安装${gl_bai}" \
                "$([[ -n "$ver" ]] && echo "版本 ${gl_lv}${ver}${gl_bai}")"
            continue
        fi
        
        echo -e ""
        echo -e "${gl_huang}开始安装:${gl_bai}${pkg}"
        echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
        
        local install_success=false
        
        for mgr in opkg dnf yum apt apk pacman zypper pkg; do
            if ! command -v "$mgr" &>/dev/null; then
                continue
            fi
            
            case $mgr in
            opkg)
                echo -e "${gl_bai}使用包管理器: ${gl_zi}opkg (OpenWrt/iStoreOS)${gl_bai}"
                if [[ "$pkg" == "7zip" || "$pkg" == "7z" ]]; then
                    echo -e "${gl_bai}正在安装: ${gl_lv}p7zip${gl_bai}"
                    opkg update && opkg install p7zip && install_success=true
                else
                    opkg update && opkg install "$pkg" && install_success=true
                fi
                ;;
            dnf)
                echo -e "${gl_bai}使用包管理器: ${gl_zi}dnf (Fedora/RHEL)${gl_bai}"
                dnf -y update && dnf install -y "$pkg" && install_success=true
                ;;
            yum)
                echo -e "${gl_bai}使用包管理器: ${gl_zi}yum (CentOS/RHEL)${gl_bai}"
                yum -y update && yum install -y "$pkg" && install_success=true
                ;;
            apt)
                echo -e "${gl_bai}使用包管理器: ${gl_zi}apt (Debian/Ubuntu)${gl_bai}"
                apt update -y && apt install -y "$pkg" && install_success=true
                ;;
            apk)
                echo -e "${gl_bai}使用包管理器: ${gl_zi}apk (Alpine)${gl_bai}"
                apk update && apk add "$pkg" && install_success=true
                ;;
            pacman)
                echo -e "${gl_bai}使用包管理器: ${gl_zi}pacman (Arch/Manjaro)${gl_bai}"
                pacman -Syu --noconfirm && pacman -S --noconfirm "$pkg" && install_success=true
                ;;
            zypper)
                echo -e "${gl_bai}使用包管理器: ${gl_zi}zypper (openSUSE)${gl_bai}"
                zypper refresh && zypper install -y "$pkg" && install_success=true
                ;;
            pkg)
                echo -e "${gl_bai}使用包管理器: ${gl_zi}pkg (FreeBSD)${gl_bai}"
                pkg update && pkg install -y "$pkg" && install_success=true
                ;;
            esac
            
            [[ "$install_success" == true ]] && break
        done
        
        if [[ "$install_success" == true ]]; then
            echo -e "${gl_lv}${pkg} 安装成功${gl_bai}"
        else
            echo -e "${gl_hong}${pkg} 安装失败${gl_bai}"
        fi
        
        echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    done
}

list_files() {
    local target_dir="${1:-.}"
    local show_hidden="${2:-0}"
    local custom_cols="${3:-2}"
    local dir="${CURRENT_DIR:-.}"
    
    LIST_FILES_ARRAY=()
    LIST_FILES_COUNT=0
    
    if [[ ! -d "$target_dir" ]]; then
        log_error "目录不存在: $target_dir"
        exit_animation
        return 1
    fi
    
    local original_dir="$(pwd)"
    
    if ! cd "$target_dir" 2>/dev/null; then
        log_error "无法进入目录: $target_dir"
        exit_animation
        return 1
    fi
    
    local file_items=()
    local dir_items=()
    
    if [[ "$show_hidden" == "0" ]]; then
        for item in *; do
            [[ "$item" == "*" ]] && continue
            
            if [[ -f "$item" ]]; then
                file_items+=("$item")
            elif [[ -d "$item" ]]; then
                dir_items+=("$item")
            fi
        done
    else
        for item in *; do
            [[ "$item" == "*" ]] && continue
            
            if [[ -f "$item" ]]; then
                file_items+=("$item")
            elif [[ -d "$item" ]]; then
                dir_items+=("$item")
            fi
        done
        
        for item in .*; do
            [[ "$item" == "." ]] && continue
            [[ "$item" == ".." ]] && continue
            [[ "$item" == ".*" ]] && continue
            
            if [[ -f "$item" ]]; then
                file_items+=("$item")
            elif [[ -d "$item" ]]; then
                dir_items+=("$item")
            fi
        done
    fi
    
    cd "$original_dir" || return 1
    
    LIST_FILES_ARRAY=("${dir_items[@]}" "${file_items[@]}")
    LIST_FILES_COUNT=${#LIST_FILES_ARRAY[@]}
    
    calculate_display_width() {
        local str="$1"
        local width=0
        local i
        local char
        local utf8_char
        
        for ((i=0; i<${#str}; i++)); do
            char="${str:$i:1}"
            utf8_char=$(printf "%s" "$char" | od -An -tx1 | tr -d ' ')
            
            if [[ ${#utf8_char} -eq 2 && "0x$utf8_char" -le "0x7F" ]]; then
                ((width++))
            else
                ((width += 2))
            fi
        done
        echo "$width"
    }
    
    format_fixed_width() {
        local str="$1"
        local fixed_width="${2:-18}"
        
        local result=""
        local char_width=0
        local total_width=0
        
        total_width=$(calculate_display_width "$str")
        
        if [[ $total_width -le $fixed_width ]]; then
            result="$str"
            char_width=$total_width
        else
            local ext=""
            local name_part="$str"
            local has_extension=false
            
            for ((i=${#str}-1; i>=0; i--)); do
                if [[ "${str:$i:1}" == "." && $i -gt 0 && $i -lt $((${#str}-1)) ]]; then
                    ext="${str:$i}"
                    name_part="${str:0:$i}"
                    has_extension=true
                    break
                fi
            done
            
            local ext_width=0
            if $has_extension; then
                ext_width=$(calculate_display_width "$ext")
            fi
            
            local reserved_for_ellipsis=2
            local name_available_width=$((fixed_width - ext_width - reserved_for_ellipsis))
            if [[ $name_available_width -lt 1 ]]; then
                name_available_width=1
            fi
            
            local truncated_name=""
            local current_width=0
            local i=0
            
            while [[ $i -lt ${#name_part} && $current_width -lt $name_available_width ]]; do
                local char="${name_part:$i:1}"
                local char_w=1
                
                local utf8_char=$(printf "%s" "$char" | od -An -tx1 | tr -d ' ')
                if [[ ${#utf8_char} -gt 2 || ("0x$utf8_char" -gt "0x7F" && ${#utf8_char} -eq 2) ]]; then
                    char_w=2
                fi
                
                if [[ $((current_width + char_w)) -le $name_available_width ]]; then
                    truncated_name="${truncated_name}${char}"
                    current_width=$((current_width + char_w))
                    ((i++))
                else
                    break
                fi
            done
            
            truncated_name="${truncated_name}.."
            current_width=$((current_width + reserved_for_ellipsis))
            
            if $has_extension; then
                result="${truncated_name}${ext}"
                char_width=$((current_width + ext_width))
            else
                result="$truncated_name"
                char_width=$current_width
            fi
        fi
        
        while [[ $char_width -lt $fixed_width ]]; do
            result="${result} "
            ((char_width++))
        done
        
        echo "$result"
    }
    
    local items_per_line="$custom_cols"
    ((items_per_line < 1)) && items_per_line=2
    ((items_per_line > 4)) && items_per_line=4
    
    local name_width
    local total_available_width=80
    local column_padding=3
    
    case $items_per_line in
        1) 
            name_width=70 
            ;;
        2) 
            name_width=$(( (total_available_width - column_padding) / 2 ))
            ;;
        3) 
            name_width=$(( (total_available_width - 2 * column_padding) / 3 ))
            ;;
        4) 
            name_width=$(( (total_available_width - 3 * column_padding) / 4 ))
            ;;
        *) 
            name_width=30 
            ;;
    esac
    
    ((name_width < 8)) && name_width=8
    ((name_width > 70)) && name_width=70
    
    echo ""
    echo -e "${gl_huang}>>> 当前目录文件列表:${gl_bai}(${gl_lv}$(pwd)${gl_bai})"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    
    if [ -z "$(ls -A)" ]; then
        echo -e "${gl_huang}当前目录为空${gl_bai}"
    fi

    local count=0
    for ((i=0; i<LIST_FILES_COUNT; i++)); do
        ((count++))
        local item="${LIST_FILES_ARRAY[$i]}"
        local item_path="$target_dir/$item"
        
        local color="${gl_lv}"
        if [[ -d "$item_path" ]]; then
            color="${gl_zi}"
        elif [[ -x "$item_path" ]]; then
            color="${gl_huang}"
        elif [[ "$item" == .* ]]; then
            color="${gl_hui}"
        elif [[ "$item" =~ \.(zip|tar|gz|bz2|7z|rar)$ ]]; then
            color="${gl_hong}"
        elif [[ "$item" =~ \.(sh|bash|zsh)$ ]]; then
            color="${gl_lan}"
        elif [[ "$item" =~ \.(txt|md|conf|ini|cfg)$ ]]; then
            color="${gl_qing}"
        fi
        
        local display_name=$(format_fixed_width "$item" $name_width)
        
        printf "${gl_bufan}%3d.${gl_bai} ${color}%s" "$count" "$display_name"
        
        if ((count % items_per_line != 0 && count < LIST_FILES_COUNT)); then
            case $items_per_line in
                2) printf "   " ;;
                3) printf "  " ;;
                4) printf " " ;;
                *) printf "  " ;;
            esac
        fi
        
        if ((count % items_per_line == 0 || count == LIST_FILES_COUNT)); then
            echo ""
        fi
    done
    
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    echo -e "${gl_bai}目录:${gl_zi}${gl_bai} 文件:${gl_lv}${gl_bai} 隐藏:${gl_hui}${gl_bai} 压缩:${gl_hong}${gl_bai} ${gl_bai}脚本:${gl_lan}${gl_bai} 配置:${gl_qing}${gl_bai} 执行:${gl_huang}${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    
    return 0
}

rz_upload_file() {
    clear
    list_files "." 0 4
    echo -e ""
    echo -e "${gl_zi}>>> 上传文件到服务器 (rz)${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    log_info "准备接收来自本地的文件"
    echo -e "${gl_bai}终端支持检测:${gl_bai}"
    echo -e "${gl_bai}支持的终端: ${gl_lv}MobaXterm, Xshell, SecureCRT, Tabby, WindTerm${gl_bai}"
    echo -e "${gl_huang}注意: PuTTY 不支持Zmodem协议${gl_bai}"
    echo -e "${gl_huang}注意: Windows Terminal 需安装lrzsz并配置${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    
    echo -e "${gl_huang}请在本地客户端选择要上传的文件${gl_bai}"
    
    for i in {2..1}; do
        echo -ne "${gl_huang}将在 ${gl_lv}$i${gl_bai} 秒后弹出文件选择对话框 \r"
        sleep_fractional 1
    done
    echo ""
    
    if command -v rz >/dev/null 2>&1; then
        echo -e "${gl_bai}执行命令: ${gl_zi}rz${gl_bai}"
        echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
        echo -e "${gl_huang}提示: 按${gl_lv}Ctrl+C${gl_bai}取消上传${gl_bai}"
        
        if rz; then
            log_ok "文件上传成功!"
            echo -e "${gl_bai}上传的文件:${gl_bai}"
            ls -lth | head -5
        else
            if [[ $? -eq 130 ]]; then
                log_info "上传被用户取消"
            else
                log_warn "文件上传失败,请检查终端是否支持Zmodem"
            fi
        fi
    else
        log_error "rz命令不可用,请先安装lrzsz"
    fi
    
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    break_end
}

rz_upload_compressed_file() {
    clear
    echo -e "${gl_zi}>>> 上传文件夹到服务器 (压缩上传)${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    log_info "此功能将本地文件夹压缩后上传到服务器"
    echo -e "${gl_bai}支持格式:${gl_bai}"
    echo -e "  ${gl_bufan}1. ${gl_bai}通过rz上传ZIP/TAR压缩包"
    echo -e "  ${gl_bufan}2. ${gl_bai}自动解压到当前目录"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    
    local -a files_before=(*)
    
    read -r -e -p "$(echo -e "${gl_bai}是否自动解压? (${gl_lv}y${gl_bai}/${gl_hong}n${gl_bai}): ")" extract_choice
    
    for i in {2..1}; do
        echo -ne "${gl_huang}将在 ${gl_lv}$i${gl_bai} 秒后开始上传 \r"
        sleep_fractional 1
    done
    echo ""
    
    if ! command -v rz >/dev/null 2>&1; then
        log_error "rz命令不可用,请先安装 lrzsz"
        break_end
        return 1
    fi
    
    echo -e "${gl_huang}提示: 按${gl_lv}Ctrl+C${gl_bai}取消上传${gl_bai}"
    echo -e "${gl_lv}请在选择文件对话框中选择要上传的压缩包${gl_bai}"
    
    if ! rz; then
        local exit_code=$?
        [[ $exit_code -eq 130 ]] && log_info "上传被用户取消" || log_warn "上传失败"
        break_end
        return 1
    fi
    
    local -a files_after=(*) new_files=()
    for f in "${files_after[@]}"; do
        local is_new=true
        for old in "${files_before[@]}"; do
            [[ "$f" == "$old" ]] && { is_new=false; break; }
        done
        [[ "$is_new" == true ]] && new_files+=("$f")
    done
    
    if [[ ${#new_files[@]} -eq 0 ]]; then
        log_warn "未检测到新上传的文件"
        break_end
        return 1
    fi
    
    local zip_file
    if [[ ${#new_files[@]} -eq 1 ]]; then
        zip_file="${new_files[0]}"
        log_ok "检测到上传文件: ${gl_huang}${zip_file}${gl_bai}"
    else
        log_info "检测到多个新文件:"
        local i=1
        for f in "${new_files[@]}"; do
            echo -e "  ${gl_lv}${i}.${gl_bai} $f"
            ((i++))
        done
        read -r -e -p "请选择编号 [1-${#new_files[@]}]: " choice
        [[ "$choice" =~ ^[0-9]+$ && $choice -ge 1 && $choice -le ${#new_files[@]} ]] || {
            log_error "无效选择"
            break_end
            return 1
        }
        zip_file="${new_files[$((choice-1))]}"
    fi
    
    if [[ "$extract_choice" == [yY] ]]; then
        log_info "正在解压: ${gl_huang}${zip_file}${gl_bai}"
        local extract_success=false
        
        case "$zip_file" in
            *.zip)
                command -v unzip >/dev/null && unzip -o "$zip_file" >/dev/null 2>&1 && {
                    log_ok "ZIP 解压完成"
                    extract_success=true
                } || log_error "解压失败或缺少 unzip"
                ;;
            *.tar.gz|*.tgz) tar -xzf "$zip_file" >/dev/null 2>&1 && { log_ok "TAR.GZ 解压完成"; extract_success=true; } ;;
            *.tar.bz2) tar -xjf "$zip_file" >/dev/null 2>&1 && { log_ok "TAR.BZ2 解压完成"; extract_success=true; } ;;
            *.tar.xz) tar -xJf "$zip_file" >/dev/null 2>&1 && { log_ok "TAR.XZ 解压完成"; extract_success=true; } ;;
            *.tar) tar -xf "$zip_file" >/dev/null 2>&1 && { log_ok "TAR 解压完成"; extract_success=true; } ;;
            *.gz) gunzip -f "$zip_file" >/dev/null 2>&1 && { log_ok "GZ 解压完成"; extract_success=true; } ;;
            *) log_warn "不支持的格式" ;;
        esac
        
        if [[ "$extract_success" == true ]]; then
            read -r -e -p "是否删除压缩包? (y/n): " delete_choice
            [[ "$delete_choice" == [yY] ]] && rm -f "$zip_file" && log_ok "已删除压缩包"
        fi
    else
        log_info "文件已保存:$(pwd)/$zip_file"
    fi
    
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    break_end
}

rz_download_folder() {
    clear
    echo -e "${gl_zi}>>> 下载文件夹到本地 (压缩下载)${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    
    local -a dir_array=() dir_names=()
    while IFS= read -r -d '' dir; do
        dir_array+=("$dir")
        dir_names+=("$(basename "$dir")")
    done < <(find . -maxdepth 1 -type d -not -name ".*" -print0 2>/dev/null | sort -zV)
    
    if [[ ${#dir_array[@]} -eq 0 ]]; then
        log_warn "当前目录无可下载文件夹"
        break_end
        return 1
    fi
    
    echo -e "${gl_bai}文件夹列表:${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    for i in "${!dir_array[@]}"; do
        printf "${gl_bufan}%3d. ${gl_bai}%-30s\n" $((i+1)) "${dir_names[$i]}"
    done
    
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    read -r -e -p "请输入序号 (0返回): " dir_choice
    [[ "$dir_choice" == "0" ]] && { cancel_return "返回"; return 1; }
    [[ ! "$dir_choice" =~ ^[0-9]+$ || $dir_choice -lt 1 || $dir_choice -gt ${#dir_array[@]} ]] && {
        log_error "无效序号"
        break_end
        return 1
    }
    
    local selected_dir="${dir_array[$((dir_choice-1))]}"
    local dir_name="${dir_names[$((dir_choice-1))]}"
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local zip_file="${dir_name}_${timestamp}"
    
    echo -e "${gl_bai}选择:${gl_huang}$dir_name${gl_bai}"
    echo -e "${gl_huang}>>> 选择压缩格式${gl_bai}"
    echo -e "1.tar.gz 2.zip 3.tar.bz2 4.tar.xz"
    read -r -e -p "请选择 [1-4]: " format_choice
    
    case $format_choice in
        1) zip_file+=".tar.gz"; tar -czf "$zip_file" "$dir_name" >/dev/null 2>&1 || { log_error "压缩失败"; break_end; return 1; } ;;
        2) zip_file+=".zip"; command -v zip >/dev/null && zip -rq "$zip_file" "$dir_name" >/dev/null 2>&1 || { log_error "缺少zip"; break_end; return 1; } ;;
        3) zip_file+=".tar.bz2"; tar -cjf "$zip_file" "$dir_name" >/dev/null 2>&1 || { log_error "压缩失败"; break_end; return 1; } ;;
        4) zip_file+=".tar.xz"; tar -cJf "$zip_file" "$dir_name" >/dev/null 2>&1 || { log_error "压缩失败"; break_end; return 1; } ;;
        *) log_warn "使用默认tar.gz"; zip_file+=".tar.gz"; tar -czf "$zip_file" "$dir_name" >/dev/null 2>&1 ;;
    esac
    
    [[ -f "$zip_file" ]] || { log_error "压缩失败"; break_end; return 1; }
    log_ok "压缩完成:$zip_file"
    
    read -r -e -p "是否下载? (y/n): " download_choice
    if [[ "$download_choice" == [yY] ]]; then
        for i in {2..1}; do echo -ne "将在 $i 秒后开始下载 \r"; sleep_fractional 1; done
        echo ""
        sz "$zip_file" && log_ok "下载成功" || log_info "下载取消/中断"
    fi
    
    read -r -e -p "是否删除临时压缩包? (y/n): " delete_choice
    [[ "$delete_choice" == [yY] ]] && rm -f "$zip_file" && log_ok "已删除"
    
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    break_end
}

rz_download_files_interactive() {
    clear
    echo -e "${gl_zi}>>> 选择文件下载 (交互式)${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    
    local search_pattern
    read -r -e -p "搜索模式(*显示全部): " search_pattern
    search_pattern=${search_pattern:-*}
    
    local -a file_list=()
    while IFS= read -r -d '' file; do
        file_list+=("$file")
    done < <(find . -maxdepth 1 -type f -name "$search_pattern" -print0 2>/dev/null | sort -zV)
    
    [[ ${#file_list[@]} -eq 0 ]] && { log_warn "无匹配文件"; break_end; return 1; }
    
    echo -e "${gl_bai}文件列表:${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    local -a interactive_files=()
    for i in "${!file_list[@]}"; do
        printf "${gl_bufan}%3d. ${gl_bai}%-40s\n" $((i+1)) "$(basename "${file_list[$i]}")"
        interactive_files+=("${file_list[$i]}")
    done
    
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    read -r -e -p "选择(序号/1-5/1,3): " file_choices
    [[ "$file_choices" == "0" ]] && { cancel_return "返回"; return 1; }
    [[ -z "$file_choices" ]] && { log_error "输入不能为空"; break_end; return 1; }
    
    local -a selected_files=()
    if [[ "$file_choices" =~ ^[0-9]+$ ]]; then
        [[ $file_choices -ge 1 && $file_choices -le ${#interactive_files[@]} ]] && selected_files+=("${interactive_files[$((file_choices-1))]}")
    elif [[ "$file_choices" =~ ^[0-9]+-[0-9]+$ ]]; then
        local start=$(echo $file_choices | cut -d'-' -f1) end=$(echo $file_choices | cut -d'-' -f2)
        for ((i=start; i<=end; i++)); do selected_files+=("${interactive_files[$((i-1))]}"); done
    else
        IFS=',' read -ra choices <<< "$file_choices"
        for c in "${choices[@]}"; do
            c=$(echo $c | tr -d ' ')
            [[ "$c" =~ ^[0-9]+$ && $c -ge 1 && $c -le ${#interactive_files[@]} ]] && selected_files+=("${interactive_files[$((c-1))]}")
        done
    fi
    
    [[ ${#selected_files[@]} -eq 0 ]] && { log_error "无有效选择"; break_end; return 1; }
    
    echo -e "选择了 ${#selected_files[@]} 个文件"
    read -r -e -p "确认下载? (y/n): " confirm
    if [[ "$confirm" == [yY] ]]; then
        for i in {2..1}; do echo -ne "将在 $i 秒后开始 \r"; sleep_fractional 1; done
        echo ""
        sz "${selected_files[@]}" && log_ok "下载完成" || log_info "取消/中断"
    fi
    
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    break_end
}

rz_upload_files_batch() {
    clear
    echo -e "${gl_zi}>>> 批量上传多个文件${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    log_info "支持一次上传多个文件"
    read -r -e -p "是否继续? (y/n): " confirm
    [[ "$confirm" != [yY] ]] && { break_end; return; }
    
    for i in {2..1}; do echo -ne "将在 $i 秒后弹出选择框 \r"; sleep_fractional 1; done
    echo ""
    
    if command -v rz >/dev/null; then
        rz -bye >/dev/null 2>&1 && log_ok "批量上传成功" || {
            [[ $? -eq 130 ]] && log_info "已取消" || { log_warn "批量失败,尝试普通模式"; rz; }
        }
    else
        log_error "rz 不可用"
    fi
    
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    break_end
}

rz_check_zmodem_support() {
    clear
    echo -e "${gl_zi}>>> 检查终端Zmodem支持${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    command -v sz && command -v rz && log_ok "lrzsz 已安装" || log_warn "lrzsz 未安装"
    [[ -n "$SSH_TTY" ]] && log_ok "SSH 连接正常" || log_warn "非SSH连接可能不支持"
    echo -e "支持:MobaXterm/Xshell/SecureCRT/Tabby/WindTerm"
    echo -e "不推荐:PuTTY / Windows Terminal(需配置)"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    break_end
}

rz_view_transfer_history() {
    clear
    echo -e "${gl_zi}>>> 传输历史/最近文件${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    echo -e "最近修改文件:"
    find . -maxdepth 1 -type f -printf "%T+\t%f\n" | sort -r | head -10
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    break_end
}

rz_download_files_to_local() {
    local target_dir="${1:-.}"
    install lrzsz bc
    clear
    echo -e "${gl_zi}>>> 下载文件到本地 (sz)${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    
    list_files "." 0 10
    
    read -r -e -p "选择文件(序号/通配符/名称,0返回):" user_input
    [[ "$user_input" == "0" ]] && { cancel_return "返回"; return 1; }
    
    local -a all_files=(*) selected_files=()
    if [[ "$user_input" =~ ^[0-9]+(-[0-9]+)?$ || "$user_input" =~ ^[0-9]+(\ [0-9]+)*$ ]]; then
        [[ "$user_input" =~ ^[0-9]+$ ]] && selected_files+=("${all_files[$((user_input-1))]}")
        [[ "$user_input" =~ ^[0-9]+-[0-9]+$ ]] && {
            local s=$(echo $user_input | cut -d- f1) e=$(echo $user_input | cut -d- f2)
            for ((i=s; i<=e; i++)); do selected_files+=("${all_files[$((i-1))]}"); done
        }
    elif [[ "$user_input" == *\** ]]; then
        selected_files=($user_input)
    else
        selected_files=($user_input)
    fi
    
    [[ ${#selected_files[@]} -eq 0 ]] && { log_error "未匹配到文件"; break_end; return 1; }
    
    echo -e "选中:${#selected_files[@]} 个文件"
    read -r -e -p "确认下载? (y/n): " confirm
    [[ "$confirm" != [yY] ]] && { log_info "已取消"; break_end; return; }
    
    for i in {3..1}; do echo -ne "将在 $i 秒后开始传输 \r"; sleep_fractional 1; done
    echo ""
    sz "${selected_files[@]}" && log_ok "传输完成" || log_info "取消/中断"
    
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    break_end
}

rz_clean_temp_files() {
    clear
    echo -e "${gl_zi}>>> 清理临时文件${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    local -a temp_files=()
    while IFS= read -r -d '' f; do temp_files+=("$f"); done < <(find . -maxdepth 1 -type f \( -name "*.tmp" -o -name "*.swp" -o -name "*~" \) -print0 2>/dev/null)
    
    [[ ${#temp_files[@]} -eq 0 ]] && { log_info "无临时文件"; break_end; return; }
    
    echo -e "找到 ${#temp_files[@]} 个临时文件"
    read -r -e -p "删除? (y/n): " del
    [[ "$del" == [yY] ]] && {
        rm -f "${temp_files[@]}"
        log_ok "清理完成"
    } || log_info "已取消"
    
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    break_end
}

rz_create_test_files() {
    clear
    echo -e "${gl_zi}>>> 创建测试文件${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    echo -e "1.1KB  2.1MB  3.10MB  4.自定义"
    read -r -e -p "选择:" opt
    
    case $opt in
        1) echo "test" > test_1k.txt; log_ok "已创建 1KB 文件" ;;
        2) dd if=/dev/zero of=test_1m.txt bs=1M count=1 >/dev/null 2>&1; log_ok "已创建 1MB 文件" ;;
        3) dd if=/dev/zero of=test_10m.txt bs=1M count=10 >/dev/null 2>&1; log_ok "已创建 10MB 文件" ;;
        4) read -p "大小(1K/1M): " sz; dd if=/dev/zero of=test_custom.txt bs=$sz count=1 >/dev/null 2>&1; log_ok "创建完成" ;;
    esac
    
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    break_end
}

file_transfer_manager() {
    while true; do
        install lrzsz bc zip unzip
        clear
        echo -e "${gl_zi}>>> Zmodem 文件传输管理器${gl_bai}"
        echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
        echo -e "${gl_bufan}1.  ${gl_bai}上传文件      ${gl_bufan}2.  ${gl_bai}下载文件 "
        echo -e "${gl_bufan}3.  ${gl_bai}上传文件夹    ${gl_bufan}4.  ${gl_bai}下载文件夹"
        echo -e "${gl_bufan}5.  ${gl_bai}选择下载      ${gl_bufan}6.  ${gl_bai}批量上传"
        echo -e "${gl_bufan}7.  ${gl_bai}测试文件      ${gl_bufan}8.  ${gl_bai}清理临时文件"
        echo -e "${gl_bufan}9.  ${gl_bai}检测Zmodem    ${gl_bufan}10. ${gl_bai}传输记录"
        echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
        echo -e "${gl_hong}0.  ${gl_bai}退出脚本"
        echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
        
        read -r -e -p "请输入你的选择:" choice
        case $choice in
            1)  rz_upload_file ;;
            2)  rz_download_files_to_local ;;
            3)  rz_upload_compressed_file ;;
            4)  rz_download_folder ;;
            5)  rz_download_files_interactive ;;
            6)  rz_upload_files_batch ;;
            7)  rz_create_test_files ;;
            8)  rz_clean_temp_files ;;
            9)  rz_check_zmodem_support ;;
            10) rz_view_transfer_history ;;
            0)  exit_script ;;
            *)  handle_invalid_input ;;
        esac
    done
}

file_transfer_manager

创建本地脚本

new_script="new_test.sh"

cat > "$new_script" << 'EOF'
#!/bin/bash

# 粘贴脚本源码

EOF

# 保留本地脚本,去掉 rm -f "$new_script"
chmod +x "$new_script" && ./"$new_script" && rm -f "$new_script"