news 2026/10/27 21:36:04

Linux归档压缩与日志备份实战:tar、gzip、zstd避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux归档压缩与日志备份实战:tar、gzip、zstd避坑指南

凌晨两点被监控叫醒,根分区使用率 98.7%。登上机器一看,/var/log底下堆着两百多个 G 的历史日志,全是散装文件,一个.log.1一个.log.2.gz,混着几个被遗忘的nohup.out。那一刻我脑子里只有一个念头:如果当初做了归档,现在就不用在这里一个一个find加rm了。Linux 下的打包和解压缩,看着是最基础的操作系统技能,但真正在生产环境里出过事的人都明白,tar的字母顺序记错一个、解压时少写一个-C、或者把绝对路径打进包里,代价可能是整个目录被覆盖、权限全乱、甚至把别人的家目录解到自己头上。这篇内容我想彻底把这件事讲透:归档和压缩到底是不是一回事,tar那串鬼画符参数怎么记才不翻车,几条主流压缩链(gzip、bzip2、xz、zstd)在什么场景下该选谁,跨平台传文件时中文名乱码怎么根治,以及日志清理、增量备份这类真实场景里的脚本骨架。不管你是刚接触命令行、还在纠结-cvf和-xvf的新手,还是已经写过几轮备份脚本、被Unexpected EOF坑过几次的老手,下面这些内容应该都能让你少走点弯路。

1. 归档和压缩根本是两件事,先把这个概念掰正

1.1 tar 只是个“箱子”,它自己不压缩

很多人第一次学 Linux 打包,脑子里形成的印象是“tar -zcvf就是压缩命令”。这个理解偏差会带来一连串问题:不知道该不该加-z、看到.tar和.tar.gz分不清、解压时报not in gzip format时完全无从下手。准确的说法是:tar(tape archive,磁带归档)干的活是把一堆文件和目录串成一个单一的文件流,同时记录它们的路径、权限、属主、时间戳、软硬链接关系。它本质上是打包,产出物叫归档文件,体积和原文件加起来差不多。

真正的压缩是另一层动作,由 gzip、bzip2、xz、zstd 这类工具完成。你在tar后面加的-z并不是让tar自己变聪明,而是让它打包完成后,把数据流通过管道丢给 gzip 处理一遍。这个“管道串联”的设计是 Unix 哲学的经典体现,也是理解后面所有参数组合的钥匙。

用生活化的比方:tar就是把散落在桌上的书、本子、笔全部装进一个纸箱,箱子上贴清单;gzip 是把这个纸箱抽真空、压扁体积。你可以只装箱(.tar),也可以装完箱再抽真空(.tar.gz)。箱子里装了什么、清单怎么写的,是tar的事;压得多紧,是压缩算法的事。分开理解,后面遇到“先打包再分卷压缩”这种需求时,你自然就知道该在哪一层动手。

1.2 四种主流压缩算法,选错就是白白浪费 CPU

Linux 下能见到的压缩后缀不下十种,但真正高频使用的就四条链路,它们的差异远比想象中大。下面这张表是我在一台普通开发机上用同一份 480MB 混合数据(源代码 + 日志 + 少量二进制)实测的结果,数值会因为数据特性浮动,但相对关系很稳定:

链路典型参数压缩后体积耗时(单核)解压耗时适用场景
gzip-z约 118MB18s3.5s通用首选,兼容性最好
bzip2-j约 96MB52s9s高压缩比、归档长期留存
xz-J约 78MB145s5s极限压缩,发布包分发
zstd-I zstd或独立命令约 92MB4s1.2s现代场景,速度与压缩比兼顾

看这张表就能明白为什么 gzip 能统治这么多年:它的压缩比虽然不是最优,但压缩和解压速度都在可接受范围内,而且几乎所有系统、几乎所有工具链都原生支持它。bzip2 的压缩比好一些,代价是压缩时间接近三倍,解压也不快,现在更多出现在一些历史项目的发布包里。

