RAR归档包可复现解压:结构探测、校验与规范落地
简介:这份名为 2018-03-20-boya.rar 的压缩包,面向准备入门或巩固 CSS 的前端学习者,围绕一个博客互动网站的简单样式实现展开,适合用来观察页面布局、配色、字体、按钮与链接状态等视觉细节,并理解样式如何服务于内容呈现。包内共 9 个文件,以 5 个 jpg、3 个 png 图片素材和 1 个 html 页面为主,压缩包约 181KB,体量轻便,便于快速解压查看。资源已有 1631 人浏览学习,说明其作为小型案例具备一定参考价值。读者可结合页面结构与 banner、产品图、logo、装饰图标等素材,梳理博客页面的模块划分和视觉层级,并在此基础上练习选择器、盒模型、背景与图片处理、链接及悬停状态等常见 CSS 知识点,作为课程作业或个人练习的对照素材较为合适。对刚接触前端的读者而言,可把它当作拆解页面构成、临摹样式和建立调试思路的小型样本,也便于在原有页面上改造出个人博客风格。
1. 一个叫 2018-03-20-boya.rar 的包,先别急着双击
2018-03-20-boya.rar这种命名一眼能读出三件事:日期当版本号,boya 是模块或项目代号,rar 只是分发容器而不是存储格式。它多半不是给人临时看的,是给三个月后的自己、或者给接手的人看的。真正麻烦的地方从来不是把它解开,而是解开之后你没法证明自己拿到的是原样——少一列、错一个编码、丢一批时间戳,后面所有分析都建在流沙上。
这篇讲的是把这类陌生归档包当成一次可复现的数据接收:先只读探测、再规范落地、再哈希校验、最后把命名和检查清单固定下来。适合手里正躺着一堆带日期的压缩包、又说不清里面到底是什么的人。新手能照着命令走完一遍,熟手能在这里看到分卷、加密头、路径穿越这些边界条件该怎么处理。
2. 拆开之前:RAR 容器结构与工具选型
动手解压之前多花十分钟看结构,是为了避免后面花两小时排错。RAR 不是 ZIP 那种“目录在尾部”的布局,它的头部块是流式写在文件开头的,一旦前端几个字节坏掉,整个包可能连文件名都读不出来。搞清楚这一点,你就明白为什么有些包“看着 200MB,却一个文件都列不出”。
2.1 RAR4 与 RAR5 的头部差异,决定了老工具能不能解开
RAR 文件的前 8 个字节是签名。RAR4 的签名是52 61 72 21 1A 07 00,RAR5 改成了52 61 72 21 1A 07 01 00,只差最后一位,但这一位决定了你手上那套老旧解压工具认不认它。签名之后是一串数据块:主头块、若干文件头块、每个文件的压缩数据块,最后是结束块。每个块自带头部 CRC,所以“包没坏但头坏了”这种情况是能被发现的,前提是你用的是会做校验的工具。
RAR5 相对 RAR4 的主要变化在于:长度字段改成变长整数编码、文件尺寸支持到 64 位、字典上限更大、恢复记录以百分比形式记录(创建时用-rr5p这类参数)。这些变化带来的实际后果是,某些年份较早的解压器打开 RAR5 包时会直接报Cannot open ... as rar archive或者把它当成损坏文件。
判断方法很简单,不需要装任何东西:
# 只看前 8 字节,用十六进制确认签名 xxd -l 8 "2018-03-20-boya.rar" # 如果输出结尾是 01 00,就是 RAR5;是 07 00,就是 RAR4 # 没有 xxd 的机器用 od od -A x -t x1z -v -N 8 "2018-03-20-boya.rar"-l 8限定只读 8 字节,-N 8是 od 的等价写法。这一步的意义在于:如果后面unrar l报“不是归档文件”,你能立刻分清是包真坏了,还是工具太老不认 RAR5,而不是一头扎进去修文件。踩过这个坑的人都知道,对着一个完好的 RAR5 包折腾半天修复工具,是纯浪费时间。
2.2 unrar、7z、python rarfile 三条路线怎么选
RAR 的压缩算法是私有的,只有官方解码器实现是完整的。这决定了工具选型不是“哪个顺手用哪个”,而是“谁能保证解出来的字节和压进去的一致”。下面这张表是我自己在不同场景下的取舍:
| 工具 | 读 RAR4 | 读 RAR5 | 写 RAR | 分卷支持 | 修复恢复记录 | 适用场景 |
|---|---|---|---|---|---|---|
| unrar(命令行) | 支持 | 需较新版本 | 不支持 | 完整 | 支持 | 生产环境落地、批量脚本 |
| 7-Zip / p7zip | 支持 | 视版本而定 | 不支持 | 支持 | 不支持 | 桌面临时查看、快速确认内容 |
| bsdtar(libarchive) | 支持 | 部分版本支持 | 不支持 | 有限 | 不支持 | 容器镜像里不想多装包 |
| python rarfile | 依赖后端 | 依赖后端 | 不支持 | 依赖后端 | 不支持 | 需要嵌进数据流水线、批量清点 |
结论很直接:真正落地解压,用 unrar。python rarfile本身不含解码器,它只是个封装层,底层还是去调unrar或bsdtar,好处是能把“列清单、算哈希、写元数据”串成一段脚本,坏处是后端没装好时它报的错非常不直观——这一点在第 5 章会展开。
需要提醒的是,rarfile依赖环境变量UNRAR_TOOL或系统 PATH 里能找到可执行文件。容器里跑的时候,不要假设镜像自带,构建阶段显式装一次,比运行时排查RarCannotExec省事得多。
2.3 只读探测:不解压也能拿到文件清单和时间戳
探测阶段的核心原则是:不写盘、不覆盖、不改动源文件。unrar 提供了好几档详细程度的列表命令,用途完全不同,混用会给自己添麻烦。
# 只列文件名,一行一个,最适合管道和 wc -l 统计 unrar lb "2018-03-20-boya.rar" # 表格式列表:文件名、大小、日期、CRC,人看最舒服 unrar l "2018-03-20-boya.rar" # 技术列表:多出压缩方法、CRC32、宿主 OS、属性位 unrar lt "2018-03-20-boya.rar" # 详细列表:把每个文件的完整头部信息摊开 unrar v "2018-03-20-boya.rar"lb的b是 bare,输出最干净,适合| wc -l数文件个数,或者| grep -i '\.csv$'快速判断里面有没有你要的那批数据。lt才是排查阶段真正要看的:Host OS字段告诉你这个包是在 Windows 还是 Unix 上打的,直接决定了中文文件名会不会乱码、路径分隔符是反斜杠还是斜杠、有没有可能带符号链接。Method字段能看到每个文件的压缩方式,m0表示只存储不压缩,这种文件在解压时几乎不耗 CPU,但也意味着包体积大。
一个很实用的组合是先跑总量估算,再决定要不要整包落地:
# 统计文件数与解压后总大小,避免磁盘被撑爆 unrar lt "2018-03-20-boya.rar" | awk ' /^ Name:/ {name=$0} /^ Size:/ {size=$3; total+=size; count++} END {printf "files=%d, unpacked=%.2f MB\n", count, total/1048576}'awk里按字段前缀匹配unrar lt的输出行,$3取的是字节数。这段脚本不是万能的,不同版本的lt输出格式略有差异,跑一次看看字段对不对齐再改。但这个思路值得保留:先算账,再动手。遇到过解压到 90% 磁盘满、留下半套文件的情况,清理起来比重来一遍还麻烦。
3. 在 Linux 与 Windows 上把包完整落地
探测确认没问题,才进入落地阶段。这一步的目标不是“把文件弄出来”,而是“把文件弄出来,并且能说清楚它们原来是什么样”。目录策略要在解压前定好,不要解到当前工作目录——2018-03-20-boya.rar里如果有几十个顶层文件,直接解到~/Downloads里,后面想收都收不回来。
3.1 用 unrar 做一次“只解压不改动”的标准化落地
我一般的做法是先建一个以包名命名的接收目录,然后把解压参数全部显式写出来,不依赖任何默认值:
# 建立独立的接收目录,包名即目录名,便于日后回溯来源 mkdir -p /data/intake/2018-03-20-boya # 关键参数逐个说明: # x 保留归档内的完整目录结构解压(区别于 e,e 会把所有文件摊平) # -o- 遇到同名文件一律不覆盖,直接跳过(-o+ 是总是覆盖,-o 是询问) # -p- 不查询密码,遇到加密内容直接报错退出,避免脚本卡在交互提示 # -idq 安静模式,只输出错误信息,方便写进日志 # -- 结束选项解析,后面的参数一律当文件/目录名处理 unrar x -o- -p- -idq -- "2018-03-20-boya.rar" /data/intake/2018-03-20-boya/--这个参数很容易被忽略,但它能救命。如果包里有文件名以-开头,不加--的话 unrar 会把它当成选项去解析,轻则报错重则行为异常。我见过.rar里混进一个叫-old的目录,导致整个解压命令直接失败。
-o-是第二个必须显式的参数。不写它,unrar 遇到同名文件会交互式询问,脚本环境里就会一直挂着;写了-o+则更危险,第二次解压同一个包时会把第一次的结果覆盖掉,而你可能还没校验过第一次解出来的是不是完整的。
如果希望解压后自动多一层目录(比如批量处理多个包时统一结构),加-ad:
# -ad 会把归档文件名(不含扩展名)作为子目录追加到目标路径后 unrar x -ad -o- -p- -idq -- "2018-03-20-boya.rar" /data/intake/ # 实际落地路径变成 /data/intake/2018-03-20-boya/批量场景下这个参数很省事,缺点是目录名会带上日期前缀,如果包名本身不合规范,目录也跟着乱。所以命名规范这件事要前置到打包环节,第 6 章会给出一个可直接抄的模板。
Windows 下命令行同理,只是路径分隔符和可执行文件名不同:
rem Windows 下注意反斜杠与引号,目标目录最好先在资源管理器里建好 "C:\tools\unrar\UnRAR.exe" x -o- -p- -idq -- "D:\incoming\2018-03-20-boya.rar" "D:\intake\2018-03-20-boya\"Windows 版本的 unrar 对长路径支持不好,路径超过 260 字符会静默截断失败。稳妥办法是把接收目录放在盘符根下一级,比如D:\intake\,不要放在桌面上那种深层嵌套的路径里。
3.2 用 Python 批量清点并生成可核对的清单
命令行走完一遍之后,我习惯再用脚本生成一份机器可读的清单,作为后续校验的基准。rarfile的价值在这里体现:它能把归档内每个文件的元数据一次性读出来,不落地就能比对。
import rarfile import json from pathlib import Path ARCHIVE = Path("/data/incoming/2018-03-20-boya.rar") OUT = Path("/data/intake/2018-03-20-boya") MANIFEST = OUT / "_manifest_from_archive.json" # rarfile 本身不含解码器,需要系统里能找到 unrar 或 bsdtar # 也可以显式指定:rarfile.UNRAR_TOOL = "/usr/local/bin/unrar" rf = rarfile.RarFile(ARCHIVE, mode="r") rows = [] with rf: # testrar 会逐个文件校验 CRC,返回第一个失败的文件名,全部通过返回 None bad = rf.testrar() if bad: raise SystemExit(f"归档校验失败,首个异常文件: {bad}") for info in rf.infolist(): rows.append({ "name": info.filename, # 归档内相对路径,可能是反斜杠 "is_dir": info.is_dir(), # 目录条目标记 "size": info.file_size, # 解压后字节数 "packed": info.compress_size, # 压缩后字节数 "crc": f"{info.CRC:08x}", # 归档记录的 CRC32,用于比对 "mtime": "%04d-%02d-%02d %02d:%02d:%02d" % info.date_time, "host_os": info.host_os, # 打包时的宿主系统,判断编码问题用 "compress_type": info.compress_type, }) # 按文件名排序后再写盘,保证两次运行结果可 diff rows.sort(key=lambda r: r["name"]) MANIFEST.write_text( json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8", ) print(f"清单写入 {MANIFEST},条目数 {len(rows)}")几个参数值得展开。mode="r"是只读打开,不涉及任何写操作,安全。testrar()在解压前就把整包 CRC 过一遍,比解压完再发现某个文件坏了要从头处理要划算得多——尤其当包里有一个 2GB 的大文件时。info.host_os这个字段很多人不用,但它在排查乱码时是决定性的证据:如果它是win32而解出来的文件名是乱码,那基本可以锁定是打包端用了本地代码页而不是 UTF-8。
清单文件名用_manifest_from_archive.json而不是manifest.json,是为了和解压后的文件区分开。清单本身要落在接收目录里,将来任何人拿到这个目录,都能看出哪些文件来自归档、哪些是后来生成的。
3.3 时间戳、权限与软链接的还原边界
这三样是跨平台解压最容易悄悄丢失的东西,而且丢了之后不会有任何报错。
时间戳方面,unrar x默认会恢复文件的修改时间(mtime),但创建时间(ctime)和解压时间(atime)是无法从 RAR 里还原的,因为格式本身就没存。如果你的下游流程依赖 mtime 做增量判断,这一点必须提前知道:解压后的 mtime 是打包那一刻的时间,不是文件被创建的时间,两者在2018-03-20-boya.rar这种场景下可能相差好几个月。
权限方面,Unix 打包时文件的可执行位是存在头部属性里的,unrar x会尝试还原。但如果以普通用户身份解压,属主会被强制改成当前用户;想保留原始属主必须用-ow并以 root 运行。跨平台场景下更麻烦:Windows 上打的包没有 Unix 权限位,解到 Linux 后所有文件权限会按 umask 生成,脚本文件可能丢掉可执行位。稳妥做法是解压后统一跑一次修正:
# 只对目录下的 .sh 补可执行位,不要无差别 chmod -R 777 find /data/intake/2018-03-20-boya -type f -name '*.sh' -exec chmod +x {} + # 核对解压后的 mtime 是否在归档记录的合理区间内 find /data/intake/2018-03-20-boya -type f -newermt "2018-03-21" -printf '%TY-%Tm-%Td %p\n'第二条命令的意思是找出 mtime 晚于 2018-03-21 的文件。如果包是在 3 月 20 日打的,按理说不会有文件比这更晚——出现了就说明这批文件不是从归档里还原的,可能是之前解压残留,或者是打包后被修改过。这个检查很土,但对判断“目录是不是干净的”非常有效。
软链接方面,RAR 是支持存符号链接的(打包时用-ol),但unrar x在普通用户或某些文件系统上会把链接展开成普通文件副本。如果2018-03-20-boya.rar里有指向外部路径的软链接,展开后会变成实体文件,体积可能翻几倍。排查办法是解压前后对比lt输出里的Attr字段,看有没有l(符号链接)标记。
4. 解压完才是开始:清点、去重与完整性校验
解压出来一堆文件,这件事本身没有任何价值。价值在于你能回答三个问题:一共多少个、每个是什么、有没有重复或缺失。这一步不做,后面无论做分析还是做迁移,都是在一堆来历不明的字节上盖楼。
4.1 用哈希清单给每个文件发一张身份证
CRC32 只能证明“归档内的数据和 CRC 记录对得上”,它挡不住解压后被人为改动。要证明“我拿到的就是归档里的那份”,得算一遍强哈希再和归档记录做交叉验证。
# -print0 与 -z 配合,解决文件名含空格、换行的问题 # sort -z 保证两次运行的输出顺序一致,便于 diff find /data/intake/2018-03-20-boya -type f -print0 \ | sort -z \ | xargs -0 sha256sum \ > /data/intake/2018-03-20-boya/_manifest_sha256.txt-print0用 NUL 而不是换行分隔文件名,xargs -0对应地按 NUL 解析。这两个参数必须成对出现,只写一个会得到意料之外的结果。sort -z同理。少了它们,只要目录里有一个带空格的文件名,sha256sum就会把它拆成两个参数,报No such file。
清单生成之后,把第 3 章里 Python 导出的 JSON 也读进来,做一次数量与大小的双向核对:
import json, hashlib from pathlib import Path root = Path("/data/intake/2018-03-20-boya") rows = json.loads((root / "_manifest_from_archive.json").read_text(encoding="utf-8")) # 只比对普通文件,目录条目跳过 expect = {r["name"].replace("\\", "/"): r for r in rows if not r["is_dir"]} actual = {} for p in root.rglob("*"): if p.is_file() and not p.name.startswith("_manifest"): rel = p.relative_to(root).as_posix() actual[rel] = p.stat().st_size missing = sorted(set(expect) - set(actual)) extra = sorted(set(actual) - set(expect)) mismatch = [k for k in expect if k in actual and expect[k]["size"] != actual[k]] print(f"归档条目 {len(expect)},落地文件 {len(actual)}") print(f"缺失 {len(missing)},多余 {len(extra)},大小不符 {len(mismatch)}") for k in (missing + extra + mismatch)[:10]: print(" ->", k)这段逻辑的关键是把归档内的路径统一成 POSIX 风格(反斜杠换成正斜杠),否则 Windows 打的包在 Linux 上比对会全量报“缺失”。rglob("*")会递归到子目录,但排除了自己生成的清单文件,避免自指。
4.2 重复文件与空目录的识别
2018-03-20-boya.rar这种带日期的手工包,重复文件几乎是必然的。同名不同路径、同内容不同名、改过一个字节的“近似重复”,三种情况处理方式完全不同。
先用大小做第一层筛选,成本最低:
# 按大小分组,只打印出现次数大于 1 的组 find /data/intake/2018-03-20-boya -type f -printf '%s\t%p\n' \ | sort -n \ | awk -F'\t' '{c[$1]++; p[$1]=p[$1] "\n " $2} END {for (s in c) if (c[s] > 1) print s " bytes x" c[s] p[s]}'大小相同的文件不一定内容相同,但大小不同的文件一定不重复。这一步能把候选集从几千个压到几十个,再对这几十个算哈希确认。直接对全量算 SHA256 在几百 GB 的包上是很痛的,先过滤再算,能省掉大量 IO。
空目录也要单独列。RAR 里目录是有独立条目的,解压时空目录会被创建,但很多下游工具(同步脚本、打包脚本)会忽略空目录,导致结构在链条中途悄悄改变:
# 找出所有空目录并保留路径,不要直接 -delete find /data/intake/2018-03-20-boya -type d -empty -print这些空目录里有一部分是打包时就有意留的占位(比如output/目录约定必须存在),删掉会破坏下游流程。所以只打印不删除,由人来判断。
4.3 CRC 校验失败时的三级排查
unrar x中途报CRC failed in ...是最常见的翻车现场。别急着换工具重试,先按层级缩小范围。
第一级:只测不写。unrar t "2018-03-20-boya.rar"只做解压和 CRC 校验,不落盘。如果t能过而x报错,问题在磁盘或权限,不在包本身——常见原因是目标分区满了,或者文件系统不支持某些字符。
第二级:定位到单文件。unrar t的输出会指明是哪个条目失败。把那个文件单独抽出来重试:
# 只解压指定文件到临时目录,缩小问题范围 unrar x -o+ -p- -- "2018-03-20-boya.rar" "data/raw/sample_042.csv" /tmp/probe/如果单文件也失败,基本可以确认是压缩数据块损坏,而不是路径问题。
第三级:看有没有恢复记录。打包时如果加了-rr,RAR 尾部会存一份冗余数据,可以修复一定比例的损坏。先查再修:
# 查看归档信息里是否记录了恢复信息 unrar l "2018-03-20-boya.rar" | head -20 # 有恢复记录时尝试修复,输出会生成一个 rebuilt 文件 unrar r "2018-03-20-boya.rar"修复的代价是时间和磁盘空间,而且只能修到恢复记录覆盖的比例为止。没有恢复记录的包,损坏就是损坏,这时候唯一的正确做法是从原始来源重新拿一份,而不是在网上找各种“修复工具”——那些工具对私有格式基本无效,还可能引入新的风险。
5. 接手陌生 rar 包最容易翻车的 5 个地方
下面这五条都是我实际碰到过的,按“现象 → 原因 → 解决”写清楚。每一条单独看都不复杂,但它们共同的特点是不报错、只是结果不对,等到下游发现的时候,排查成本已经翻了好几倍。
5.1 中文文件名为什么不启用了中文菜单,打开就成了问号
现象:解压出来的文件名全是????.txt或者脜脣脝路.txt这类乱码,用unrar l列表时就已经是乱码。
原因:打包端在 Windows 上用了本地代码页(GBK 一类),而 RAR4 格式不强制记录编码,解码端只能猜。RAR5 对 UTF-8 有更好的标记支持,但老包改不了。unrar在 Linux 上默认按 UTF-8 解释,遇到 GBK 字节流就乱。
解决:先确认lt输出里的Host OS字段,如果是Win32且文件名乱码,基本可以锁定编码问题。处理办法是解压后批量重命名,而不是反复换解压参数:
# 仅演示思路:把疑似 GBK 的文件名按 GBK->UTF-8 转换后重命名 # 务必先在副本目录上试跑,确认无误再对正式目录执行 find /data/intake/2018-03-20-boya -depth -name '*' -print0 \ | while IFS= read -r -d '' f; do dir=$(dirname "$f"); base=$(basename "$f") new=$(printf '%s' "$base" | iconv -f GBK -t UTF-8 2>/dev/null) || continue [ "$new" != "$base" ] && mv -- "$f" "$dir/$new" done-depth保证先处理子目录内容再处理目录本身,否则改名后路径失效。iconv失败时continue跳过,避免把本来就正常的 UTF-8 文件名改坏。这段脚本风险不小,一定要先cp -a一份副本试跑。
5.2 分卷包少了一卷,解到一半才报错
现象:unrar x前几卷正常,到中间突然报Cannot find volume,或者解出来的文件大小明显偏小。
原因:分卷命名通常形如xxx.part1.rar、xxx.part2.rar,拷贝过程中漏了一卷,或者卷号不连续(.r00、.r01这种老式命名更容易漏)。unrar只有真正读到缺失卷时才会报错,前面几卷看起来一切正常。
解决:解压前先把同目录下所有分卷列出来,核对卷号的连续性:
# 列出同名前缀的所有分卷,按名称排序后检查是否有断号 ls -1 2018-03-20-boya.part*.rar 2>/dev/null | sort -V # 直接用 unrar 的列表命令验证第一卷能否串起整个卷链 unrar l "2018-03-20-boya.part1.rar" | tail -5sort -V做版本号排序,避免part10排在part2前面造成误判。列表命令能读出末卷信息,说明卷链是完整的;读不全就是缺卷,别硬解。
5.3 加了密码还勾了“加密文件名”,连列表都拿不到
现象:unrar l直接要求输密码,输错就什么都不返回,甚至看不到有几个文件。
原因:RAR 支持“加密文件内容”和“加密文件头”两个选项。前者只加密数据,文件名仍然可读;后者把头也加密了,不输密码连目录结构都拿不到。很多老包默认勾了后者。
解决:脚本里永远用-p-,让它在没密码时立刻失败而不是卡住:
# 有密码时显式传入,注意密码会出现在进程列表里,敏感环境改用环境变量或配置文件 unrar l -p"$RAR_PASS" -- "2018-03-20-boya.rar" # 无密码时用 -p- 快速失败,避免交互挂起 unrar l -p- -- "2018-03-20-boya.rar" || echo "需要密码或已损坏"密码写进命令行会被ps看到,生产环境建议用unrar支持的环境变量方式,或者干脆把密码写进权限受控的配置文件再读取。忘了密码的包基本没救,不要去试那些号称能破解的工具。
5.4 包里躺着../../开头的路径,解压直接写到目录外面
现象:解压后目标目录之外多出了一批文件,或者某些系统文件被覆盖。
原因:恶意构造或打包脚本出错,都会产生带路径穿越的条目(../../etc/xxx)。部分老版本解压工具不做路径规范化,直接把相对路径拼接上去。
解决:解压前先扫一遍路径,把可疑条目拦下来:
import rarfile rf = rarfile.RarFile("/data/incoming/2018-03-20-boya.rar") suspect = [] for info in rf.infolist(): name = info.filename.replace("\\", "/") # 绝对路径、盘符、向上的相对路径,三类都要拦 if name.startswith("/") or ".." in name.split("/") or (len(name) > 1 and name[1] == ":"): suspect.append(name) if info.is_symlink() and ".." in (info.link_target or ""): suspect.append(f"{name} -> {info.link_target}") print("可疑条目:", suspect[:20], "共", len(suspect))这段检查要放在任何解压动作之前。发现可疑条目,正确做法是拒绝整包,而不是“只解压看起来正常的那部分”——包的来源本身就可疑了,里面还有什么都不奇怪。
5.5 第二次解压把第一次的结果覆盖了
现象:解压完之后发现某些文件的时间戳变了、大小和清单对不上,但没有任何报错。
原因:用了-o+,或者忘写-o-导致工具按默认策略覆盖。同名文件被新版替换,旧的直接消失,而你可能已经基于旧版做了一轮处理。
解决:把“永不复用目录”写进流程。每次解压都落到带时间戳的新目录,旧目录保留到校验通过为止:
# 用解压时刻生成唯一目录,天然避免覆盖 STAMP=$(date +%Y%m%dT%H%M%S) DEST="/data/intake/2018-03-20-boya/run_${STAMP}" mkdir -p "$DEST" unrar x -o- -p- -idq -- "2018-03-20-boya.rar" "$DEST/" echo "本次落地目录: $DEST"date +%Y%m%dT%H%M%S精确到秒,同名冲突概率极低。目录多了之后定期清理run_*,但一定要在校验清单归档之后再删,否则回滚点就没了。
6. 把一次性解压固化成归档规范:命名、清单与回滚点
处理完2018-03-20-boya.rar之后,真正有价值的不是“我把它解开了”,而是“下一个包来的时候我不需要重新想一遍”。我现在的习惯是每接手一批归档,就把接收过程的检查项固化成一张表,贴在接收目录的_intake_checklist.md里,谁接手谁往下勾:
| 检查项 | 通过标准 | 命令或依据 |
|---|---|---|
| 签名与格式 | 前 8 字节匹配 RAR4 或 RAR5 签名 | xxd -l 8 |
| 归档完整性 | testrar()返回空 | unrar t |
| 卷链完整 | 无断号,末卷可读 | ls part*.rar | sort -V |
| 路径安全 | 无绝对路径、无..、无外指软链接 | Python 遍历infolist() |
| 数量一致 | 落地文件数 == 归档条目数(去目录) | JSON 清单比对 |
| 内容一致 | 每个文件 SHA256 可复算 | _manifest_sha256.txt |
| 时间戳合理 | 无晚于打包日期的 mtime | find -newermt |
| 编码确认 | 文件名可读,无替换字符 | lt的Host OS字段 |
命名这块我吃过亏,现在只用一个模板:YYYY-MM-DD_模块_内容摘要_vN.rar。2018-03-20-boya.rar缺了摘要和版本,半年后完全想不起来里面是日志、是配置还是原始数据,只能解开看。加上摘要之后,.rar的后缀反而次要了,因为名字本身已经说明了内容。
还有一个我坚持保留的动作:每次解压完,在接收目录里写一个_source.txt,只记三行——原始包路径、解压时间、执行人。听起来很啰嗦,但它是唯一的后悔药。有过一次,某个包解压后两周才发现少了一批文件,靠这三行才确认当时是从哪个共享路径拿的,重新取了一份对比,十分钟定位到是拷贝时漏了分卷。没有这三行,那天可能要在几个 TB 的存储里翻一整天。
校验脚本也别写得太花。我见过一个把校验、去重、转码、上传全串在一起的脚本,任何一步挂了都得从头跑,而且中间状态没人说得清。现在我的做法是每步一个脚本、每步落一个中间产物(JSON 清单、SHA256 清单、异常列表),坏了只重跑那一步。
希望帮到你。
本文还有配套的精品资源,点击获取