tar 的坑集中在三处:-f 吃哪个参数、解包会不会覆盖已有文件、链接怎么存。以下结论均在 WSL 实测。
打包:-f 吃掉的永远是紧跟它的词
mkdir -p /tmp/demo/src && echo hello > /tmp/demo/src/a.txt
tar cfz good.tar.gz /tmp/demo/src # f 的值是 good.tar.gz
tar -cfz bad.tar.gz /tmp/demo/src # f 的值是 z:落地的归档文件叫 z
手册口径:短选项可以合并,但带参数的选项必须留在字母串末尾,否则紧跟它的词会被当成它的参数 [1]。实测两条结果不同:第一条生成 good.tar.gz;第二条报 tar: bad.tar.gz: Cannot stat: No such file or directory、退出码 2,并在当前目录落了一个名为 z 的归档。把操作、开关、归档名分开写(tar -czf good.tar.gz 路径)读起来更不容易出错。
解包:默认会覆盖同名文件,要保护用 –keep-newer-files
tar -tvf good.tar.gz # 先看清单再决定解到哪
mkdir -p out && tar -xf good.tar.gz -C out # -C 指定解包目录
tar -xvf good.tar.gz -C out './a.txt' # 只取一个成员:名字照 -t 的输出写
tar -xvf good.tar.gz --keep-newer-files -C out './a.txt'
实测:默认解包会直接覆盖同名文件——磁盘上比归档更新的 a.txt 被归档内容替换;加 –keep-newer-files 后同一条命令打印 Current './a.txt' is newer or same age 并保留磁盘上的版本,退出码仍是 0 [3]。手册把 –overwrite 定义为”解包时覆盖已存在文件”,把 –keep-newer-files 定义为”不替换比归档副本更新的文件” [2],与实测一致。-C 指向不存在的目录会报 Cannot open: No such file or directory 并退出 2(实测)。读压缩归档不用加 -z:实测 tar -tf、tar -xf 对 .tar.gz 直接可用;但向压缩归档追加会报 Cannot update compressed archives、退出码 2(实测)[2]。
只取一个成员:通配符要显式开启,还要匹配整条成员名
tar -tf good.tar.gz | head -4 # 成员名带前缀,如 tmp/demo/src/a.txt
tar -xf good.tar.gz --wildcards -C out 'sub/*.txt'
tar -xf good.tar.gz --wildcards --no-anchored -C out '*.txt'
手册口径:通配符默认只用于排除,选成员时要显式加 –wildcards;成员名要与 –list 打印的一致 [1] [2]。实测吻合:不加 –wildcards 报 Pattern matching characters used in file names(退出 2);加上之后 sub/*.txt 仍匹配不到(默认从名首开始匹配),换 –no-anchored 才成功。引号不能省——tar -xf a.tar *.txt 会被 shell 先展开成磁盘上的文件,实测报 a.txt: Not found in archive。不想写整条前缀就用 –strip-components=1(实测内容直接落在目标目录根部)。
路径与链接:前导 / 被剥掉,符号链接和硬链接存成”链接”
tar -cf abs.tar /tmp/demo/src # 打印 Removing leading `/' 的警告
tar -cf abs2.tar -P /tmp/demo/src # -P 保留绝对路径
tar -C /tmp/demo/src -cf link.tar a.txt hard link # 硬链接和符号链接按链接存
tar -C /tmp/demo/src --hard-dereference -cf dl.tar a.txt hard
tar cf - -C /tmp/demo/src . | (mkdir -p dest && cd dest && tar xf -)
手册口径:tar 存档和还原时都会把文件名改成相对路径(去掉前导 /),要保留得显式加 –absolute-names;解包时对带前导 / 或内部 .. 的名字会特殊处理 [1] [3]。实测两条都退出 0,只有第一条打印那条警告。带 -P 的归档会按绝对路径落地,用之前先 tar -tvf 看清成员名。链接部分:实测默认把符号链接存成链接本身(-tvf 显示 link -> a.txt),硬链接存成指向首个成员的 link member(hard link to a.txt),解包后仍是同一 inode、link count 为 2;加 –dereference 或 –hard-dereference 才改成存目标内容 [2]。管道两头用 - 即读写标准输入输出,实测 diff -r 逐字节一致 [3]。非 root 解包时属主变成执行者——手册说 –same-owner 与 -p 默认只对 superuser 生效 [3];实测权限按归档记录的模式落地(640),属主变普通用户。
回滚与排错速查表
tar 没有撤销。稳妥顺序是:先 tar -tvf 核对成员名,再解到空目录,diff -r 核对后再动原目录;要覆盖已有文件前先备份,或加 –keep-newer-files。
| 现象 | 原因 | 处理 | |—|—|—| | Cannot stat: No such file or directory、归档名被吃掉 | -cfz 这类写法把下一个词当成 f 的参数 [1] | 把 f 留在字母串末尾 | | Removing leading '/' from member names | 默认把成员名改成相对路径 [1] | 想保留绝对路径用 -P,用前先看清成员名 | | Pattern matching characters used in file names | 选成员时通配符默认不开 [2] | 显式加 –wildcards,必要时配 –no-anchored | | Not found in archive | 成员名与 -t 输出不一致,或 glob 被 shell 先展开 | 照 -t 的输出写并加引号 | | 解包后同名文件被覆盖 | 默认覆盖 [2] | –keep-newer-files,或先备份 | | Cannot update compressed archives | 压缩归档不能追加或拼接 [3] | 重新打包,或先解压再改 |
常见问题与局限性
读压缩归档要不要加 -z——不需要,实测直接可用;创建时靠 -z 或 –auto-compress(按归档后缀选压缩程序)[1]。退出码口径:0 成功、1 文件有差异或被改动、2 致命错误 [1] [3];实测触发 2 的是 Cannot stat / Cannot open / Cannot update 三类,1 的场景(打包过程中文件被改动)在 WSL 里没能复现,仅文档核查。
状态:草稿待审。实测环境 WSL2 Ubuntu 24.04.4 LTS,GNU tar 1.35(tar –version),日期 2026-10-26。已实测:两种写法落地的归档名不同、默认覆盖与 –keep-newer-files、成员名口径与 –wildcards/–no-anchored、前导 / 剥离与 -P、符号链接与硬链接的存法与解后 link count、-C 目录不存在、压缩归档不能追加、管道 -f -、–strip-components、非 root 的属主与权限。仅文档核查尚未执行:退出码 1 的触发场景、BSD/macOS 上 bsdtar 与 busybox tar 的默认口径、跨文件系统(不支持硬链接的文件系统)与非 root 打包他人文件。
参考资料
- [1] GNU tar manual – Invoking GNU tar(GNU tar 手册)
- [2] tar(1) – Linux manual page (man7.org)(tar(1) 手册页)
- [3] tar(1) – tar – Debian bookworm man page(Debian tar(1))
文中主要命令已在 WSL Ubuntu(24.04)实测,个别条目(未装工具/安装类命令)仅核对官方文档,未实际执行。