xz 的处境比较特殊。它的压缩比确实强,但单核压缩慢到令人发指,好处是解压速度还算正常(解压比压缩快得多是 LZMA 类算法的特性)。所以 xz 的正确用法是压缩端愿意等,解压端数量多——比如一个软件发布包,你打一次包,几万人下载解压,这时候多花几分钟压缩完全值得。

zstd 是近几年越来越常见的选择,它的定位很清晰:在压缩比接近 gzip 甚至更好的前提下,把速度提升一个数量级。如果在你的环境里zstd命令可用(主流发行版基本都能直接装),日常备份场景我会优先推荐它,尤其是配合多线程参数,能把多核机器的性能吃满。

1.3 后缀名是“处理链”的说明书,别乱改

.tar.gz这个后缀其实在告诉你一条完整的处理链:先tar打包,再gzip压缩。同理.tar.bz2、.tar.xz、.tar.zst分别对应后接 bzip2、xz、zstd。.tgz只是.tar.gz的缩写,.tbz2、.txz也是一样的简写,本质没区别。

理解这一点之后,一个很常见的坑就自动解释了:你把一个.tar.gz改名成.tar,tar -xf解压时大概率还是会成功,因为 GNU tar 有自动探测能力,会读文件头判断真实格式。但如果你用gzip -d去处理一个纯.tar文件,就会得到not in gzip format的报错——因为它读到的头几个字节根本不是 gzip 的魔数1f 8b。这个报错信息看起来吓人,其实是 gzip 在老老实实告诉你“这玩意我没法解”。

反过来,用file命令看一眼文件真实身份,是我排查这类问题的第一反应:

file backup.tar.gz # backup.tar.gz: gzip compressed data, from Unix, original size modulo 2^32 ...

它不依赖后缀,直接读文件头。遇到来路不明的压缩包,先file一下,比反复试参数高效得多。

2. tar 的字母组合到底怎么记才不翻车

2.1 五个核心字母的真实分工

tar -zcvf这串东西之所以难记,是因为它把“动作”和“附加行为”混在一起了。拆开看其实很规整:

  • c(create)创建归档,x(extract)解出归档,t(list)列出内容。这三个是互斥的主动作,一次只能选一个,且必须放在最前面。
  • f(file)指定归档文件名,后面必须紧跟文件名,这是最容易出错的位置关系。
  • v(verbose)打印过程,属于可选项,打包小文件时有用,处理几十万个小文件时反而拖慢速度、刷屏。
  • z/j/J是压缩链路的开关,只影响“要不要顺手压一下”。

记住一句话就够了:主动作 + 压缩方式 + 附加行为 + f 文件名。所以创建并 gzip 压缩是tar -czvf out.tar.gz src/,解压是tar -xzvf out.tar.gz,只看不拆是tar -tzvf out.tar.gz。这三个动作用熟了,日常 90% 的场景都能覆盖。

有个细节值得强调:-f必须在所有单字母参数的最后,因为它后面那个位置是留给文件名的。写成tar -cfv out.tar src/会被解析成“文件名是v”,然后out.tar和src/变成要打包的内容,结果就是你在当前目录下得到一个名为v的归档文件,里面装着out.tar和src。这个错误我第一次犯的时候找了好久,因为命令没报错,只是结果诡异。

2.2 打包时的路径处理,决定了解压会不会污染目录

这是tar最容易埋雷的地方,也是我认为最应该被反复强调的一点。打包时你写的路径长什么样,解压出来就是什么样。

假设当前目录是/home/dev/project,你执行tar -czf ../back.tar.gz .,那么归档里的路径全部是./xxx形式。之后在任何地方解压,内容都会平铺到解压时的当前目录,不会自动创建一个project目录。如果解压目标目录里原本就有同名文件,直接被覆盖。这就是“解压污染目录”的经典事故模型。

