Linux 基础体系 · 第 49/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

Linux 归档与压缩:tar、gzip、xz、zstd、校验和和大文件

一、先区分归档、压缩和校验

**归档(archive)**是把多个文件及其目录结构、元数据组织成一个数据流或文件;**压缩(compression)**是利用数据中的重复模式减少数据量;**校验和(checksum)**是根据数据计算摘要,用于发现传输或存储过程中的变化。

tar 主要负责归档,gzipxzzstd 主要负责压缩。常见文件名:

backup.tar              仅归档
backup.tar.gz           tar 归档后使用 gzip 压缩
backup.tar.xz           tar 归档后使用 xz 压缩
backup.tar.zst          tar 归档后使用 zstd 压缩

它们的处理顺序通常是:

多个文件
   │
   ▼
tar 归档
   │
   ▼
gzip / xz / zstd 压缩
   │
   ▼
一个压缩归档文件

恢复时顺序相反:

压缩归档文件
   │
   ▼
解压缩
   │
   ▼
tar 解析归档
   │
   ▼
恢复目录、文件和元数据

tar 本身通常不压缩。把 backup.tar.gz 称为“tar 压缩包”在日常交流中可以理解,但更准确的描述是“使用 gzip 压缩的 tar 归档”。

压缩率的定义

设原始数据大小为 SS,压缩后大小为 CC,压缩率可以表示为:

R=CSR = \frac{C}{S}

例如,原始数据为 100 MiB,压缩后为 40 MiB,则:

R=40100=0.4R = \frac{40}{100} = 0.4

也可以使用节省比例:

E=1CSE = 1 - \frac{C}{S}

此时节省比例为 60%。

压缩效果取决于数据本身。文本、JSON、日志通常包含大量重复模式;JPEG、MP4、ZIP 等已经压缩过的数据通常几乎不能继续压缩,甚至可能因为格式开销而略微变大。


二、tar 如何保存文件

1. tar 是顺序数据流

一个 tar 归档可以抽象为:

文件头 + 文件内容
文件头 + 文件内容
文件头 + 文件内容
结束标记

文件头中通常包含:

  • 文件名;
  • 文件类型;
  • 文件大小;
  • 权限;
  • 用户 ID 和组 ID;
  • 修改时间;
  • 符号链接目标;
  • 硬链接关系;
  • 扩展属性、ACL 等扩展信息,取决于参数和实现。

tar 按顺序写入文件,因此创建归档时不需要为每个文件建立独立压缩流。多个文件之间的重复内容可以被压缩器共同利用,这通常比“先分别压缩每个文件,再打包”更有效。

例如,下面两个方案的压缩上下文不同:

方案 A:tar 多个日志文件,再整体 gzip
a.log + b.log + c.log -> tar -> gzip
方案 B:每个日志文件分别 gzip,再 tar
a.log -> a.gz
b.log -> b.gz
c.log -> c.gz
a.gz + b.gz + c.gz -> tar

方案 A 允许压缩器跨文件发现重复字符串,通常更有利于压缩;方案 B 则允许单独解压某个文件,但会损失跨文件压缩机会。

2. 基本创建、查看和提取

准备测试目录:

mkdir -p demo/etc demo/logs
printf 'server=example\n' > demo/etc/app.conf
printf 'same line\nsame line\n' > demo/logs/app.log
ln demo/etc/app.conf demo/etc/app.conf.hardlink
ln -s ../etc/app.conf demo/logs/app.conf.link

创建未压缩归档:

tar -cf demo.tar demo/

参数含义:

  • -c:create,创建归档;
  • -f demo.tar:指定归档文件;
  • demo/:要归档的路径。

查看归档目录,不释放文件:

tar -tf demo.tar

典型输出:

demo/
demo/etc/
demo/etc/app.conf
demo/etc/app.conf.hardlink
demo/logs/
demo/logs/app.log
demo/logs/app.conf.link

提取到当前目录:

mkdir restore
tar -xf demo.tar -C restore

这里的 -x 是 extract,-C restore 表示在提取前切换到 restore 目录。归档中的路径仍会保留 demo/ 前缀,因此结果是:

restore/demo/etc/app.conf

只提取某个路径:

tar -xf demo.tar -C restore demo/etc/app.conf

列出单个文件内容而不落盘:

tar -xOf demo.tar demo/etc/app.conf

