← cd ../posts

打包、压缩、同步:tar / gzip / zstd / rsync 实战

2026-06-27

这是 Linux 系列的第 14 篇——文件与权限章的收尾。前面学了找文件、看权限、管挂载——这一篇学搬运它们。

0. 为什么 rsync 一年节省你一年时间

举个真实例子:你笔记本上有 1TB 的项目目录,需要传到一台远程服务器:

方式 时间 评价
scp -r 几小时(看带宽) 简单,但每次都从零开始
`tar ssh server "tar -x"` 同 scp,无压缩稍快
rsync -av --partial 首次同 scp,第二次几秒 只传变化的部分,断了能续

rsync 是 1996 年 Andrew Tridgell 写的算法——比对两边文件后只传不同的块。这一个工具值得专门花 1 小时学。

下面分四节:tar / 压缩格式 / rsync / 真实场景。


1. tar:打包瑞士军刀

tar 来自 tape archive(磁带归档)的缩写——历史上是给磁带备份用的。今天它的本职工作是:

把一棵目录树打包成一个文件

常用 4 个 flag 记下来够 95% 场景:

c  create     建
x  extract    解
t  list       列内容(不解压)
v  verbose    显示过程
f  file       后面接文件名(必加,否则 tar 默认对 stdin/stdout)

再加 3 个表示压缩的:

z  gzip       .tar.gz
j  bzip2      .tar.bz2
J  xz         .tar.xz

组合 4 件套:

# 打包 + gzip 压缩
$ tar czvf project.tar.gz project/

# 解压
$ tar xzvf project.tar.gz

# 只看里面有什么(不解压)
$ tar tzvf project.tar.gz

# 不压缩纯打包(速度最快)
$ tar cvf project.tar project/

记忆:create / extract / list + zip / file。读起来 czvf 就是 "create-zip-verbose-file"。

现代 tar 自动识别压缩格式

# 不用记 z / j / J
$ tar xvf anything.tar.gz       # 自动 gunzip
$ tar xvf anything.tar.bz2      # 自动 bunzip2
$ tar xvf anything.tar.xz       # 自动 unxz
$ tar xvf anything.tar.zst      # 自动 unzstd(zstd 用 --zstd)

GNU tar 1.31+ 都支持自动识别。记一条 tar xvf 解 99% 的归档。

高频选项

# 解到指定目录
$ tar xvf foo.tar.gz -C /tmp/

# 只解某些文件
$ tar xvf foo.tar.gz --wildcards 'src/*.py'

# 打包时排除某些
$ tar czvf src.tar.gz --exclude='node_modules' --exclude='.git' src/

# 备份时保留权限 / 属主(重要!)
$ sudo tar czvf etc-backup.tar.gz --preserve-permissions /etc/

# 跟 ssh 配合:"直接传到远端不落地"
$ tar cf - data/ | ssh server "tar xf - -C /backup"

最后这条特别有用——本机不留中间文件,直接流式传。


2. 压缩格式怎么挑

格式 速度 压缩比 CPU 占用 适合
gzip (.gz) 最通用,兼容性最好
bzip2 (.bz2) 不推荐(被 xz/zstd 全面碾压)
xz (.xz) 很慢 最高 最高 发布软件包(一次压缩多次下载)
zstd (.zst) 最快 接近 xz 强烈推荐,2017+ 后所有新场景
zip 跟 Windows 互通

实测对比(压缩 1GB 文本日志)

命令 时间 输出大小
gzip 8s 220MB
gzip -9 30s 200MB
bzip2 60s 180MB
xz -9 5min 140MB
zstd -3 (默认) 3s 230MB
zstd -19 2min 150MB
zstd -19 --long 4min 140MB ← 媲美 xz 但解压快 5x

经验

  • 临时压一下 / 日志压缩:zstd
  • 一次性发布要小:xzzstd --long -19
  • 给老系统 / 通用:gzip

单独命令用法

# 压缩(原文件被替换成 .gz)
$ gzip large.log              # → large.log.gz
$ gzip -k large.log           # -k 保留原文件
$ gunzip large.log.gz

