From 0d7608c867d4d06f3d07e50d5cec8c23a2b6a7e6 Mon Sep 17 00:00:00 2001 From: sora <2075279110@qq.com> Date: Wed, 5 Aug 2026 05:38:20 +0000 Subject: [PATCH] feat: add docker image cleanup script for benchmark images --- bash/cleanup_docker_images.sh | 205 ++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100755 bash/cleanup_docker_images.sh diff --git a/bash/cleanup_docker_images.sh b/bash/cleanup_docker_images.sh new file mode 100755 index 0000000..05810e4 --- /dev/null +++ b/bash/cleanup_docker_images.sh @@ -0,0 +1,205 @@ +#!/bin/bash +# ============================================================ +# 清理评测后遗留的 Docker 镜像 +# +# 用途: +# SWE-bench、Terminal-Bench 等 benchmark 会拉取大量 Docker 镜像, +# 评测完后很占磁盘。本脚本按命名空间批量删除这些镜像。 +# +# 用法: +# # 默认只预览,不会真的删除 +# bash bash/cleanup_docker_images.sh --preset swe +# +# # 确认删除 +# bash bash/cleanup_docker_images.sh --preset swe --yes +# +# # 删除 terminal-bench 镜像 +# bash bash/cleanup_docker_images.sh --preset terminal --yes +# +# # 删除所有常见 benchmark 镜像 +# bash bash/cleanup_docker_images.sh --preset all-bench --yes +# +# # 自定义匹配规则(支持多个,逗号分隔) +# bash bash/cleanup_docker_images.sh --pattern 'swebench/*,alexgshaw/*' --yes +# +# # 同时清理 dangling 镜像和未使用卷 +# bash bash/cleanup_docker_images.sh --preset all-bench --yes --prune +# ============================================================ + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# 预设的 benchmark 镜像命名空间 +PRESET_SWE=('swebench/*') +PRESET_TERMINAL=('alexgshaw/*') +PRESET_ALL_BENCH=() +PRESET_ALL_BENCH+=("${PRESET_SWE[@]}") +PRESET_ALL_BENCH+=("${PRESET_TERMINAL[@]}") + +DRY_RUN=1 +PRESET='' +PATTERNS='' +PRUNE=0 + +usage() { + sed -n '7,34p' "$0" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --preset) + PRESET="${2:-}" + shift 2 + ;; + --pattern) + PATTERNS="${2:-}" + shift 2 + ;; + --yes) + DRY_RUN=0 + shift + ;; + --prune) + PRUNE=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" + usage + exit 1 + ;; + esac +done + +# 解析出要删除的 pattern 列表 +PATTERN_LIST=() +if [[ -n "$PRESET" ]]; then + case "$PRESET" in + swe) + PATTERN_LIST+=("${PRESET_SWE[@]}") + ;; + terminal) + PATTERN_LIST+=("${PRESET_TERMINAL[@]}") + ;; + all-bench) + PATTERN_LIST+=("${PRESET_ALL_BENCH[@]}") + ;; + *) + echo "ERROR: unknown preset '$PRESET'. Available: swe, terminal, all-bench" + exit 1 + ;; + esac +fi + +if [[ -n "$PATTERNS" ]]; then + IFS=',' read -ra CUSTOM_PATTERNS <<< "$PATTERNS" + PATTERN_LIST+=("${CUSTOM_PATTERNS[@]}") +fi + +if [[ ${#PATTERN_LIST[@]} -eq 0 ]]; then + echo "ERROR: 请指定 --preset 或 --pattern" + usage + exit 1 +fi + +echo "============================================================" +echo "清理目标 pattern:" +for p in "${PATTERN_LIST[@]}"; do + echo " - $p" +done +echo "============================================================" + +# 收集所有匹配且没有被运行中容器使用的镜像 ID +MATCHED_IMAGES=() +RUNNING_IMAGES=$(docker ps --format '{{.Image}}' | sort -u) + +for pattern in "${PATTERN_LIST[@]}"; do + while IFS= read -r img; do + [[ -z "$img" ]] && continue + repo="${img%:*}" + # 检查是否有运行中容器在使用该镜像 + if echo "$RUNNING_IMAGES" | grep -qx "$img"; then + echo "SKIP (running container uses): $img" + continue + fi + # 避免重复 + if [[ ! " ${MATCHED_IMAGES[*]} " =~ " ${img} " ]]; then + MATCHED_IMAGES+=("$img") + fi + done < <(docker images --format '{{.Repository}}:{{.Tag}}' --filter=reference="$pattern") +done + +if [[ ${#MATCHED_IMAGES[@]} -eq 0 ]]; then + echo "没有匹配到可删除的镜像" + exit 0 +fi + +echo "" +echo "匹配到 ${#MATCHED_IMAGES[@]} 个镜像:" +for img in "${MATCHED_IMAGES[@]}"; do + size=$(docker images --format '{{.Size}}' "$img" | head -1) + echo " $img ($size)" +done + +# 计算标记总大小(注意共享层会被多次计算,仅作参考) +TOTAL_SIZE_GB=$(docker images --format '{{.Size}} {{.Repository}}:{{.Tag}}' \ + | awk -v img_list="${MATCHED_IMAGES[*]}" ' + BEGIN { + n = split(img_list, imgs, " "); + for (i=1; i<=n; i++) target[imgs[i]] = 1; + } + { + size = $1; + img = $2; + if (!(img in target)) next; + unit = substr(size, length(size)-1); + val = substr(size, 1, length(size)-2); + if (unit == "GB") sum += val * 1024 * 1024 * 1024; + else if (unit == "MB") sum += val * 1024 * 1024; + else if (unit == "kB") sum += val * 1024; + } + END { + printf "%.2f", sum / 1024 / 1024 / 1024; + }') + +echo "" +echo "标记总大小约: ${TOTAL_SIZE_GB} GB(含共享层,实际释放会小于该值)" + +if [[ "$DRY_RUN" -eq 1 ]]; then + echo "" + echo "当前是预览模式,不会删除。如需删除请加上 --yes" + exit 0 +fi + +echo "" +echo "开始删除..." +FAILED=0 +for img in "${MATCHED_IMAGES[@]}"; do + if docker rmi -f "$img" >/dev/null 2>&1; then + echo " DELETED $img" + else + echo " FAILED $img" + FAILED=1 + fi +done + +if [[ "$PRUNE" -eq 1 ]]; then + echo "" + echo "清理 dangling 镜像和未使用卷..." + docker image prune -f + docker volume prune -f +fi + +echo "" +echo "============================================================" +if [[ "$FAILED" -eq 0 ]]; then + echo "清理完成" +else + echo "部分镜像删除失败" +fi +echo "============================================================"