-O 将文件内容写到标准输出,适合查看配置或把归档中的内容交给其他命令。不要对大型二进制文件随意使用,因为内容会直接写入终端或管道。

3. 归档根目录和路径名

下面两条命令保存的路径不同:

tar -cf absolute.tar /etc/hosts
tar -cf relative.tar -C /etc hosts

现代 GNU tar 通常会去除归档中的前导 /,并给出类似“Removing leading /'”的提示,以避免提取时默认覆盖绝对路径。第二条命令明确保存为 hosts`,通常更适合可移植归档。

更常用的写法是从目标目录的父目录开始:

tar -C /var -czf var-backup.tar.gz lib/systemd

这样归档中的路径是 lib/systemd/...,而不是 /var/lib/systemd/...。恢复时可以选择目标根目录:

tar -xzf var-backup.tar.gz -C /restore-root

三、gzip、xz 和 zstd 的区别

1. gzip:兼容性优先

gzip 使用 DEFLATE 压缩算法。它的特点是:

  • 在 Unix 工具链中普及时间长;
  • 几乎所有 Linux、BSD、macOS 和归档工具都支持;
  • 压缩和解压通常较快;
  • 压缩率一般不如 xz;
  • 支持流式处理。

直接压缩 tar:

tar -czf demo.tar.gz demo/

其中 -z 表示通过 gzip 处理。

手动拆开两步:

tar -cf demo.tar demo/
gzip -k demo.tar

-k 保留原始 demo.tar。如果不使用 -kgzip 默认可能删除未压缩的输入文件。

验证 gzip 数据流:

gzip -t demo.tar.gz

无输出且退出状态为 0,通常表示 gzip 数据流的校验通过。注意,这不等于已经验证 tar 中的每个文件都能按预期恢复;还应测试 tar 层:

tar -tzf demo.tar.gz > /dev/null

2. xz:压缩率优先

xz 通常使用 LZMA2。它的特点是:

  • 对文本、源码、日志等数据通常有较好的压缩率;
  • 高压缩级别可能需要较多 CPU 和内存;
  • 解压通常比压缩轻,但仍可能受内存和字典大小影响;
  • 适合发布包、长期存档和带宽受限场景;
  • 不应仅因为压缩率高就用于所有在线备份。

创建 xz 归档:

tar -cJf demo.tar.xz demo/

-J 表示 xz。

显式测试:

xz -t demo.tar.xz
tar -tJf demo.tar.xz > /dev/null

设置压缩级别:

tar -cJf demo.tar.xz demo/

GNU tar 的 -J 使用 xz 默认设置。若需要显式传递参数,可以使用 -I

tar -I 'xz -6' -cf demo.tar.xz demo/

压缩级别不是简单的“越大越好”。更高级别通常提高压缩时间和内存消耗,实际节省的空间可能很小。生产环境应使用真实数据测试,而不是根据数字直接推断收益。

3. zstd:速度和压缩率的平衡

zstd 是现代压缩格式,通常在压缩速度、解压速度和压缩率之间取得较好的平衡。它支持:

  • 流式压缩;
  • 多线程压缩;
  • 不同压缩级别;
  • 字典压缩;
  • 对大型数据流的较好吞吐表现。

创建 zstd 归档:

tar --zstd -cf demo.tar.zst demo/

也可以使用显式压缩命令:

tar -I 'zstd -3' -cf demo.tar.zst demo/

使用多个线程:

tar -I 'zstd -T0 -3' -cf demo.tar.zst demo/

-T0 通常表示让 zstd 使用可用 CPU 线程数,但具体资源消耗由 zstd 版本、CPU 和输入决定。在线备份中盲目使用所有线程,可能影响同机业务。

验证:

zstd -t demo.tar.zst
tar --zstd -tf demo.tar.zst > /dev/null

4. 选择压缩器的因果关系

可以从数据流和资源约束推导选择:

场景 通常更合适的选择 原因
需要最大兼容性 gzip 工具链支持广
发布包、长期存储、带宽昂贵 xz 通常更重视压缩率
在线备份、快速传输、现代 Linux zstd 通常在速度和压缩率间平衡
已压缩媒体或 ZIP 低级别 gzip/zstd,甚至不压缩 再压缩收益小,CPU 可能浪费

这些是经验取舍,不是格式规范保证。实际结果取决于输入数据、压缩级别、CPU、磁盘和网络。