# 流式压缩(不替换原文件)
$ gzip < large.log > large.log.gz
$ zcat large.log.gz | grep ERROR    # 不解压直接读

# zstd
$ zstd -3 file
$ zstd -19 --long file        # 高压缩比
$ unzstd file.zst
$ zstdcat file.zst | grep ...

# 看压缩比
$ ls -lh large.log{,.gz}

zcat 系列工具(zcat / bzcat / xzcat / zstdcat)让你不解压直接读 —— 跑 grep / awk 都行:

$ zcat app.log.gz | awk '$9 == 500' | head

3. rsync:增量同步的杀手锏

rsync 解决一个核心问题:

A 目录有 100GB 数据,B 目录有 99.9GB 几乎一样的数据。我怎么把 A 同步到 B,只传那 100MB 差异?

答案:rsync 对每个文件分块算 hash,只传 hash 不一样的块

基本用法

# 本地同步
$ rsync -av source/ destination/

# 远程同步(最常用)
$ rsync -av source/ user@server:/path/to/dest/

# 反向:从远端拉
$ rsync -av user@server:/path/ ./local/

注意路径末尾的 /

  • rsync src/ dst/ → 把 src 内容放到 dst 下
  • rsync src dst/ → 把 src 这个目录放到 dst 下(多套一层)

90% 的人都被这个坑过。习惯:两边都带 / 表示"目录内容"。

-av 这个组合

a (archive) = -rlptgoD:
   r  递归
   l  保留 symlink
   p  保留权限
   t  保留时间戳
   g  保留属组
   o  保留属主
   D  保留设备 / 特殊文件

v (verbose) = 显示传输过程

-av = "完整保真复制 + 看到进度"。

常用补充选项

-z              # 传输时压缩(带宽紧时用,CPU 多的时候用)
-h              # 人类可读大小
--progress      # 显示每个文件进度
-P              # = --partial --progress(推荐)
--delete        # 让目标跟源一模一样(**会删多余文件**,小心)
-n / --dry-run  # 干跑,看会传什么但不真的传
--exclude='*.log'   # 排除模式
--exclude-from=file # 从文件读排除规则(每行一条)
--bwlimit=10M   # 限速 10MB/s(避免占满带宽)

经典用法 5 例

# ① 推代码到生产服务器(不要 .git 不要 node_modules)
$ rsync -avP --delete \
    --exclude '.git/' --exclude 'node_modules/' \
    ./ user@prod:/var/www/myapp/

# ② 每天备份家目录到远程,带断点续传
$ rsync -avP --link-dest=/backups/yesterday \
    ~/ backup@nas:/backups/today/
# --link-dest 让没变的文件做硬链接(多次备份只占一份空间)

# ③ 镜像本地一份网站到 USB 移动盘
$ rsync -avh --delete /var/www/ /mnt/usb/web-backup/

# ④ 把 root 备份还原到新机器(保权限)
$ ssh new-server "rsync -avh root@old-server:/etc/ /etc/"

# ⑤ 验证两边一致(不实际传)
$ rsync -avhn source/ dest/ | grep -v '^total'
# 输出不为空 = 还有差异

--delete 的危险性

# 这会让 dest 跟 src 一模一样——dest 比 src 多的文件**都被删**
$ rsync -av --delete src/ dest/

# 写错斜杠 ← 很容易
$ rsync -av --delete src dest/      # ← 没加 / 表示同步整个 src 目录
# 但配 --delete 后果可能极坏

--delete 之前先 -n 干跑确认

$ rsync -avn --delete src/ dest/
# 看输出里有没有意外被删的,确认 OK 再去掉 -n

4. 实战场景

场景 A:1TB 数据从笔记本到 server,首次

# 笔记本上
$ rsync -avP --bwlimit=10M \
    ~/projects/big-data/ \
    user@server:/mnt/data/

-P 关键——断网了重跑同一条命令会从断点续。

场景 B:服务器 A 数据库每天备份到 B

# A 上(crontab)
0 2 * * * /usr/bin/rsync -avh --delete \
    /var/backups/mysql/ \
    backup@B:/backups/A/mysql/