更危险的是用绝对路径打包:tar -czf /tmp/back.tar.gz /etc/nginx。GNU tar 出于安全考虑会剥掉开头的/,并且在解压时打印Removing leading '/' from member names的警告,实际解到当前目录下变成etc/nginx。如果你的当前目录恰好是/,那就真的往/etc/nginx里覆盖了。

我的习惯做法是永远用-C切换工作目录,用相对路径打包:

tar -czf /backup/nginx-20250101.tar.gz -C /etc nginx

这条命令的意思是:先cd /etc,然后打包nginx这个目录。归档里的顶层条目就是nginx/,解压时会老老实实创建一个nginx目录,不会散落一地,也不会碰系统路径。这个写法在脚本里用起来最稳,因为路径全部可控,不依赖脚本执行时的当前目录。

提示:如果你确实需要保留绝对路径(比如做整机备份),可以加-P(大写)参数,但要极其清楚自己在干什么。解压时同样需要-P,否则路径还是会被剥离。生产环境我基本不用这个开关。

2.3--exclude和-p:让备份包真正可用

打包目录的时候,总会有些东西不该进包:缓存目录、临时文件、.git这种体积大又没必要的元数据、还有正在写入的日志。--exclude就是干这个的,但它有个容易被忽略的规则——匹配的是归档内的路径模式,不是绝对路径。

tar -czf site-back.tar.gz -C /var/www site \ --exclude='site/cache' \ --exclude='site/*.log' \ --exclude='site/.git'

这三个排除项都写成相对-C之后那个根目录的形式,才能正确命中。我第一次用--exclude=/var/www/site/cache这种绝对路径写法时,排除完全没生效,包大了一倍多,排查半天才发现问题。

另外两个参数在“备份要能还原”这个诉求上很关键:

  • -p(--preserve-permissions):解压时保留原权限。普通用户解压时某些权限位本来会被 umask 影响,加上它更可控。
  • --numeric-owner:用数字 UID/GID 记录属主,而不是用户名。备份跨机器还原时这个非常重要——两台机器的用户名可能相同但 UID 不同,或者目标机器上压根没有那个用户。用数字记录,还原后再按需chown,比依赖用户名映射可靠得多。

打包时的权限记录是tar的默认行为(作为 root 执行时),所以在做系统级备份时,我会明确写成:

tar --numeric-owner -czpf /backup/system-$(date +%F).tar.gz -C /data app

这行看着啰嗦,但每部分都有理由:数字属主保证跨机还原,-p保证权限位,-C保证路径干净。

3. 解压才是坑最多的一步,多花十秒能省两小时

3.1 解压之前,先看里面有什么

这是我最想推给所有人的习惯:任何来源不明的压缩包,解压前先-t看一眼。它不产生任何副作用,只是把归档内的文件列表打印出来。

tar -tzf unknown.tar.gz | head -30 tar -tzf unknown.tar.gz | wc -l # 顺便看看有多少个文件

看什么?看顶层目录是不是只有一个(这样解压才干净),看有没有../这种试图往上跳的路径(这是经典的路径穿越手法,恶意归档会用它往你的上级目录甚至系统目录写文件),看有没有以/开头的绝对路径。GNU tar 默认会拒绝解出..路径,但老旧版本或某些第三方实现不一定,先看一遍成本极低。

如果只想解压归档里的部分文件,后面直接跟文件名模式即可:

tar -xzf logs.tar.gz --wildcards '*/app-*.log'

--wildcards让 tar 用通配符匹配成员名。注意 macOS 自带的 bsdtar 默认就支持通配符,不需要这个参数,脚本如果需要跨平台,可以先判断一下 tar 的实现类型。

3.2-C的两个用法,语义完全不同

-C在打包和解压时的含义是“切换目录”,但两个方向的效果要注意区分:

打包时tar -czf out.tar.gz -C /path/to src,-C作用于tar自身的工作目录,用来构造干净的相对路径。