四、归档中的元数据:权限、所有者、链接和扩展属性

1. 文件内容不等于文件状态

仅保存普通文件内容,无法完整恢复一个 Linux 系统中的文件状态。恢复结果还可能受到以下信息影响:

  • 权限位,例如 06440755
  • 用户和组;
  • 符号链接;
  • 硬链接关系;
  • 文件时间;
  • ACL;
  • xattr,例如 SELinux 标签;
  • 稀疏文件的空洞;
  • 设备节点和 FIFO。

GNU tar 默认会保存许多基本属性,但 ACL 和 xattr 需要显式启用:

sudo tar --acls --xattrs -czf etc-backup.tar.gz /etc

恢复时也应使用对应选项:

sudo tar --acls --xattrs -xzf etc-backup.tar.gz -C /restore-root

这不是所有 tar 实现都完全一致。GNU tar 的选项不能无条件假设在 BusyBox tar、BSD tar 或其他精简环境中存在。跨发行版恢复前应检查:

tar --version
tar --help | grep -E 'acls|xattrs|sparse'

2. 所有者恢复需要权限

普通用户创建归档时,通常无法读取其他用户的文件,也无法在恢复时任意设置文件所有者。系统级备份一般需要 root 或具有相应能力的进程:

sudo tar --acls --xattrs -czf /backup/etc.tar.gz -C / etc

恢复时:

sudo tar --acls --xattrs -xzf /backup/etc.tar.gz -C /restore-root

即使以 root 恢复,也应区分两种目标:

  • 系统恢复:希望尽可能保留原所有者、权限、设备节点和标签;
  • 普通文件分发:希望文件归当前用户所有,避免归档中的 UID 造成访问问题。

如果目标是普通用户可读目录,直接使用系统恢复参数可能导致文件属于不存在的 UID,或属于 root 而当前用户无法修改。

3. 硬链接和符号链接

硬链接是同一个 inode 的多个目录项;符号链接是保存目标路径的特殊文件。tar 通常会记录硬链接关系,而不是重复写入硬链接指向的文件内容。

验证恢复后的硬链接:

stat -c '%d:%i %n' restore/demo/etc/app.conf \
                     restore/demo/etc/app.conf.hardlink

如果两者 inode 相同,说明硬链接关系保留。

符号链接应使用:

readlink restore/demo/logs/app.conf.link

归档和恢复时不要把符号链接误当作普通文件。处理不可信归档时,符号链接可能与目录覆盖组合形成安全风险,因此恢复权限和目标目录必须受控。


五、校验和:验证完整性,但不要误当作认证

1. SHA-256 校验文件是否发生变化

对归档文件计算 SHA-256:

sha256sum demo.tar.zst > demo.tar.zst.sha256
cat demo.tar.zst.sha256

输出类似:

<64 个十六进制字符>  demo.tar.zst

在另一台机器或传输后验证:

sha256sum -c demo.tar.zst.sha256

成功时通常输出:

demo.tar.zst: OK

这里校验的是整个压缩归档文件。只要一个字节变化,摘要大概率就会变化。

2. 压缩器内部校验和与外部校验和不是一回事

gzip、xz、zstd 的格式通常包含各自的数据完整性检查。命令:

gzip -t file.tar.gz
xz -t file.tar.xz
zstd -t file.tar.zst

主要检查压缩格式内部的数据是否损坏。

外部 SHA-256 文件则解决另一个问题:传输后拿到的文件是否与发布者或备份端计算的文件一致。

完整验证可以分层进行:

sha256sum -c demo.tar.zst.sha256 &&
zstd -t demo.tar.zst &&
tar --zstd -tf demo.tar.zst > /dev/null

三步分别对应:

  1. 文件与记录中的摘要一致;
  2. zstd 压缩流可以正确解码;
  3. 解码后的 tar 结构可以读取。

如果还要验证实际恢复结果,必须真正提取到隔离目录并检查文件:

rm -rf restore-check
mkdir restore-check
tar --zstd -xf demo.tar.zst -C restore-check
diff -ruN demo restore-check/demo

对于包含特殊元数据的备份,还需要额外检查:

stat restore-check/demo/etc/app.conf
getfacl restore-check/demo/etc/app.conf 2>/dev/null
getfattr -d restore-check/demo/etc/app.conf 2>/dev/null

3. 校验和不提供身份认证

