Skip to content
White's Tech Notes
Go back

mc rm --recursive 是前綴比對,不是目錄比對

DevOpsEdit page

問題

用 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 比對與參數展開。



Previous Post
Claude Code plugin 跨機同步:設定會同步,binary 不會
Next Post
Redis ZREMRANGEBYRANK 不收縮 dict,判斷要不要瘦身一律看 ZCARD 不看 MEMORY USAGE