侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 129 篇文章
  • 累计收到 7 条评论

--exclude 写在目录后面,tar 当它是要打包的文件

2026-9-26 / 0 评论 / 11 阅读

--exclude 写在目录后面,tar 当它是要打包的文件

打包脚本出问题时,第一反应通常是通配符写错了。位置错了更常见:--exclude 站在源目录后面,bsdtar 把它当成一个待添加的成员,直接报 Cannot stat,同时排除规则一条都没生效。本机是 macOS 自带的 bsdtar 3.5.3,十二处容易站错位置的写法各跑一遍,输出如下。

tar 版本: bsdtar 3.5.3 - libarchive 3.7.4 zlib/1.2.12 liblzma/5.4.3 bz2lib/1.0.8
系统: Darwin

== 1. --exclude 写在源目录后面
tar: --exclude=*.log: Cannot stat: No such file or directory
tar: Error exit delayed from previous errors.
   包内成员: src/ src/run.log src/b.txt src/logs/ src/a.txt src/logs/run.log

== 2. --exclude 写在源目录前面
   包内成员: src/ src/b.txt src/logs/ src/a.txt

== 3. --exclude 想连子目录里的 log 一起排掉
   包内成员: src/ src/b.txt src/logs/ src/a.txt
   换 src/logs/* 之后: src/ src/run.log src/b.txt src/a.txt

== 4. 打包时给绝对路径
   tar: Removing leading '/' from member names
   包内前三个成员: var/folders/vz/zf2_sm250pgd4qb_p847p1240000gn/T/tmp.LqTiZwJksE/src/ var/folders/vz/zf2_sm250pgd4qb_p847p1240000gn/T/tmp.LqTiZwJksE/src/run.log var/folders/vz/zf2_sm250pgd4qb_p847p1240000gn/T/tmp.LqTiZwJksE/src/b.txt

== 5. -C 的位置(想只打 src 里那一层,不想要 src/ 这层目录)
   tar -cf out.tar -C src .  → ./ ./run.log ./b.txt ./logs/ ./a.txt
   tar -cf out.tar src -C src → src/ src/run.log src/b.txt src/logs/ src/a.txt src/logs/run.log

== 6. 带路径的打包 vs 不带
   进目录打 . → ./ ./run.log ./b.txt ./logs/ ./a.txt
   进目录只打两个文件 → a.txt b.txt

== 7. 选项里的 f 后面必须紧跟包名
   tar -czf out.tar.gz src → 成功, 成员数 6
   tar: out/bad.tar.gz: Cannot stat: No such file or directory
   tar: Error exit delayed from previous errors.

== 8. 往 .tar.gz 里追加文件
   tar: Cannot append to compressed archive.

== 9. 解包时的 -C 与 --strip-components
   直接解到 out/x1: a.txt b.txt logs run.log
   --strip-components=1 之后: a.txt b.txt logs

== 10. 解包时目标位置已有同名文件
   默认行为, a.txt 现在是: 订单 A
   加 -k 之后, a.txt 现在是: 旧内容

逐条对一遍

  1. --exclude 写在源目录后面,排除规则整条失效。命令是 tar -cf out.tar src --exclude='*.log',结果是报 tar: --exclude=*.log: Cannot stat: No such file or directory,然后包里照样有 src/run.log 与 src/logs/run.log。它一边报错一边把包打完了,只看退出码的脚本会以为成功。

  2. 把选项挪到源目录前面,同一份目录立刻排干净:tar -cf out.tar --exclude='*.log' src,成员只剩四个,两层里的 log 都不见了。这是全部十条里最值得先记住的一条。

  3. 排除子目录时,通配要写全路径。--exclude='src/logs/*' 的成员列表里连 src/logs/ 这个目录本身都没有了;换句话说它连空目录一起排掉。--exclude='*.log' 保留目录、只排文件。要不要那个空目录,决定选哪种写法。

  4. 绝对路径会被削掉开头的斜杠。tar -cf out.tar /var/folders/.../src 打印 Removing leading '/' from member names,包内成员变成长长的相对路径。想控制包里的层级结构,用 -C 而不是靠绝对路径。

  5. -C 站在成员后面等于没写。tar -cf out.tar -C src . 得到 ./ ./a.txt ./b.txt 这一层;写成 tar -cf out.tar src -C src 之后,包内成员跟不带 -C 时一模一样,连 src/logs/run.log 都还在里面。参数是按出现顺序生效的,位置一变语义就变。

  6. 包内结构由打包时那串路径决定,解包时改不了。同一个目录,进目录打 . 得到带 ./ 前缀的成员,进目录只打两个文件名就得到平铺的两层文件。这一步想清楚,后面就不用 --strip-components 打补丁。

  7. -f 后面紧跟的那一位就是包名,选项字母别乱堆。tar -czf out.tar.gz src 正常;写成 tar -cfz out/bad.tar.gz src,-f 吃掉了 z,out/bad.tar.gz 被当成成员去 stat,报同样的 Cannot stat。错误信息指向的是「文件不存在」,跟真实原因隔着好几层。

  8. 压缩包不能追加。tar -rf out.tar.gz 新文件 直接报 Cannot append to compressed archive.,因为 gzip 流无法就地插入。需要反复加文件的场景,先把 .tar 留着,最后再压一次。

  9. --strip-components 只在解包时起作用,而且按成员路径层级数。解 src/ 那一层用 --strip-components=1,配合 -C 指定目标目录,就不用先 cd 再解。

  10. 解包时目标位置已有同名文件,默认直接覆盖。实测 a.txt 从「旧内容」变成包里的内容;加 -k 之后旧文件保留,内容仍是「旧内容」。备份恢复脚本里这个默认值值得显式写出来,别指望它保守。

  11. 上面这些错误都走「延迟到最后」这条路。bsdtar 报 Error exit delayed from previous errors.,包已经写出来了,退出码非 0 但文件在磁盘上,看起来半成功。脚本里不能只看返回值,也顺手看一眼 stderr。

  12. 打完包最好回头对一次成员列表。tar -tf out.tar 打出来的是解包后的真实路径,比打包命令更能说明问题;把期望的文件名和排除掉的模式各 grep 一次,比对着命令参数推理可靠。

落到脚本上

把顺序固定成一种形状,上面十二条里有十条都不会遇到:先写选项,再写 -C,最后才是要打包的目录或文件。

# 选项在最前,-C 紧挨着目标目录的含义
tar -czf "$OUT" --exclude='*.log' --exclude='.DS_Store' -C "$SRC" .

若只让我留一条,就是「要排除的先写在前面」:它同时解决第 1、2 两条,而这两条是上面这趟输出里直接撞上的,也最容易被安静地放过。整份输出里没有一条报错会让 tar 提前退出,Cannot stat 只是「延迟到最后的错误」,包已经写出来了,所以这种错误必须自己去 grep 输出,不能只看返回值。

    🤞 分享