SHA-256 可以发现随机损坏,但如果攻击者能够同时修改归档和 .sha256 文件,就可以重新计算摘要。因此:

  • 完整性检查:使用 SHA-256;
  • 来源认证:使用数字签名或带密钥的 MAC;
  • 保密性:使用加密工具或加密备份系统。

例如,签名流程可以是:

sha256sum demo.tar.zst > demo.tar.zst.sha256
gpg --detach-sign --armor demo.tar.zst.sha256

验证需要可信的公钥:

gpg --verify demo.tar.zst.sha256.asc demo.tar.zst.sha256
sha256sum -c demo.tar.zst.sha256

签名验证通过,只能说明摘要文件由对应私钥签名;随后仍需执行 SHA-256 校验,确认归档文件确实匹配该摘要。


六、一个可验证的备份流程

下面的示例将 demo/ 归档为 zstd 文件,并同时保留校验和。

set -euo pipefail

src="$(pwd)/demo"
out="$(pwd)/backup"
mkdir -p "$out"

tar --acls --xattrs \
    --zstd \
    -cf "$out/demo.tar.zst" \
    -C "$(dirname "$src")" \
    "$(basename "$src")"

sha256sum "$out/demo.tar.zst" > "$out/demo.tar.zst.sha256"

zstd -t "$out/demo.tar.zst"
tar --acls --xattrs --zstd -tf "$out/demo.tar.zst" > "$out/demo.file-list"

sha256sum -c "$out/demo.tar.zst.sha256"

每一步的作用不同:

  1. tar ... -cf 读取源目录并生成归档;
  2. -C 使归档使用相对路径,避免直接保存 /绝对路径
  3. --acls --xattrs 请求保存 ACL 和扩展属性;
  4. sha256sum 记录归档文件的外部摘要;
  5. zstd -t 验证压缩层;
  6. tar -t 验证 tar 层能够遍历;
  7. sha256sum -c 验证文件没有被改变。

set -o pipefail 对管道很重要。例如:

set -o pipefail
tar -cf - demo | zstd -3 > demo.tar.zst

如果 tar 因为权限错误提前失败,而 zstd 正常结束,缺少 pipefail 时脚本可能只看到 zstd 的成功状态。启用 pipefail 后,只要管道中的任意关键命令失败,管道状态就会反映失败。

生产脚本还应避免在归档未验证前删除源数据:

if tar --zstd -cf backup.tar.zst demo/ &&
   zstd -t backup.tar.zst &&
   sha256sum backup.tar.zst > backup.tar.zst.sha256
then
    echo "归档已创建,尚未删除源数据"
else
    echo "归档失败,保留源数据" >&2
    exit 1
fi

“命令返回 0”只表示该命令认为操作成功,不表示备份策略已经具备可恢复性。恢复演练仍然必须定期执行。


七、大文件的核心问题

“大文件”不是单一限制,而是多个边界叠加:

  1. 文件系统是否支持该大小;
  2. tar 格式是否能表示该文件大小;
  3. 压缩器的字典、内存和时间是否可接受;
  4. 单个归档是否适合传输;
  5. 失败后是否能从中间位置恢复;
  6. 目标介质是否支持相同的稀疏文件、权限和元数据。

1. 现代 Linux 通常支持大于 4 GiB 的文件

在现代 64 位 Linux 和常见文件系统上,单个普通文件超过 4 GiB 通常没有问题。历史 tar 格式曾存在文件大小字段限制,因此现代 GNU tar 使用 POSIX pax 扩展或 GNU 扩展表示大文件。

可以检查归档格式:

tar --format=pax -cf large.tar large-file

pax 格式是 POSIX 体系中用于扩展 tar 元数据的格式,适合需要长文件名、大文件大小、扩展时间等信息的场景。若需要和非常老的 tar 工具互操作,必须在创建端和恢复端实际测试;“现代 Linux 可以创建”不代表“老系统一定能读取”。

2. 内存不一定与文件总大小相等

tar 处理文件时通常按块读取,并不需要把整个文件载入内存。压缩器的内存消耗主要受算法、压缩级别、字典大小和实现影响。

因此,一个 1 TiB 文件不必然要求 1 TiB 内存,但以下风险仍存在:

  • xz 高级别可能消耗较多内存;
  • zstd 大字典或高压缩级别会提高资源使用;
  • 多线程压缩会同时增加 CPU 和内存压力;
  • 磁盘、网络和目标文件系统可能先成为瓶颈。