解压时tar -xzf out.tar.gz -C /target/dir,-C指定解压到哪个目录。这里有个硬性前提:目标目录必须已经存在,tar不会帮你创建。写脚本时忘了mkdir -p,就会得到Cannot open: No such file or directory的报错。

mkdir -p /opt/release && tar -xzf release.tar.gz -C /opt/release

还有一个细节:-C的位置会影响它作用的成员范围。写成tar -xzf pkg.tar.gz -C /tmp a.txt -C /var b.txt是合法的,意思是a.txt解到/tmp、b.txt解到/var。日常用不到这么花哨,但知道-C是“对后续成员生效”的,能帮你理解一些奇怪的命令行为。

3.3 覆盖、属主、权限三个开关怎么取舍

解压时是否覆盖已有文件,tar的默认行为是直接覆盖,不问不提示。这个默认值在自动化脚本里很方便,在手工操作时很危险。想先确认再覆盖,可以加-k(--keep-old-files),遇到已存在的文件就报错跳过;想交互确认,用--confirmation。

关于属主,普通用户解压时无法把文件改为别人所有(这是内核权限限制,不是 tar 的问题),会看到Cannot change ownership的警告。这些警告在非 root 场景下是无害的,如果嫌刷屏可以加--no-same-owner明确关掉。

权限方面,root 解压时默认会还原归档里记录的权限,普通用户则受当前 umask 影响。做应用发布包时我倾向于在打包阶段就把权限定死,解压后统一跑一次修正:

tar -xzf app.tar.gz -C /opt/app --no-same-owner find /opt/app -type d -exec chmod 755 {} \; find /opt/app -type f -exec chmod 644 {} \; find /opt/app -name "*.sh" -exec chmod 755 {} \;

这套组合拳看着笨,但比指望tar帮你精确还原要可靠得多,尤其是包是从别的环境带过来的时候。

4. 跨平台传文件,zip 的编码坑几乎躲不掉

4.1 中文名乱码的根因不在 Linux 这边

tar系列是 Linux 内部交流的通用语言,但只要文件需要发给使用其他操作系统的同事,zip 就成了绕不开的格式。而 zip 的坑几乎都集中在一个地方:文件名编码。

zip 格式早期没有规定文件名用什么编码,历史实现里大量使用本地代码页(在中文环境下就是 GBK 系列)。而现代 Linux 的unzip默认按 UTF-8 解读文件名。结果就是解压出来一堆Êý¾Ý之类的乱码。这不是文件损坏,是编码解释错了。

判断方法很简单,看unzip -l的输出:

unzip -l package.zip

如果列表里中文全是问号或者乱码,基本可以确认是编码问题。较新版本的unzip提供了-O参数指定源编码:

unzip -O GBK package.zip -d ./out

-O需要unzip6.0 以上并且编译时带了相应支持,某些发行版的包可能没有。如果报invalid option -- O,还有两个备选方案:一是用7z x解压,7z 对编码的探测和兼容处理通常更宽容;二是用 Python 的zipfile手动指定编码,虽然麻烦但绝对可控。

4.2-l、-x、-d三个参数覆盖大部分日常

unzip的参数不多,常用的就三个,组合起来足够应付日常:

unzip -l pack.zip # 只看列表,不解压 unzip pack.zip -d /tmp/unpack # 解到指定目录 unzip pack.zip -x "*.log" -d /tmp/u # 排除指定文件

-d指定的目录不存在时unzip会自动创建,这点比tar -C友好。-x的排除是模式匹配,写"*.log"会把所有层级的日志都排掉。

还有一个很少人用但很好使的参数-n:只解压比目标位置更新的文件,等于做了一次增量同步。做静态资源更新时我用过几次,比全量解压再比对省事。

反向操作,在 Linux 上打 zip 包给别的平台用,建议加-r递归并显式处理编码:

zip -r -q package.zip ./docs

如果担心对方解压乱码,最稳妥的方式是打包前把文件名统一成 ASCII,比如用拼音或者编号,从源头绕开问题。这个建议听着土,但在跨团队协作里是最省沟通成本的做法。