每天凌晨 2 点跑,第二次开始只传当天变化。

场景 C:远程下载一个网站做镜像(只读

# wget 也行,但 rsync 更聪明(如果对方支持 rsync daemon)
$ rsync -avh --progress rsync://mirror.kernel.org/linux/kernel/v6.x/ ./linux-mirrors/

很多镜像站(Linux kernel / Debian / Arch)都开了 rsync 接口,比 HTTP 快几倍。

场景 D:临时打包 + ssh 传输

不能持续 rsync 的场景(比如对方 rsync 没装),临时用 tar + ssh:

# 本地打包,stdin 给 ssh,远端 tar 解包,零中间文件
$ tar czf - src/ | ssh server "cd /target && tar xzf -"

# 加进度条
$ tar czf - src/ | pv | ssh server "cd /target && tar xzf -"

# 加压缩级别(zstd)
$ tar cf - src/ | zstd -3 | ssh server "cd /target && zstd -d | tar xf -"

场景 E:增量备份 + 历史快照

$ rsync -avh --delete \
    --link-dest=/backups/$(date -d 'yesterday' +%F) \
    /home/ \
    /backups/$(date +%F)/

每天跑:

  • 没变化的文件硬链接到昨天的备份(几乎不占空间
  • 变化的文件复制新版
  • 100 天后你有 100 份"完整快照",但实际只占略大于 1 份的空间

这是 Time Machine / borgbackup 的核心原理。


5. 几个相关工具简介

工具 一句话
scp 简单远程复制;新版 ssh-9 把它改成调 sftp,旧脚本可能有兼容问题。日常优先用 rsync
sftp 交互式远程文件操作(put / get / ls)
rclone rsync 风格但面向云存储(S3 / GCS / OSS / OneDrive 一锅端)
borgbackup 加密 + 去重 + 压缩 的备份神器,给 NAS 用
restic 同 borg 但更现代,go 写的,跨平台
lftp 给老 FTP / 不稳定网络用

云存储相关:

# rclone 例子:把本地目录传到阿里云 OSS
$ rclone config            # 配 OSS 凭据
$ rclone sync ./data/ oss-mybucket:/backup/data/

6. 一份"备份策略最小可用模板"

#!/bin/bash
# /usr/local/bin/daily-backup.sh
set -e

SOURCE="/home /etc /var/lib/myapp"
DEST="backup-user@nas:/backups/$(hostname)"
TODAY=$(date +%F)
YESTERDAY=$(date -d 'yesterday' +%F 2>/dev/null || date -v -1d +%F)

rsync -avh --delete \
    --link-dest="$DEST/$YESTERDAY" \
    --exclude='.cache' \
    --exclude='node_modules' \
    --exclude='*.tmp' \
    $SOURCE \
    "$DEST/$TODAY/"

放 crontab:

30 3 * * * /usr/local/bin/daily-backup.sh >> /var/log/daily-backup.log 2>&1

凌晨 3:30 跑,30 天循环。一台 NAS 撑下来即使 100 个快照也不会爆。


7. 现在做一件事

# 1. 把你 ~/Documents(或任何小目录)打包压缩
$ tar czvf docs.tar.gz ~/Documents/
$ ls -lh docs.tar.gz

# 2. 看看压缩比
$ du -sh ~/Documents
$ ls -lh docs.tar.gz

# 3. 用 rsync 干跑模拟一次部署
$ rsync -avhn --delete ~/Documents/ /tmp/docs-test/ | head

# 4. 测一下 zstd vs gzip 速度差
$ time tar cf - ~/Documents | gzip  > /tmp/x.gz
$ time tar cf - ~/Documents | zstd  > /tmp/x.zst
$ ls -lh /tmp/x.*

# 5. 装个 rsync daemon 模式跟外面互通(高阶,可选)
$ man rsyncd.conf

把这些命令变成肌肉记忆——文件与权限章节就毕业了。


下一篇fork-exec——"进程怎么来的"。fork()/exec() 这一对古老 syscall 是 Unix 进程模型的根,理解它你才懂为什么 shell 是这么设计的、为什么 ps 树是那个样子。