可以先观察输入和输出,不要只凭文件名判断:

du -h --apparent-size large-file
du -h large-file
ls -lh large-file

--apparent-size 显示逻辑大小;普通 du 通常反映实际占用的磁盘块。两者差距很大时,文件可能是稀疏文件。

3. 稀疏文件

稀疏文件包含逻辑上的大段零,但这些零可能没有实际分配磁盘块。若普通复制或归档过程把空洞当作真实零字节写入,恢复后文件逻辑大小相同,但磁盘占用会显著增加。

创建一个稀疏文件:

truncate -s 10G sparse.img
du -h sparse.img
du -h --apparent-size sparse.img

使用 GNU tar 的稀疏选项:

tar --sparse -cf sparse.tar sparse.img
tar --sparse -xf sparse.tar -C restore

恢复后比较:

du -h restore/sparse.img
du -h --apparent-size restore/sparse.img

--sparse 通过识别零块并在归档中记录空洞来减少实际数据量。目标文件系统必须支持相应的稀疏文件语义,否则恢复结果可能仍占用实际零块。

4. 分卷和 split

压缩归档通常是一个连续数据流。如果文件太大,不适合直接放入某个文件系统、对象存储对象或移动介质,可以在压缩完成后切分:

split -b 4G --numeric-suffixes=1 --suffix-length=4 \
      demo.tar.zst demo.tar.zst.part-

结果类似:

demo.tar.zst.part-0001
demo.tar.zst.part-0002
demo.tar.zst.part-0003

恢复为完整归档:

cat demo.tar.zst.part-* > demo-restored.tar.zst

这里使用固定宽度数字后缀,能够保证字典序与分片顺序一致。不要使用可能产生如下排序问题的命名:

part-1
part-2
part-10

因为普通字典序可能把 part-10 排在 part-2 前面。

应同时记录:

sha256sum demo.tar.zst.part-* > parts.sha256
sha256sum demo-restored.tar.zst > demo-restored.tar.zst.sha256

恢复时先验证分片,再拼接:

sha256sum -c parts.sha256
cat demo.tar.zst.part-* > demo-restored.tar.zst
sha256sum -c demo-restored.tar.zst.sha256
zstd -t demo-restored.tar.zst

分片不会增加随机恢复能力。若第 2 个分片损坏,连续压缩流可能从该位置开始无法继续解码,仍需要重新获得损坏分片或整个归档。

GNU tar 也有 -M 多卷归档功能,但它面向磁带等顺序介质,常涉及换卷提示和交互式流程:

tar -cMf archive.tar -L 4000000 source/

这种方式不一定适合无人值守的文件上传。对于普通文件、对象存储和脚本流程,先生成压缩归档再使用 split 通常更容易验证和重试。

5. 流式传输和远程归档

tar 不要求归档必须落在本地文件中,可以写到标准输出:

tar -cf - demo | zstd -3 | ssh backup-host 'cat > /backup/demo.tar.zst'

恢复时反向处理:

ssh backup-host 'cat /backup/demo.tar.zst' |
    zstd -dc |
    tar -xf - -C restore

这类命令的数据流是:

本地 tar
  -> 本地 zstd
  -> SSH 加密传输
  -> 远端文件

SSH 已经提供传输加密,但没有自动替代文件级校验。传输后仍应在发送端和接收端分别计算摘要,或者在接收端验证已保存文件。

如果需要边传输边计算摘要,可以使用 tee

set -o pipefail

tar -cf - demo |
    zstd -3 |
    tee demo.tar.zst |
    sha256sum > demo.tar.zst.sha256

这里 tee 将同一数据流同时写入文件和 sha256sum。只有在整个管道成功结束后,摘要文件才应被视为有效。若磁盘写满、源文件读取失败或 SSH 断开,必须检查各命令退出状态,而不是仅查看输出文件是否存在。


八、归档一致性:正在变化的文件不能自动变成一致备份

tar 读取文件时,文件可能仍在被其他进程写入。可能出现以下状态:

  1. tar 先读取到文件大小;
  2. 应用继续追加内容;
  3. tar 读取到旧大小以内的一部分或全部内容;
  4. 归档记录的内容与某个瞬间的完整文件状态不一致。

即使 tar 成功退出,这也不代表数据库、虚拟机磁盘或多个相互关联文件处于一致状态。