4.3 7z 与其他格式:什么时候值得多装一个工具

7z(p7zip 包)值得装的理由有三个:一是编码兼容性好,二是压缩比通常优于 zip,三是它能同时处理几十种格式,遇到.rar、.iso、.cab这类不常见的东西不用再去找专门工具。

7z x archive.7z -o/opt/target # 注意 -o 后面不能有空格 7z a -t7z backup.7z ./data -mx=7 # 创建,指定压缩级别 7z l archive.zip # 查看列表

要留意的是-o这个参数在 7z 里必须紧贴目录名,写成-o /opt/target(中间有空格)会被当成两个独立参数,输出目录就变成了当前目录下的/opt/target字面量,非常反直觉。这个坑我被绊过,写脚本时特别容易忽略。

至于.rar,解压需要unrar或7z,创建则需要商业版工具。我的建议是能不用就不用:团队内部约定统一用 tar 系列或 zip,把格式收敛到两三种,比支持一堆格式省心得多。

5. 压缩率和速度怎么权衡,别拿 CPU 换那点空间

5.1 数据特性决定压缩效果,级别不是唯一变量

选压缩算法之前,先看你的数据是什么类型。这个判断比调压缩级别重要得多:

数据类型压缩效果建议
纯文本日志极好,常达 10:1 以上gzip 或 zstd,够用且快
源代码好,约 4:1 到 6:1gzip 默认级别完全够
已压缩格式(jpg、mp4、zip)几乎为 0别压,纯浪费时间
数据库导出 SQL很好zstd 或 xz
虚拟机镜像中等zstd 多线程,或干脆不压
二进制程序中等zstd,兼顾速度

最典型的浪费是对已经压缩过的数据再压一遍。图片、视频、.gz、.zip、.jar这些东西内部已经是压缩态,再套一层 gzip 只能省下 1% 到 2%,但 CPU 时间照样花。我做归档脚本时会先把这类后缀排除掉:

tar -czf media.tar.gz --exclude='*.jpg' --exclude='*.mp4' --exclude='*.gz' -C /data media

不过要注意,这不是绝对的。如果归档的目的是“把所有东西打成一个文件方便传”,那还是得打进去,只是可以选-I zstd -1这种最快档位,别用-9死磕。

5.2-1到-9的实测差距没你想的那么大

gzip 的压缩级别从-1(最快最松)到-9(最慢最紧),默认是-6。很多人习惯性写-9,觉得“反正能压更小”。实测下来,在一份 200MB 的文本日志上:

  • gzip -1:42 秒→ 注释:实际是 4.2 秒左右,压缩到 31MB
  • gzip -6:11 秒,压缩到 26MB
  • gzip -9:32 秒,压缩到 25MB

从-6到-9,压缩时间翻了三倍,体积只小了 4%。这 4% 在绝大多数场景下毫无意义,而三倍的 CPU 时间意味着你的备份窗口可能被拉长、机器负载被抬高。除非是在做一次性的、需要长期归档的数据,否则我基本只用默认级别。

xz 的情况类似但更极端。xz -9相比-6压缩时间可能翻倍,收益也就百分之几。而 xz 真正该用的场景是配合字典大小调整,-9e会把字典开到 64MB,对大文件的压缩比提升比单纯调级别明显,代价是压缩时占内存很凶,小内存机器上要谨慎。

5.3 多核闲着太可惜,pigz 和 zstd 的多线程

tar -czf这条链路有个先天限制:gzip 是单线程的。机器八个核,只有一核在干活,其余七个在围观。如果你的备份任务耗时很长,这是最值得优化的一环。

pigz(parallel gzip)就是解决这个问题的,输出格式与 gzip 完全兼容:

tar -cf - -C /data logs | pigz -p 8 > /backup/logs.tar.gz

注意这里我去掉了-z,改成tar -cf -把归档流输出到标准输出,再用管道交给pigz。这个“tar 输出到 stdout + 管道 + 外部压缩”的模式是万能公式,任何压缩工具都能用这种方式接进来:

