問題
用 mc 清理 MinIO 上累積的舊版本目錄,每個平台保留最新 5 個。腳本跑完後對帳,
應該保留 102 個目錄,實際只剩 84 個——18 個該保留的被刪掉了,而且全部都是最新版:
new-bi/201-ds-鼎盛-v1.25.11 ~ v1.25.15 ← 該平台 5 個全滅
new-bi/217-fc-飛馳-v1.14.26 ~ v1.14.29
new-bi/223-cx-暢享娛樂-v1.0.21 ~ v1.0.25 ← 該平台 5 個全滅
old-bi/219-ss-v2.0.10 ~ v2.0.12
new-bi/205-tw-貪玩-v1.11.10
刪除清單事前驗證過:與保留清單交集為 0,聯集正好等於原始總數。清單是對的, 執行結果卻不對。
原因
S3 沒有目錄,只有 key 前綴。mc rm --recursive --force alias/bucket/prefix
會刪掉所有以該字串開頭的 key,不會在斜線處停下來。
mc rm --recursive --force t/infinite/new-bi/201-ds-鼎盛-v1.25.1
這行的意圖是刪 v1.25.1,實際刪掉的是:
201-ds-鼎盛-v1.25.1/...
201-ds-鼎盛-v1.25.10/...
201-ds-鼎盛-v1.25.11/...
...
201-ds-鼎盛-v1.25.15/...
只要版本號是「舊的比較短、新的比較長」——v1.25.1 之於 v1.25.15——刪舊版
就會連新版一起帶走。這也解釋了為什麼受害的清一色是最新版:x.y.1 幾乎一定
是刪除清單裡的第一批,而 x.y.1N 才是要保留的。
解法
路徑補結尾斜線:
mc rm --recursive --force "t/infinite/new-bi/${d}/"
加了斜線後,前綴變成 201-ds-鼎盛-v1.25.1/,v1.25.10/ 開頭是
201-ds-鼎盛-v1.25.10/,不匹配,就不會被誤刪。
另一個同時踩到的坑:sort -V 排混合命名
同一批 tag 有兩種命名,一種帶中文名一種不帶:
217-fc-飛馳-v1.14.29 ← 舊格式
217-fc-v2.0.0 ← 新格式
sort -V 排整串字串時,比較的是 飛 與 v 這兩個 byte,v(0x76) 小於
中文字的 UTF-8 首 byte(0xE9),所以 217-fc-v2.0.0 被排到最前面,
當成最舊的版本刪掉,留下一年前的 v1.14.x。
修法是先抽出版本號再排,不要排整個名字:
v="${n##*-v}" # 217-fc-v2.0.0 -> 2.0.0
# 217-fc-飛馳-v1.14.29 -> 1.14.29
case "$v" in *[!0-9.]*) continue ;; esac # 格式不合就跳過不刪
printf '%s\t%s\n' "$v" "$n"
done | sort -V | cut -f2
關鍵洞察
清單正確不等於執行正確。 我驗了刪除清單與保留清單無交集、聯集等於總數, 就以為安全了——但驗的是「要刪哪些」,沒驗「每一次刪除實際命中什麼」。 批次刪除要對帳的是執行後的狀態,不是執行前的清單。
順帶:minio/mc image 是精簡的,有 bash/sort/head/tail/cut/tr/wc,
沒有 sed/awk/grep/jq/xargs。寫進 CI 的腳本只能靠 bash 內建
的 case 比對與參數展開。