> 技术文档 > shell脚本02 |批量打包镜像到/opt/docker-test-images |批量传输服务器a镜像tar包到远程服务器b | 批量从本地指定目录解压镜像tar包到服务器 |显示当前镜像列表

shell脚本02 |批量打包镜像到/opt/docker-test-images |批量传输服务器a镜像tar包到远程服务器b | 批量从本地指定目录解压镜像tar包到服务器 |显示当前镜像列表

家人们,咱又写了综合脚本  其实4个脚本可以拆开来单独用,只是我嫌弃脚本太多,

给合并了 

vim 1.sh  

chmod  +x  1.sh

./1.sh

Docker镜像高级管理工具
1. 批量打包镜像到/opt/docker-test-images
2. 传输镜像包到远程服务器
3. 从目录导入镜像包
4. 显示当前镜像列表

#!/bin/bash
# Docker镜像高级管理工具增强版
# 修复循环逻辑,增强进度显示和错误处理

set -u pipefail
trap \'echo -e \"\\n操作被中断\"; exit 1\' SIGINT

# 全局变量
IMAGE_DIR=\"/opt/docker-test-images\"  # 默认镜像存储目录

# 显示当前镜像列表(带序号
function display_images() {
    echo -e \"\\033[36m当前Docker镜像列表:\\033[0m\"
    docker images --format \"table {{.Repository}}\\t{{.Tag}}\\t{{.ID}}\\t{{.Size}}\" | awk \'NR==1 {print \"序号\\t\"$0} NR>1 {print NR-1\"\\t\"$0}\'
}

# 创建镜像存储目录
function create_image_dir() {
    echo -e \"\\n\\033[33m正在创建镜像存储目录...\\033[0m\"
    sudo mkdir -p \"$IMAGE_DIR\"
    sudo chmod 777 \"$IMAGE_DIR\"  # 确保有写入权限
    echo -e \"\\033[32m✓ 目录已创建: $IMAGE_DIR\\033[0m\"
}