tar -cf - -C /data src | xz -T0 -9 > out.tar.xz tar -cf - -C /data src | zstd -T0 -19 > out.tar.zst

-T0表示使用全部可用核心。解压时 zstd 和 pigz 同样支持多线程,tar -I pigz -xf out.tar.gz或者zstd -d out.tar.zst | tar -xf -,速度提升非常明显。

这套管道写法还有个额外好处:不产生中间文件。直接tar -czf在某些实现里会先写一个临时 tar 再压缩,磁盘空间紧张时管道形式更安全。

6. 落到真实场景:日志归档与备份脚本

6.1 按日期滚动打包日志的骨架

回到开头那个凌晨的场景。要避免历史重演,脚本得满足几个条件:按日期切分、打包后删源、保留最近 N 天、出错要能看出来。下面这个骨架我用了很久,改改就能落地:

#!/bin/bash set -euo pipefail LOG_DIR="/var/log/app" BACKUP_DIR="/backup/logs" KEEP_DAYS=30 YESTERDAY=$(date -d "yesterday" +%F) mkdir -p "$BACKUP_DIR" # 找出属于昨天那一批的日志文件 mapfile -t FILES < <(find "$LOG_DIR" -maxdepth 1 -type f -name "*.log" -newermt "$YESTERDAY 00:00" ! -newermt "$(date +%F) 00:00") if [ ${#FILES[@]} -eq 0 ]; then echo "no files to archive for $YESTERDAY" exit 0 fi tar --numeric-owner -czf "$BACKUP_DIR/logs-$YESTERDAY.tar.gz" -C "$LOG_DIR" "${FILES[@]##*/}" # 校验归档可读后再删源 if tar -tzf "$BACKUP_DIR/logs-$YESTERDAY.tar.gz" > /dev/null 2>&1; then rm -f "${FILES[@]}" else echo "archive verify failed, keep source files" >&2 exit 1 fi # 清理过期归档 find "$BACKUP_DIR" -name "logs-*.tar.gz" -mtime +$KEEP_DAYS -delete

几个设计点值得说明。set -euo pipefail让脚本遇到任何异常立刻停,避免带着错误往下跑。mapfile加find的组合比ls安全得多,文件名里带空格也不会出事。先校验再删源是最重要的一步,用tar -tzf通读一遍归档,能读通才说明包是完整的,这一步花的时间远小于从零重造日志的成本。