普通日志文件

日志追加通常可以接受“某个时间窗口内的近似快照”,但要明确这不是严格一致备份。

数据库

数据库文件可能有页缓存、WAL、事务日志和多个关联文件。直接 tar 数据目录可能得到无法恢复或需要额外修复的状态。应使用数据库原生备份机制,或者先创建一致性快照,再从快照上归档。

LVM、文件系统和虚拟机快照

更可靠的流程通常是:

应用协调或暂停写入
        │
        ▼
创建一致性快照
        │
        ▼
从快照读取并 tar + 压缩
        │
        ▼
校验、传输、恢复演练

快照只提供某个时刻的视图,并不自动提供长期备份。快照所在存储损坏、被误删或空间耗尽时,快照本身不能替代独立备份。


九、递归归档时的边界与排除

递归归档可能把不应保存的内容一起写入,例如:

  • /proc/sys/dev/run 等伪文件系统;
  • 挂载在源目录下的其他文件系统;
  • 缓存、临时文件和构建产物;
  • 备份输出目录本身,导致递归增长;
  • 包含密码、令牌和私钥的配置文件。

对系统目录归档时,--one-file-system 可以避免跨越文件系统边界:

sudo tar --one-file-system \
    --acls --xattrs \
    --zstd -cf root-files.tar.zst -C / \
    etc home var

--one-file-system 不是“只归档一个目录”的意思,而是当递归遍历遇到不同设备号的挂载点时不继续进入。它适合避免把 NFS、容器挂载或临时文件系统意外打包,但也可能漏掉业务真正需要的数据,因此必须明确备份范围。

排除路径:

tar --zstd -cf home.tar.zst \
    --exclude='home/*/.cache' \
    -C / home

排除模式的语义与 tar 版本有关,复杂规则应先用 tar -tvf 检查归档清单,不要直接依赖 shell 展开。


十、解压不可信归档的安全边界

tar 归档可以包含绝对路径、.. 路径、符号链接、设备节点和特殊文件。直接以 root 解压不可信归档,可能覆盖系统文件或制造符号链接相关风险。

先查看清单:

tar --zstd -tvf untrusted.tar.zst

检查可疑路径:

tar --zstd -tf untrusted.tar.zst |
    grep -E '(^/|(^|/)\.\.(/|$))'

更安全的基本做法:

mkdir -p /tmp/safe-restore
tar --zstd \
    --no-absolute-names \
    --keep-old-files \
    -xf untrusted.tar.zst \
    -C /tmp/safe-restore

含义:

  • --no-absolute-names:不按归档中的绝对路径写入;
  • --keep-old-files:目标文件已存在时不覆盖;
  • 使用专门的临时目录,避免直接写入生产目录。

--keep-old-files 会使已存在的文件导致提取失败,这通常是安全行为。若确实需要覆盖,应先确认归档来源、清理目标目录并明确恢复范围,而不是简单删除保护选项。

设备节点、所有者和权限恢复通常需要 root,因此最安全的做法是:

  1. 先以普通用户在隔离目录查看;
  2. 只提取需要的内容;
  3. 确认归档来源;
  4. 必须系统恢复时,再在受控环境中使用 root。

十一、可重复归档和时间变化

相同文件内容不一定产生相同 tar 文件,因为归档还包含:

  • 修改时间;
  • 所有者和组;
  • 文件遍历顺序;
  • tar 格式;
  • pax 扩展字段;
  • ACL 和 xattr;
  • 压缩器版本或参数。

如果要构建可复现的软件发布包,可以显式固定部分字段:

tar --format=pax \
    --sort=name \
    --mtime='UTC 1970-01-01' \
    --owner=0 \
    --group=0 \
    --numeric-owner \
    -cf source.tar source/

各选项的目的:

  • --sort=name:固定文件遍历顺序;
  • --mtime:固定修改时间;
  • --owner=0 --group=0:固定归档中的数值所有者;
  • --numeric-owner:不依赖创建机器上的用户名解析;
  • --format=pax:使用现代扩展格式表示长名称和大文件。

但这不等于所有环境下的字节级可复现。xattr、ACL、文件名编码、pax 字段和压缩器版本仍可能产生差异。可复现构建必须把这些输入也纳入控制;普通备份则不应为了“文件字节完全相同”而擅自丢弃真实元数据。


十二、tar 增量选项不是完整备份策略

