#!/bin/bash
# 脚本版本号
SCRIPT_VERSION="1.1.4"
# 日志函数
log() {
local LEVEL="$1"
local MESSAGE="$2"
local TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$TIMESTAMP] [$LEVEL] $MESSAGE"
}
# 显示帮助信息
usage() {
echo
echo "用法: $0 [选项]"
echo
echo "选项:"
echo " --onefile 将所有镜像导出到一个tar文件中"
echo " --compress 在导出每个镜像后立即压缩tar文件"
echo " --dir <目录> 指定导出文件存放目录,默认为 $HOME/docker_images_<时间戳>"
echo " -h, --help 显示此帮助信息"
echo
echo "版本: $SCRIPT_VERSION"
echo
}
# 全局变量
EXPORT_DIR=""
declare -A INCOMPLETE_FILES # 使用关联数组更可靠
SHOULD_EXIT=false
CURRENT_PID=""
# 清理函数
cleanup() {
log "INFO" "检测到中断信号,正在终止所有操作..."
SHOULD_EXIT=true
# 终止当前正在运行的进程
if [ -n "$CURRENT_PID" ] && ps -p "$CURRENT_PID" > /dev/null; then
kill -TERM "$CURRENT_PID" 2>/dev/null
wait "$CURRENT_PID" 2>/dev/null
fi
log "INFO" "正在清理未完成的文件..."
# 清理未完成的文件
for FILE in "${!INCOMPLETE_FILES[@]}"; do
if [[ -f "$FILE" ]]; then
log "INFO" "删除未完整保存的文件: ${FILE##*/}"
rm -f "$FILE"
fi
done
# 额外清理可能遗留的.incomplete文件
find "$EXPORT_DIR" -name "*.incomplete" -exec rm -f {} \;
find "$EXPORT_DIR" -name "*.tmp" -exec rm -f {} \;
log "INFO" "未完成的文件清理完成。"
# 提示用户是否删除导出目录
local TS=$(date +"%Y-%m-%d %H:%M:%S")
printf "[%s] [INFO] 是否删除保存的文件夹 %s? (y/N): " "$TS" "$EXPORT_DIR"
# 设置无缓冲输入并显示输入字符
saved_stty=$(stty -g)
stty raw -echo
REPLY=$(dd bs=1 count=1 2>/dev/null)
stty "$saved_stty"
# 显示用户输入并处理
case "$REPLY" in
[Yy]) echo -n "y" ;;
[Nn]) echo -n "n" ;;
*) echo -n "" ;;
esac
echo # 换行
if [[ $REPLY =~ ^[Yy]$ ]]; then
log "INFO" "删除导出目录: $EXPORT_DIR"
rm -rf "$EXPORT_DIR"
else
log "INFO" "保留导出目录: $EXPORT_DIR"
fi
exit 1
}
# 捕获退出信号
trap cleanup SIGINT SIGTERM SIGHUP
# 解析参数
while [[ $# -gt 0 ]]; do
case "$1" in
--onefile)
ONEFILE=true
;;
--compress)
COMPRESS=true
;;
--dir)
if [[ -n "$2" ]]; then
EXPORT_DIR="$2"
shift
else
log "ERROR" "--dir 参数需要一个目录路径"
usage
exit 1
fi
;;
-h|--help)
usage
exit 0
;;
*)
log "ERROR" "未知选项: $1"
usage
exit 1
;;
esac
shift
done
# 获取当前时间戳
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
# 默认导出目录
EXPORT_DIR="${EXPORT_DIR:-$HOME/docker_images_$TIMESTAMP}"
# 创建导出目录
mkdir -p "$EXPORT_DIR"
if [ $? -ne 0 ]; then
log "ERROR" "无法创建导出目录: $EXPORT_DIR"
exit 1
fi
log "INFO" "创建导出目录: $EXPORT_DIR"
# 获取所有镜像ID
IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}")
# 检查是否获取到镜像
if [ -z "$IMAGES" ]; then
log "ERROR" "未找到Docker镜像。"
exit 1
fi
# 导出镜像函数
export_images() {
if [ "$ONEFILE" = true ]; then
# 合并所有镜像到一个tar包
log "INFO" "正在将所有镜像导出到一个tar文件..."
TAR_FILE="$EXPORT_DIR/docker_images_$TIMESTAMP.tar"
INCOMPLETE_FILES["$TAR_FILE.incomplete"]=1
docker save $IMAGES > "$TAR_FILE.incomplete" &
CURRENT_PID=$!
wait $CURRENT_PID
CURRENT_PID=""
if [ $? -eq 0 ] && [ "$SHOULD_EXIT" != true ]; then
mv "$TAR_FILE.incomplete" "$TAR_FILE"
unset INCOMPLETE_FILES["$TAR_FILE.incomplete"]
else
return 1
fi
if [ "$COMPRESS" = true ] && [ "$SHOULD_EXIT" != true ]; then
log "INFO" "正在压缩tar文件: $TAR_FILE"
GZ_FILE="$TAR_FILE.gz"
INCOMPLETE_FILES["$GZ_FILE.incomplete"]=1
(gzip -c "$TAR_FILE" > "$GZ_FILE.incomplete" && \
mv "$GZ_FILE.incomplete" "$GZ_FILE") &
CURRENT_PID=$!
wait $CURRENT_PID
CURRENT_PID=""
if [ $? -eq 0 ] && [ "$SHOULD_EXIT" != true ]; then
unset INCOMPLETE_FILES["$GZ_FILE.incomplete"]
else
return 1
fi
fi
log "INFO" "所有镜像已导出到 $TAR_FILE"
else
# 分别导出每个镜像到单独的tar包
for IMAGE in $IMAGES; do
if [ "$SHOULD_EXIT" = true ]; then
break
fi
log "INFO" "正在导出镜像: $IMAGE"
SAFE_NAME=$(echo "$IMAGE" | tr '/' '_' | tr ':' '_')
IMAGE_FILE="$EXPORT_DIR/${SAFE_NAME}.tar"
INCOMPLETE_FILES["$IMAGE_FILE.incomplete"]=1
docker save "$IMAGE" > "$IMAGE_FILE.incomplete" &
CURRENT_PID=$!
wait $CURRENT_PID
CURRENT_PID=""
if [ $? -eq 0 ] && [ "$SHOULD_EXIT" != true ]; then
mv "$IMAGE_FILE.incomplete" "$IMAGE_FILE"
unset INCOMPLETE_FILES["$IMAGE_FILE.incomplete"]
else
continue
fi
if [ "$COMPRESS" = true ] && [ "$SHOULD_EXIT" != true ]; then
log "INFO" "正在压缩tar文件: ${IMAGE_FILE##*/}"
GZ_FILE="$IMAGE_FILE.gz"
INCOMPLETE_FILES["$GZ_FILE.incomplete"]=1
(gzip -c "$IMAGE_FILE" > "$GZ_FILE.incomplete" && \
mv "$GZ_FILE.incomplete" "$GZ_FILE") &
CURRENT_PID=$!
wait $CURRENT_PID
CURRENT_PID=""
if [ $? -eq 0 ] && [ "$SHOULD_EXIT" != true ]; then
unset INCOMPLETE_FILES["$GZ_FILE.incomplete"]
rm -f "$IMAGE_FILE" # 压缩成功后删除原tar文件
fi
fi
done
log "INFO" "镜像导出完成。"
fi
}
# 执行导出
export_images
if [ "$SHOULD_EXIT" = true ]; then
exit 1
fi
log "INFO" "导出完成。"