${FILES[@]##*/}这个写法是去掉绝对路径前缀,只保留文件名,配合-C "$LOG_DIR"保证归档里的路径是干净的相对路径。

6.2 不落地直接搬走:管道流式传输

服务器磁盘紧张时,“先打包到本地再传输”这个流程本身就会失败。用管道把归档流直接送到目标机器,全程不占本地磁盘:

tar -czf - -C /data recycle | ssh backup-host "cat > /backup/recycle-$(date +%F).tar.gz"

这里tar -czf -的-表示输出到 stdout,ssh 那头cat >直接写文件。整条链路只有一条数据流,不需要临时空间。

同样的思路可以做边打包边校验,或者传到对象存储:

tar -czf - -C /data recycle | tee >(sha256sum > /tmp/recycle.sha256) | ssh backup-host "cat > /backup/recycle.tar.gz"

tee加分进程替换,一份流同时喂给校验和传输,事后拿/tmp/recycle.sha256对一下就知道传输有没有损坏。这套用法我第一次见的时候觉得有点炫技,但用在不稳定的网络环境里确实省事。

要注意的是:管道传输没有断点续传,中间断了就得重来。大文件场景下,宁可用rsync先传原目录,再在目标机器上打包。

6.3 分卷归档与大文件校验

超过单盘容量的归档需要分卷。split是最直接的方案:

tar -czf - -C /data bigdir | split -b 2G -d - /backup/bigdir.part-

-b 2G指定每卷大小,-d用数字后缀,-表示从标准输入读。合并时cat /backup/bigdir.part-* | tar -xzf -即可。

tar自身也支持分卷(-M加--tape-length),但它依赖磁带设备语义,在普通磁盘上用起来很别扭,我不推荐。split加cat的组合更通用,而且分卷文件可以用sha256sum单独校验,定位坏卷更方便。

校验这一步不能省。归档完立刻生成校验和,比事后发现包坏了要幸运得多:

sha256sum /backup/bigdir.tar.gz > /backup/bigdir.tar.gz.sha256 sha256sum -c /backup/bigdir.tar.gz.sha256

大文件用sha256sum会花点时间,但这个过程能顺带把整个文件读一遍,某种程度上也能暴露磁盘读取错误。如果追求速度,xxh128sum之类的新哈希工具快得多,只是生态支持还不算普及。

7. 报错排查:那些绕不开的坑和它们的真面目

7.1not in gzip format和Unexpected EOF

这两个报错出现的频率极高,但原因完全不同。

gzip: stdin: not in gzip format通常意味着你给 gzip 的东西根本不是 gzip 数据。最常见的情况是把纯.tar当成.tar.gz去解,或者下载的文件其实是 HTML 错误页(服务器返回了 404 页面,但文件名还带着.tar.gz)。动手前先file一下、head -c 100看一眼开头是不是文本,两秒钟就能定性。

tar: Unexpected EOF in archive是另一种性质:文件头是对的,但读到一半数据没了。典型原因是下载中断、磁盘写满、传输过程中被截断。这时候文件大小往往对不上,用ls -l和源站声明的体积比一比就能确认。

还有一种隐蔽的情况:归档本身是完整的,但里面某个成员的数据块有问题,报错会是tar: Skipping to next header或者gzip: stdin: invalid compressed>df -i

如果IUse%接近 100%,说明 inode 用光了,无法再创建新文件。日志目录里几十万个小文件是典型的 inode 杀手,tar打包时如果输出目录在同一分区,中途就会失败。判断方法是df -h有余量但df -i满了,这时候清理小文件比清理大文件更有效。

还有一种更隐蔽的:打包输出到了被别人rm掉但仍有进程持有的文件上,空间不会释放。lsof +L1能列出这类被删除但仍占用的文件,重启对应进程或者truncate一下就能回收。

7.3 打包途中文件变化,报file changed as we read it

这个警告在打包活跃日志目录或者运行中的数据库目录时几乎必然出现:

tar: app/current.log: file changed as we read it

它的含义很直白:tar读这个文件的过程中,文件的 size 或 mtime 变了,归档里的内容可能是不完整的。对于日志这种持续追加的文件,这个警告基本可以忽略——你拿到的是打包那一刻的快照,语义上是合理的。

但如果是数据库文件,这个警告就必须重视了。对着运行中的数据库数据目录直接tar,拿到的东西大概率不可用,因为文件内部状态不一致。正确做法是走应用层的导出(比如数据库自带的 dump 工具),或者先在文件系统层面做快照再打包。

如果确认可以忽略,加--warning=no-file-changed让脚本不被警告干扰,但要注意tar的退出码可能是 1,脚本里判断成功不能只看$? -eq 0,得允许退出码 1:

tar -czf out.tar.gz -C /data app || [ $? -eq 1 ]

这个写法有点丑但很实用,是我从多次“脚本误判失败导致重复打包”的教训里总结出来的。

7.4 权限拒绝与符号链接的两种处理方式

以普通用户身份打包系统目录,必然遇到Permission denied。tar的行为是打印错误、跳过该文件、继续往下走,最后退出码非 0。如果这个目录对你很重要,跳过就意味着备份不完整,不能不管。

处理方式有两种思路。一是明确用 root 执行,这是最直接的。二是在脚本里加--ignore-failed-read,让tar不因为读失败而返回错误码,但这样等于承认备份不完整,得配合日志记录哪些文件被跳过了:

tar -czf out.tar.gz -C /data app 2> >(grep -i "permission denied" >> /var/log/backup-skip.log)

符号链接的处理是另一个容易踩的点。tar默认不跟随符号链接,只把链接本身存进去,解压时恢复成链接。这个语义通常是对的,因为跟随链接可能会把链接指向的整个大树都打进去,体积失控。如果确实需要跟随,用-h(--dereference),但要清楚后果。

--hard-dereference则是针对硬链接的,默认tar会保留硬链接关系(多个路径指向同一 inode),解压时还原。如果你希望每个路径都是独立文件,加这个参数。

最后分享一个小技巧:tar的--checkpoint和--checkpoint-action可以在处理大归档时输出进度,比干等着强:

tar -czf big.tar.gz -C /data bigdir --checkpoint=10000 --checkpoint-action=dot

每处理一万个文件打一个点,虽然简陋,但能让你知道它还在动,不是卡死了。这个参数在处理几十万小文件的目录时特别有用,我现在的备份脚本里基本都带着它。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 21:35:46

领域驱动设计与AI融合:本体论建模实践

1. 领域驱动设计与AI时代的碰撞领域驱动设计&#xff08;Domain-Driven Design&#xff0c;简称DDD&#xff09;作为一种经典的软件架构方法论&#xff0c;在过去二十年里深刻影响了企业级应用的开发模式。其核心思想是通过建立统一的领域模型&#xff0c;将业务概念转化为软件…

作者头像 李华
网站建设 2026/10/27 21:34:54

MySQL 8.0认证插件错误解决方案与原理详解

1. 问题现象与背景解析 当你在MySQL客户端或应用程序中看到"Plugin mysql_native_password is not loaded"错误时&#xff0c;这通常意味着MySQL服务器无法加载传统的密码验证插件。这个错误在MySQL 8.0及以上版本中尤为常见&#xff0c;因为从8.0开始&#xff0c;默…

作者头像 李华
网站建设 2026/10/27 21:34:54

马尾辫造型全攻略:从高度定位到细节处理的高阶教程

1. 为什么马尾辫值得单独写一篇&#xff1a;从"随手一扎"到"正式造型"马尾辫&#xff08;ponytail&#xff09;大概是最被低估的发型了。我平时被问到最多的发型问题&#xff0c;往往不是复杂的编发或烫卷&#xff0c;反而是"怎么扎马尾才不土"。…

作者头像 李华
网站建设 2026/10/27 21:34:31

impeccable CLI开发范式:零依赖、npx直跑、跨平台一致的现代命令行设计

1. 项目概述&#xff1a;这不是一个工具&#xff0c;而是一套可复用的 CLI 开发范式“impeccable”这个词在英文里本意是“无懈可击、无可挑剔”&#xff0c;但放在当前开发者生态中&#xff0c;它已悄然演变为一个高度特化的技术代号——特指一类以极简设计、零依赖、强可移植…

作者头像 李华
网站建设 2026/10/27 21:33:41

Text-to-CAD深度解析:自然语言如何生成可编辑的STEP模型

先说个有意思的事。前阵子有个做机械设计的群友发了个链接问我&#xff1a;“现在AI都能直接生成CAD了&#xff0c;我们学SolidWorks还有啥用&#xff1f;”我看了一下&#xff0c;那是个text-to-cad工具&#xff0c;输入一段话就能出STEP文件。我当时第一反应是&#xff1a;这…

作者头像 李华
网站建设 2026/10/27 21:33:38

Java高并发接口优化实战与性能提升策略

1. Java接口高并发优化方案概述在当今互联网应用中&#xff0c;高并发场景已经成为常态。一个典型的电商系统在促销期间可能面临每秒数万甚至数十万的请求量&#xff0c;这对Java接口的性能提出了严峻挑战。我经历过多次618和双11大促的备战&#xff0c;深刻体会到未经优化的接…

作者头像 李华