GNU tar 提供基于快照文件的增量能力,例如:

tar --listed-incremental=snapshot.snar \
    -czf backup-inc.tar.gz data/

该机制会记录目录状态,后续运行时根据快照文件判断哪些文件变化。它与 rsync 的增量算法不同,也不是数据库级备份,更不是快照。

需要特别注意:

  • snapshot.snar 是增量链状态的一部分;
  • 完整备份和后续增量必须保留正确顺序;
  • 丢失或错误替换快照文件会破坏增量判断;
  • 删除文件的表达和恢复行为需要实际测试;
  • 增量链越长,恢复依赖越多;
  • 并发修改期间仍然存在一致性问题。

如果目标是长期备份,通常需要设计完整备份、增量备份、保留周期、异地副本、校验和和恢复演练,而不是仅给 tar 增加一个增量参数。


十三、常见失败表现和诊断顺序

“tar: Cannot open: Permission denied”

原因通常是:

  • 当前用户无权读取源文件;
  • 无权写入目标目录;
  • 目标归档已存在但无权覆盖;
  • 父目录搜索权限不足。

诊断:

id
namei -l /path/to/source
ls -ld /path/to/output

必要时使用 sudo,但不要把 sudo 当作忽略权限设计的工具。备份脚本应明确运行身份和输出文件权限。

“Unexpected EOF” 或压缩测试失败

可能原因:

  • 文件未传输完整;
  • 分片缺失或顺序错误;
  • 磁盘写满导致输出被截断;
  • 网络连接中断;
  • 文件在生成过程中被读取;
  • 存储介质发生损坏。

诊断顺序:

sha256sum -c backup.tar.zst.sha256
zstd -t backup.tar.zst
tar --zstd -tf backup.tar.zst > /dev/null
df -h
df -i

先检查外部摘要,再检查压缩层和 tar 层,可以区分“文件变了”和“文件本身结构损坏”。

“tar: Removing leading /

这通常是 tar 为避免提取到绝对路径而进行的安全处理提示,不一定是错误。更好的创建方式是使用:

tar -C / -cf root-part.tar etc

让归档从一开始就使用相对路径。

归档恢复后文件所有者不对

可能是:

  • 创建时不是 root,无法读取原始所有者;
  • 恢复时不是 root,无法设置所有者;
  • 使用了用户名而非数值 UID/GID;
  • 目标系统没有同名用户;
  • 目标系统的 UID 分配不同。

系统恢复时使用数值 UID、root、ACL 和 xattr 选项,并在目标环境检查身份数据库。跨主机迁移普通数据时,则应根据目标系统的用户模型重新设置所有者。


十四、一个完整的恢复验证流程

假设已经拿到:

demo.tar.zst
demo.tar.zst.sha256

先在非生产目录执行:

set -euo pipefail

sha256sum -c demo.tar.zst.sha256
zstd -t demo.tar.zst

rm -rf restore-check
mkdir restore-check

tar --zstd -xf demo.tar.zst -C restore-check

随后检查目录内容:

find restore-check/demo -printf '%M %u:%g %s %p\n' | sort

检查内容差异:

diff -ruN demo restore-check/demo

检查硬链接:

stat -c '%i %n' \
    restore-check/demo/etc/app.conf \
    restore-check/demo/etc/app.conf.hardlink

检查符号链接:

readlink restore-check/demo/logs/app.conf.link

如果备份包含稀疏文件、ACL、xattr、设备节点或服务配置,还应分别验证这些属性。只有“归档可以列目录”而没有“实际提取并检查”,不能证明恢复流程可用。


结语

tar 解决的是文件集合、路径和元数据的顺序归档;gzipxzzstd 解决的是归档数据流的压缩;压缩器内部检查和外部 SHA-256 解决的是不同层面的损坏检测。大文件则把 tar 格式、文件系统、压缩资源、流式传输、分片和恢复顺序这些问题同时暴露出来。

可靠流程不是“执行一条 tar 命令”,而是:

确定一致的数据视图
  -> 正确选择归档范围和元数据
  -> 选择压缩器与资源参数
  -> 生成归档
  -> 计算外部校验和
  -> 验证压缩层和 tar 层
  -> 在隔离环境中实际恢复
  -> 定期重复恢复演练

在生产环境中,备份文件只有在能够被验证、传输、读取并恢复为正确状态时,才真正具有备份价值。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。