# 选项1:批量打包镜像(支持按序号选择)
function batch_export_images() {
    create_image_dir
    echo -e \"\\n\\033[36m===== 镜像列表 =====\\033[0m\"
    
    # 获取带序���的镜像列表
    mapfile -t all_images < <(docker images --format \"{{.Repository}}:{{.Tag}}\" | grep -v \"REPOSITORY:TAG\")
    local index=1
    declare -A image_map
    
    # 显示可导出的镜像列表
    for full_image in \"${all_images[@]}\"; do
        if [[ -z \"$full_image\" || \"$full_image\" == \":\" ]]; then
            continue
        fi
        echo -e \"${index}) \\033[33m${full_image}\\033[0m\"
        image_map[\"$index\"]=\"$full_image\"
        ((index++))
    done
    
    # 用户第一次选择镜像
    read -p \"请输入要导出的镜像序号(支持格式: 1,3,5-8,留空导出全部): \" selection
    declare -a first_selected_images
    if [[ -z \"$selection\" ]]; then
        first_selected_images=(\"${all_images[@]}\")
        echo -e \"\\033[32m✓ 将导出全部镜像(共${#all_images[@]}个)\\033[0m\"
    else
        declare -a selected_indices
        IFS=\',\' read -ra parts <<< \"$selection\"
        for part in \"${parts[@]}\"; do
            if [[ $part =~ ^[0-9]+$ ]]; then
                selected_indices+=(\"$part\")
            elif [[ $part =~ ^([0-9]+)-([0-9]+)$ ]]; then
                start=${BASH_REMATCH[1]}
                end=${BASH_REMATCH[2]}
                for ((i=start; i<=end; i++)); do
                    selected_indices+=(\"$i\")
                done
            fi
        done
        for i in \"${selected_indices[@]}\"; do
            if [[ -n \"${image_map[$i]}\" ]]; then
                first_selected_images+=(\"${image_map[$i]}\")
                echo -e \"已选择: \\033[33m${image_map[$i]}\\033[0m\"
            else
                echo -e \"\\033[31m✗ 无效序号: $i\\033[0m\"
            fi
        done
    fi
    if [[ ${#first_selected_images[@]} -eq 0 ]]; then
        echo -e \"\\033[31m✗ 未选择任何镜像,操作取消\\033[0m\"
        return
    fi
    # 显示“待导出列表”带序号
    echo -e \"\\n\\033[36m待导出镜像列表(再次编号):\\033[0m\"
    for idx in \"${!first_selected_images[@]}\"; do
        echo \"$((idx+1))) ${first_selected_images[$idx]}\"
    done
    # 第二次输入序号
    read -p \"请再次输入要实际导出的镜像序号(如1,3,5,留空全部): \" second_selection
    declare -a selected_images
    if [[ -z \"$second_selection\" ]]; then
        selected_images=(\"${first_selected_images[@]}\")
    else
        IFS=\',\' read -ra second_indices <<< \"$second_selection\"
        for i in \"${second_indices[@]}\"; do
            if [[ \"$i\" =~ ^[0-9]+$ ]] && (( i >= 1 )) && (( i <= ${#first_selected_images[@]} )); then
                selected_images+=(\"${first_selected_images[$((i-1))]}\")
            else
                echo -e \"\\033[33m警告: 无效序号 $i 被忽略\\033[0m\"
            fi
        done
    fi
    if [[ ${#selected_images[@]} -eq 0 ]]; then
        echo -e \"\\033[31m✗ 未选择任何镜像,操作取消\\033[0m\"
        return
    fi
    # 初始化计数器
    local exported_count=0
    local skipped_count=0
    total=${#selected_images[@]}
    current=1
    echo -e \"\\n\\033[36m===== 开始导出 ($total个镜像) =====\\033[0m\"
    for full_image in \"${selected_images[@]}\"; do
        image_name=\"${full_image%:*}\"
        tag=\"${full_image#*:}\"
        safe_name=$(echo \"$image_name\" | sed \'s|[^a-zA-Z0-9._-]|-|g\')
        filename=\"${safe_name}-${tag}.tar.gz\"
        filepath=\"${IMAGE_DIR}/${filename}\"
        if [[ -f \"$filepath\" ]]; then
            echo -e \"\\033[33m↺ ($current/$total) 跳过已存在: ${filename}\\033[0m\"
            ((skipped_count++))
            ((current++))
            continue
        fi
        echo -e \"\\033[34m➤ ($current/$total) 打包中: $full_image → $filename\\033[0m\"
        {
            if docker save \"$full_image\" | gzip > \"$filepath\"; then
                echo -e \"\\033[32m✓ ($current/$total) 打包成功: $filename\\033[0m\"
                ((exported_count++))
            else
                echo -e \"\\033[31m✗ ($current/$total) 打包失败: $full_image\\033[0m\"
            fi
        } || {
            echo -e \"\\033[41m[ERROR] docker save/gzip 执行异常,退出循环 current=$current\\033[0m\"
        }
        ((current++))
    done
    {
        echo -e \"\\n\\033[35m===== 操作完成! =====\\033[0m\"
        echo \"成功打包: $exported_count 个镜像\"
        echo \"跳过重复: $skipped_count 个镜像\"
        echo \"所有镜像包存储在: $IMAGE_DIR\"
    } || {
        echo -e \"\\033[41m[ERROR] 收尾统计语句执行异常\\033[0m\"
    }
}

# 选项2:传输镜像包(增强版)
function transfer_images() {
    # 允许自定义目录
    read -p \"请输入镜像包目录路径(默认$IMAGE_DIR): \" transfer_dir
    transfer_dir=${transfer_dir:-$IMAGE_DIR}
    
    if [[ ! -d \"$transfer_dir\" ]]; then
        echo -e \"\\033[31m目录不存在: $transfer_dir\\033[0m\"
        return 1
    fi

    echo -e \"\\n\\033[36m$transfer_dir 中的镜像包:\\033[0m\"
    
    # 获取镜像包列表
    mapfile -t packages < /dev/null)

    if [[ ${#packages[@]} -eq 0 ]]; then
        echo -e \"\\033[31m未找到任何镜像包\\033[0m\"
        return 1
    fi
    
    # 显示带序号列表
    for i in \"${!packages[@]}\"; do
        echo \"$((i+1))) ${packages[$i]##*/}\"
    done
    
    # 第一次选择包
    read -p \"请选择要传输的镜像包序号(多个用逗号分隔,如1,3,5,留空全部): \" selected
    declare -a first_selected_packages
    if [[ -z \"$selected\" ]]; then
        first_selected_packages=(\"${packages[@]}\")
    else
        IFS=\',\' read -ra selections <<< \"$selected\"
        for sel in \"${selections[@]}\"; do
            index=$((sel-1))
            if [[ $index -ge 0 && $index -lt ${#packages[@]} ]]; then
                first_selected_packages+=(\"${packages[$index]}\")
                echo \"已选择: ${packages[$index]##*/}\"
            fi
        done
    fi
    if [[ ${#first_selected_packages[@]} -eq 0 ]]; then
        echo -e \"\\033[31m未选择任��镜像包,操作取消\\033[0m\"
        return 1
    fi
    # 显示“待传输列表”带序号
    echo -e \"\\n\\033[36m待传输镜像包列表(再次编号):\\033[0m\"
    for idx in \"${!first_selected_packages[@]}\"; do
        echo \"$((idx+1))) ${first_selected_packages[$idx]##*/}\"
    done
    # 第二次输入序号
    read -p \"请再次输入要实际传输的镜像包序号(如1,3,5,留空全部): \" second_selected
    declare -a selected_packages
    if [[ -z \"$second_selected\" ]]; then
        selected_packages=(\"${first_selected_packages[@]}\")
    else
        IFS=\',\' read -ra second_indices <<< \"$second_selected\"
        for i in \"${second_indices[@]}\"; do
            idx=$((i-1))
            if [[ $idx -ge 0 && $idx -lt ${#first_selected_packages[@]} ]]; then
                selected_packages+=(\"${first_selected_packages[$idx]}\")
            else
                echo -e \"\\033[33m警告: 无效序号 $i 被忽略\\033[0m\"
            fi
        done
    fi
    if [[ ${#selected_packages[@]} -eq 0 ]]; then
        echo -e \"\\033[31m未选择任何镜像包,操作取消\\033[0m\"
        return 1
    fi
    
    # 获取目标信息
    read -p \"请输入目标服务器IP地址: \" target_ip
    read -p \"请输入目标服务器用户名(默认root): \" username
    username=${username:-root}
    read -p \"请输入目标路径(默认/tmp): \" target_path
    target_path=${target_path:-/tmp}
    
    # 安全读取密码
    echo -n \"请输入SSH密码(输入时不会显示): \"
    read -s sshpass
    echo
    
    # 设置环境变量(临时)
    export SSHPASS=\"$sshpass\"
    
    # 传输文件(带进度显示)
    transfer_success=0
    transfer_fail=0
    total=${#selected_packages[@]}
    current=1
    
    echo -e \"\\n\\033[33m开始传输文件到 ${username}@${target_ip}:${target_path} ...\\033[0m\"
    
    for pkg in \"${selected_packages[@]}\"; do
        filename=$(basename \"$pkg\")
        echo -n \"($current/$total) ��输: $filename ... \"
        
        if sshpass -e scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \"$pkg\" \"${username}@${target_ip}:${target_path}/\"; then
            echo -e \"\\033[32m成功\\033[0m\"
            ((transfer_success++))
        else
            echo -e \"\\033[31m失败\\033[0m\"
            ((transfer_fail++))
        fi
        ((current++))
    done
    
    # 清理环境变量
    unset SSHPASS
    unset sshpass
    
    echo -e \"\\n\\033[35m传输完成!\\033[0m\"
    echo \"成功传输: $transfer_success 个文件\"
    echo \"失败传输: $transfer_fail 个文件\"
    echo -e \"\\n\\033[35m  注意如果报错 执行下 yum install -y sshpass 确认服务器是否安装sshpass这个命令  \\033[0m\"

    # echo -e \"\\n\\033[35m  代码 UserKnownHostsFile=/dev/null 的意思是自动跳过 yes/no 询问 原因是如果目标主机第一次连接会出现如下提示:Are you sure you want to continue connecting (yes/no)? yes 那么需要跳过这个不然脚本会卡住  代码StrictHostKeyChecking=no  在脚本和自动化场景中,特别是在持续集成/持续部署(CI/CD)流程中,通常会使用StrictHostKeyChecking=no来实现无人值守的连接  \\033[0m\"
}

# 选项3:导入镜像包(增强版)
function import_images() {
    # 保持原有目录选择逻辑
    read -p \"请输入镜像包目录路径(默认$IMAGE_DIR): \" import_dir
    import_dir=${import_dir:-$IMAGE_DIR}
    
    if [[ ! -d \"$import_dir\" ]]; then
        echo -e \"\\033[31m目录不存在: $import_dir\\033[0m\"
        return 1
    fi
    
    echo -e \"\\n\\033[36m$import_dir 中的镜像包:\\033[0m\"
    
    # 获取镜像包列表
    mapfile -t packages < /dev/null)
    
    if [[ ${#packages[@]} -eq 0 ]]; then
        echo -e \"\\033[31m未找到任何镜像包\\033[0m\"
        return 1
    fi
    
    # 显示带序号列表
    for i in \"${!packages[@]}\"; do
        echo \"$((i+1))) ${packages[$i]##*/}\"
    done
    
    # 第一次选择包
    read -p \"请选择要导入的镜像包序号(多个用逗号分隔,如1,3,5,留空全部): \" selected
    declare -a first_selected_packages
    if [[ -z \"$selected\" ]]; then
        first_selected_packages=(\"${packages[@]}\")
    else
        IFS=\',\' read -ra selections <<< \"$selected\"
        for sel in \"${selections[@]}\"; do
            index=$((sel-1))
            if [[ $index -ge 0 && $index -lt ${#packages[@]} ]]; then
                first_selected_packages+=(\"${packages[$index]}\")
                echo \"已选择: ${packages[$index]##*/}\"
            fi
        done
    fi
    if [[ ${#first_selected_packages[@]} -eq 0 ]]; then
        echo -e \"\\033[31m未选择任何镜像包,操作取消\\033[0m\"
        return 1
    fi
    # 显示“待导入列表”带序号
    echo -e \"\\n\\033[36m待导入镜像包列表(再次编号):\\033[0m\"
    for idx in \"${!first_selected_packages[@]}\"; do
        echo \"$((idx+1))) ${first_selected_packages[$idx]##*/}\"
    done
    # 第二次输入序号
    read -p \"请再次输入要实际导入的镜像包序号(如1,3,5,留空全部): \" second_selected
    declare -a selected_packages
    if [[ -z \"$second_selected\" ]]; then
        selected_packages=(\"${first_selected_packages[@]}\")
    else
        IFS=\',\' read -ra second_indices <<< \"$second_selected\"
        for i in \"${second_indices[@]}\"; do
            idx=$((i-1))
            if [[ $idx -ge 0 && $idx -lt ${#first_selected_packages[@]} ]]; then
                selected_packages+=(\"${first_selected_packages[$idx]}\")
            else
                echo -e \"\\033[33m警告: 无效序号 $i 被忽略\\033[0m\"
            fi
        done
    fi
    if [[ ${#selected_packages[@]} -eq 0 ]]; then
        echo -e \"\\033[31m未选择任何镜像包,操作取消\\033[0m\"
        return 1
    fi
    
    # 导入镜像(带进度显示)
    import_success=0
    import_fail=0
    total=${#selected_packages[@]}
    current=1
    
    echo -e \"\\n\\033[33m开始导入镜像...\\033[0m\"
    
    for pkg in \"${selected_packages[@]}\"; do
        filename=$(basename \"$pkg\")
        echo -n \"($current/$total) 导入: $filename ... \"
        {
            if gunzip -c \"$pkg\" | docker load; then
                echo -e \"\\033[32m成功\\033[0m\"
                ((import_success++))
            else
                echo -e \"\\033[31m失败\\033[0m\"
                ((import_fail++))
            fi
        } || {
            echo -e \"\\033[41m[ERROR] gunzip/docker load 执行异常,退出循环 current=$current\\033[0m\"
        }
        ((current++))
    done
    {
        echo -e \"\\n\\033[35m导入完成!\\033[0m\"
        echo \"成功导入: $import_success 个镜像\"
        echo \"失败导入: $import_fail 个镜像\"
    } || {
        echo -e \"\\033[41m[ERROR] 收尾统计语句执行异常\\033[0m\"
    }
}

# 主菜单
function main_menu() {
    while true; do
        echo -e \"\\n\\033[1;36mDocker镜像高级管理工具\\033[0m\"
        echo \"1. 批量打包镜像到/opt/docker-test-images\"
        echo \"2. 传输镜像包到远程服务器\"
        echo \"3. 从目录导入镜像包\"
        echo \"4. 显示当前镜像列表\"
        echo \"5. 退出\"
        
        read -p \"请选择操作: \" choice
        
        case $choice in
            1) batch_export_images ;;
            2) transfer_images ;;
            3) import_images ;;
            4) display_images ;;
            5) echo \"退出程序\"; exit 0 ;;
            *) echo -e \"\\033[31m无效选择,请重新输入\\033[0m\" ;;
        esac
    done
}

# 启动程序
main_menu