1. 别再背参数了先弄懂 tar 到底在做什么我见过太多人包括早年的我自己一上来就死记硬背tar -czvf好像这五个字母是什么咒语一样。背下来倒是容易可一旦遇到tar -xvf、遇到.tar.gz和.tar.bz2混着用、遇到要排除某个目录、要解压到指定路径人立马就懵了。事实上 tar 这个名字非常直白——它来自 Tape Archive磁带归档。几十年前人们需要把一堆文件按顺序写进一盘磁带里于是就有了 tar它把所有文件首尾相接地拼成一个文件这个文件本身不压缩只是打包。真正负责压缩的是它内部调用的 gzip对应后缀.tar.gz、bzip2对应.tar.bz2、xz对应.tar.xz这些工具。把这件事想通之后你再看 tar 的所有参数逻辑一下子就通了tar 只做三件事把多个文件拼成一个文件ccreate把一个文件拆回原来的多个文件xextract查看这个文件里面装了什么tlist。它为什么快因为它不压缩只是搬运和拼接。压缩工作是交给别的程序干的。为什么 tar 有那么多看起来差不多的参数因为同一份代码要兼容磁带时代的命令格式老式不带-和后来大家习惯用的-格式比如tar czvf和tar -czvf其实是同一回事还得兼容各种压缩算法。所以别急着记先把下面这三条命令模型刻在脑子里剩下的全是细节意图老式写法新式写法说明打包不压缩tar cf backup.tar folder/tar -cf backup.tar folder/c 创建f 指定文件名打包并 gzip 压缩tar czf backup.tar.gz folder/tar -czf backup.tar.gz folder/z 表示调 gzip解包tar xf backup.tartar -xf backup.tarx 解包f 指定文件名注意f 这个参数必须放在最后面后面紧跟归档文件名。因为f的意思是接下来的这个参数是文件名如果你写成tar -fzcv或者把f放到中间tar 就会把zcv当文件名处理立刻报错。这是我见过新手踩得最多的坑没有之一。还有一点非常重要tar czf老式写法里那个z并不是必须写在c后面的。真正让 tar 自动识别压缩格式的是它看到文件名后缀是.gz之后自己调用的。所以你在新版 tarGNU tar 1.20 以后里写tar -czf和写tar -cf然后把结果用 gzip 单独压一遍效果完全一样。理解了这个机制下面我讲的所有进阶内容你都会觉得顺理成章。2.这句话请收回。上面我写下面我讲的所有进阶内容你都会觉得顺理成章——这就有甩术语的嫌疑了。更准确的说法是本节先讲最常用的命令组合不需要理解压缩算法也能立刻上手。2. 最常用的五组命令照着抄就行这部分是给现在就要用的人准备的每条命令我直接标注了适用场景。你先把这些练熟再去看后面的原理和坑会轻松很多。2.1 打包一个目录tar -czf project_backup.tar.gz project/这条命令的作用是把project/整个目录打包并压缩生成project_backup.tar.gz。个人建议哪怕你只是临时备份也务必加上-c和-z两者搭配才能在后续让你少踩两个坑只要加z输出的.tar.gz就能被绝大多数图形界面解压工具直接识别只要加c遇到已存在的同名归档文件tar 会直接覆盖而不是追加或报错老式磁带的语义是追加新用户千万别被商场的旧教程带偏。能不加v吗可以。v是 verbose也就是把当前正在处理哪个文件打印到屏幕。加了v能让你看到进度但对于几十万个小文件的项目光是打印文件名就能让命令慢不少。所以备份前我一般不加v只在解压或查看时加。2.2 解包到当前目录tar -xzf backup.tar.gzx是解包z是按 gzip 解压f指定文件。解出来的目录结构与打包时完全一样直接在当前目录下铺开。这是最常用的一条命令唯一的坑是它会把所有文件解压到当前目录。如果你在/home/user下执行而打包时路径是/home/user/project那你会得到一个project/目录。如果你在/根目录执行它甚至可能在你意想不到的位置铺开文件非常危险。我一会儿专门讲路径陷阱。2.3 解包到指定目录tar -xzf backup.tar.gz -C /data/restore-C是 change directory先切到目标目录再解压。这条命令比上面那条安全得多因为它给了你明确的落点控制。注意-C的目录必须已经存在tar 不会自动创建多级目录。2.4 查看归档文件内容不解压tar -tzf backup.tar.gzt是 list 的古老缩写它会列出归档内的所有路径但不会解压任何文件。当你拿到一个陌生的.tar.gz想看看里面结构再决定怎么解压就用这条。再加v还能看到权限和文件大小。2.5 追加文件到已有归档tar -rzf backup.tar.gz newfile.txtr是 append 追加。这条只对未压缩的 tar有效如果你对.tar.gz执行会得到Cannot update compressed archive错误。原因也很简单压缩格式决定了数据必须按顺序解码没法在中间插入一段。所以用 gzip 压缩过的归档不支持追加。要追加就得先解压、追加、再压回去或者干脆不要用压缩格式做增量备份后面我会讲为什么以及怎么做。提示如果你用tar -tf backup.tar.gz看到的是path/to/file这种带多级目录的路径那么解出来的时候目录结构也会一样。千万别假设 tar 会把里面的文件摊平它不会。3. 参数拆解与三个易错点为什么你的命令总是差一点很多教程会把 tar 的参数表直接甩出来然后说记住就行。我不赞成因为 tar 的参数设计里有几个反直觉的地方你只要理解了这几个反直觉整张参数表就自然记住了。3.1 你只需要分清的 8 个功能参数tar 的功能性参数其实不多常用的就这 8 个参数含义一句话说明-ccreate打包-xextract解包-tlist列出内容-rappend追加文件仅未压缩格式-uupdate比归档新的文件才会被加进去-ddiff比较归档和磁盘上的差异-zgzip调用 gzip 压缩/解压-jbzip2调用 bzip2-Jxz调用 xz-ffile指定归档文件名-Cdirectory指定工作目录--exclude排除排除指定文件或目录你会发现压缩参数z/j/J和功能参数c/x/t是相互独立的两维。所以任何快捷键式的写法比如tar -xjf也可以拆解为x解包j用 bzip2f文件。这样理解哪怕哪天遇到tar -xJfJ 大写xz 格式你也不会慌——因为它在逻辑上和tar -xjf完全同构。3.2 易错点一-C的位置很多人把-C写在文件名后面# 错误示范 tar -xzf backup.tar.gz -C /data/restore # 这样是对的 # 错误示范把 -C 放在文件名前 tar -xzf -C /data/restore backup.tar.gz # 大部分版本会报错或顺序不受控虽然 GNU tar 的解析器做了一定兼容但在不同系统上行为不一致。更稳妥的做法是把-C放在紧挨着文件名之后的任意位置或者干脆在命令最前面统一写tar -xzf最后统一写-C 目录。格式统一才不会在换不同发行版/不同 Unix 版本时翻车。3.3 易错点二忘了处理隐藏文件默认情况下tar czf a.tar.gz folder/会连同folder/.config这类隐藏文件一起打进去只要你指定的是目录而不是目录里的通配符。但如果你用的是tar czf a.tar.gz /path/folder/*这种写法Shell 会在 tar 运行之前把*展开成具体文件名列表隐藏文件因为默认不被*匹配天然就会被漏掉。这个坑极其隐蔽。解决方法有三个永远用目录名而不是目录/*来打包如果需要选择性地打一部分用--exclude在 tar 内部排除而不是在 tar 外部用 Shell 通配符如果确定要包含隐藏文件可以用tar --include.* ...但说实话日常用第一种最稳。3.4 易错点三绝对路径与--strip-componentstar -xzf backup.tar.gz -C /data/restore --strip-components2如果归档内部路径是home/user/project/file.txt加上--strip-components2后解出来就变成project/file.txt即剥掉最前面的两层目录。这在打包时带了完整路径但解压时不想保留那么深的场景非常有用。但要特别注意tar 在打包时用的是相对路径还是绝对路径直接影响解压行为。用绝对路径打包的归档比如tar -czf b.tar.gz /home/user/project/在解压时会默认把文件放到绝对路径下这对备份来说是灾难性的。所以我在下文专门讲打包时如何正确使用路径。4. 打包时路径怎么选这是新手和熟练工的分水岭tar 打包时的路径写法决定了你解压出来的文件夹结构。这里面的逻辑很简单但影响巨大值得单独一节来讲。4.1 用cd控制相对路径假设你有一个项目目录/home/user/work/project/里面有src/、docs/、package.json等。如果你在/home/user/work下执行tar -czf project.tar.gz project那么归档内的路径是project/src/...解压到你指定的任何目录后都会得到一个project/文件夹。这是最理想的——因为你明确知道解压后会落在哪里。如果你在/home/user下执行tar -czf project.tar.gz work/project那归档内路径变成work/project/...解出来的时候会多出work/两层空目录。不是错但十有八九不是你想要的效果。所以我的习惯是先cd到目标目录的父目录然后用父目录下的目录名来打包。这样归档路径干净解压出来也干净。4.2 打包当前目录的正宗写法如果你已经cd到了项目目录里想把当前目录内容全部打包新手常写成tar -czf ../project.tar.gz . # 注意结尾是点这里结尾的点代表打包当前目录下所有内容。值得留意的是它打包出来的路径是./src/...这样带./前缀的。解压时不会有额外目录层级文件会直接铺在目标目录下。但这么做有一个隐患——如果你在打包命令里把归档文件也放在了当前目录比如tar -czf project.tar.gz .那么project.tar.gz本身也会被打进归档里形成一个自己装自己的递归。tar 会在打包时警告project.tar.gz is the same as the archive然后跳过它但你极有可能得到一个少了点东西而不自知的归档。正确做法是归档文件放在当前目录之外或者用第 4.3 节的排除选项。4.3 使用--exclude排除不必要的目录排除最常用的两个场景node_modules和.git。tar -czf project-clean.tar.gz \ --excludenode_modules \ --exclude.git \ -C /home/user/work project注意--exclude的匹配规则很有意思它匹配的是归档内的路径模式不是磁盘上的绝对路径。简单的名字node_modules就能匹配任意层级的node_modules目录。如果想精确排除某个深路径的子目录可以写--excludesrc/temp这样相对的路径模式。提示--exclude必须在指定源路径比如最后的project之前书写GNU tar 对命令行选项的顺序有严格要求。把 exclude 放在最前面、把文件源放在最后基本不会踩坑。4.4 备份时的最小可用方案如果你只是定期备份某个项目的源码不需要包含依赖和版本库历史我建议你直接把这行存成脚本cd /home/user/work \ tar -czf /data/backups/project-$(date %F).tar.gz \ --exclude.git \ --excludenode_modules \ project$(date %F)会生成2024-11-15这样的日期字符串让备份名自带时间戳。配合 cron 定时执行就完成了一个最原始但足够可靠的自动备份方案。别小看它——在没有外部存储、不需要多版本管理的场景这一条命令比很多备份软件都干净。5. 解压时的经典场景临时目录、选择性解压、权限问题这一节聚焦几个高频解压场景每一个都有讲究。5.1 永远先看包内结构再动手我拿到一个陌生归档第一件事永远是tar -tzf whatever.tar.gz | head -50head -50限定了只看前 50 行。为什么因为有些归档包含几千个文件全部列出来毫无意义你只需要确认它第一层目录是什么、有没有奇怪的前缀路径。这一步能避免至少 80% 的解压惨案——比如你明明想解到project/结果解出了一堆散落的文件。如果看到的第一层路径确实干净例如project/再执行解压tar -xzf whatever.tar.gz -C /data/restore等解压完/data/restore/project/就会出现。5.2 只提取归档里的某几个文件如果你的归档里包含很多文件但只需要其中一两个比如只需project/config/default.jsontar -xzf whatever.tar.gz project/config/default.json -C /data/restore注意tar 会保留这个文件在归档内的完整路径。如果归档内路径是project/config/default.json那么解到/data/restore下会自动创建project/config目录结构。如果你只想要文件内容而不想要目录结构比较费劲可以用--strip-components再配合文件名来操作或者解出来后自己mv。对于大多数场景保留目录结构反而是个好事。5.3 权限问题解出来文件权限/属主不对tar 会保留文件的权限位permission bits所以从归档解出来的脚本可能依然是可执行的也可能依然只读。如果你遇到解出来文件无权修改原因多半是有个目录/文件在打包时写了只读权限。如果是属主问题——明明你用自己的账号解压但解出来属主是1000:1000这种数字——那是因为打包时记录的是原机器的 uid/gid而当前机器没有对应用户。处理方法取决于需求如果只是临时使用不管它属主数字对你没影响如果需要强制改为当前用户解压后批量执行一步sudo chown -R $USER:$USER /data/restore/project这里不建议用 tar 的--owner和--group参数直接解压时改属主因为你可能并没有 root 权限。相比之下解压后统一chown -R是最简单可靠的做法。5.4 解压时不覆盖已有文件有时重复执行解压脚本希望旧文件保留、新文件别覆盖tar -xzf back.tar.gz -C /data/restore --keep-old-files这个参数会让 tar 遇到已存在文件时跳过打印File exists警告但不中断整体解压。对应的反方向参数是--overwriteGNU tar 默认是静默覆盖。日常写脚本时我习惯加--keep-old-files防止误覆盖等确认没有问题再改回默认。6. 增量备份、加密与跨平台日常不会教你的进阶玩法终于到进阶部分了。前半部分所有知识如果你能熟练运用已经能覆盖 90% 以上的场景。剩下这 10%才是让 tar 从能用上升到好用的关键。6.1 增量备份第一次全量之后只打变化部分tar 的--listed-incremental参数可以在归档旁边生成一个快照文件记录哪些文件在本次归档中出现过。下次备份时它会拿磁盘现状与快照对比只有新增或被修改的文件会被打包。来看演示。第一次全量备份tar -czf backup-20241115.tar.gz \ --listed-incrementalbackup.snar \ /home/user/project第二次增量备份比如到了 20241116tar -czf backup-20241116.tar.gz \ --listed-incrementalbackup.snar \ /home/user/project注意backup.snar是快照文件不是归档文件千万别混。目录结构如下backup.snar # 快照 backup-20241115.tar.gz # 全量备份 backup-20241116.tar.gz # 增量备份只包含新增/修改的文件恢复时先解全量包再依次解每个增量包按时间顺序tar -xzf backup-20241115.tar.gz -C /data/restore tar -xzf backup-20241116.tar.gz -C /data/restore这种方案的优点是纯 tar 实现不需要额外软件缺点是每恢复一个新机器必须 build 全套顺序包容错性不如专门备份软件高。所以更稳妥的策略是定期做一次全量比如每周日全量、平时增量。6.2 归档内文件被改动后的追加问题第 2.5 节里说过 gzip 不支持追加。如果你确实需要追加文件到已打包的.tar.gz正确姿势是解压到临时目录、追加、打包回去mkdir -p /tmp/untar_add \ tar -xzf old.tar.gz -C /tmp/untar_add \ cp newfile /tmp/untar_add/ \ tar -czf old-new.tar.gz -C /tmp/untar_add .但这种事做一次两次还好做多了你会疯狂后悔当初没用未压缩格式。因此我在处理高频更新的临时归档时会直接选择tar -cf tmp_archive.tar folder_that_changes/ # 不压缩 tar -rf tmp_archive.tar newfile # 追加等到需要归档存储时再统一压缩一次。这比反复解压、再打包高效得多。6.3 加密应用tar openssl 的灰常好用组合tar 本身不做加密。要加密就把它交给 openssl 或 gpg。如果你只是想给备份加一层基础保护openssl 最简单加密打包一条命令完成tar -czf - /home/user/project | openssl enc -aes-256-cbc -pbkdf2 -salt -out project.tar.gz.enc解密并解压一条命令完成openssl enc -d -aes-256-cbc -pbkdf2 -in project.tar.gz.enc | tar -xzf - -C /data/restore第一个命令里的-cf -是把归档流输出到 stdout那个-代表标准输出然后管道传送给 openssl 加密成文件。第二个命令则是反过来解密成标准输出再管道给 tar 解压。这个方案的好处是没有任何额外依赖——openssl 在几乎所有 Linux 系统上都预装。但它也有弱点如果你丢失了口令备份基本等于报废。另外建议使用-pbkdf2而不是老的-md md5后者现在被认为不安全。gpg 方案安全层级更高但命令更长不展开。6.4 tar 在 Windows/macOS 下的常用差异虽然标题没有明确平台但实际工作环境往往是混合的。我简单提三个最容易踩的点macOS 自带的 tar 是 BSD tar功能比 GNU tar 少不支持--strip-components的部分旧版本行为有差异。mac 用户建议brew install gnu-tar装完命令名是gtar。Windows 10/11 自带的tar.exe已经能解压最常见的.tar.gz但在处理长路径、特殊字符比如中文文件名时可能报错更稳妥的是用 7-Zip 或 WSL。在两类平台上路径分隔符不同。如果归档内路径是/分隔Unix 风格Windows 下用tar.exe一般也能兼容处理反过来在 Linux 下解压 Windows 打包出来的 tar 反而更没压力。6.5 性能对比什么时候用 gzip、bzip2、xz压缩算法压缩速度压缩比越小越好常见后缀使用建议gzip最快一般.tar.gz日常首选均衡bzip2慢较好.tar.bz2需要更高压缩比且不着急xz最慢最好.tar.xz生产发布包压缩比优先不压缩极快无.tar临时归档或频繁追加如果你的归档里有大量已经压缩过的文件比如.jpg、.mp4再压一遍 gzip 收益很低纯打包即可。如果在高密度文本项目里追求极致压缩比xz -eextreme 模式可以把压缩比再提高几个百分点但压缩耗时成倍增加。我自己在发布软件包时用 xz日常备份用 gzip临时归档用未压缩 tar。7. 常见报错的完整排查链路从现象到根因报错是学命令最快的方式但网上查到的答案往往是零散的。我把自己常遇到的报错整理成一个排查链路按这个顺序走几分钟内基本能找到原因。7.1Error opening archive file或Cannot open: No such file or directory这通常不是你权限不够而是归档文件路径拼错了或者当前目录并不是你以为的目录。处理链路是用pwd确认当前所在目录。用ls -lh判断文件名和实际文件是否一致注意大小写和后缀。确认不是软链接失效——如果你是通过软链接访问归档文件软链接本身指向的路径可能已经不存在。很少见但确实存在的情况是归档文件本身损坏gzip: 文件已损坏。这种直接重新获取备份。7.2gzip: stdout: No space left on device这个报错看起来像是写出了问题但根因往往是输出目录所在分区满了。按下面顺序排查df -h .看当前分区的剩余空间。df -h /tmp看临时目录的情况。为什么tar 有时会在/tmp创建临时文件即使你输出到了别的分区。用du -sh看看是不是有一两个超大文件先占据了空间。如果空间确实不够先用du -sh * | sort -h找出最大的文件清理后再重试。7.3Cannot update compressed archive这就是我在第 2.5 节讲过的压缩归档不支持追加。如果你真的需要追加方法 A解压、追加、再压缩步骤见 6.2。方法 B如果原归档是.tar未压缩直接tar -rf追加后再整体压缩。方法 C以后做这类定期更新的归档时不要总是一起压缩。很多备份软件之所以用未压缩 tar 做中间格式就是出于这个原因。7.4Message: Cannot open: Permission denied权限报错有三种常见场景需要区分对待场景表现解法读归档文件没权限查看/解压都报 Permission deniedchmod r 归档文件或换文件属主写入目标目录没权限解压到/root或别的系统目录时报错用普通用户目录或sudo -u指定用户解压归档内文件权限特殊解压过程中某些文件跳过看是不是文件权限位如 000导致的解压后chmod -R urwx...我踩过最深的坑是第三种某次从服务器拉下来的归档里有个目录权限是700而且是 root 所有。在普通用户下解压时那一层目录就被跳过了我看到命令行提示跳过字样还以为只是个别文件结果整个子目录都丢了。所以解压完成后建议随手find /data/restore -maxdepth 3 | head -30检查第一层内容是否齐全别等到用时才发现。7.5 解压出来是乱码文件名如果归档是在旧系统上打包——尤其是用非 UTF-8 编码打包的中文文件名——解压出来后文件名是乱码多半是编码未转换。在 Linux 下可以尝试用convmv批量修正convmv -r --notest -f gbk -t utf-8 /data/restore/project--notest表示真正执行重命名而不是干跑测试。如果没有 convmv可以用apt install convmv或yum install convmv安装。这个场景在跨国协作里并不少见遇到了别慌。8. 我的日常脚本与真实体会最后分享几个我实际用在生产环境里的脚本片段以及踩过坑后的血泪经验。这些内容不是学术层面的优化而是每次出现问题后我不断往脚本里加的防护措施。8.1 带日志的定期备份脚本#!/bin/bash SRC_DIR/home/user/work/project BACKUP_BASE/data/backups SNAPSHOT/data/backups/project.snar cd $SRC_DIR || exit 1 # 增量备份并用 tee 同时写日志和屏幕 tar -czf $BACKUP_BASE/project-$(date %F).tar.gz \ --exclude.git \ --excludenode_modules \ --exclude*.tmp \ --listed-incremental$SNAPSHOT \ . 21 | tee -a /var/log/backup.log # 备份完成后立即可视化确认归档大小 ls -lh $BACKUP_BASE/project-*.tar.gz | tail -5这个脚本我刻意加了几个细节cd $SRC_DIR而不是在脚本外指定源路径为了拿到干净的./xxx路径tee -a让日志既进文件也上屏幕排查时就不用反复重新跑一遍每次备份完成后列一下最近几个归档的大小直观确认这次增量是否异常小/异常大——异常小可能是漏了文件异常大可能是意外包含了缓存目录。8.2 自动清理不再需要的归档# 保留最近 7 天的归档其余删除 find /data/backups -name project-*.tar.gz -mtime 7 -deletefind -mtime 7是按修改时间筛选-delete直接删除。这条命令配合 cron 每天跑一次等效于一个早期自动清理策略。如果你有更强的诉求比如保留每周一份、每天一份用date控制一下命名即可。8.3 我在 tar 上栽过的最大跟头几年前我给一个模拟项目做全量备份用了这样一条命令tar -czf /home/user/work/project/backup.tar.gz /home/user/work/project结果你猜怎么着归档文件被保存到了被备份目录内部tar 在递归遍历时发现backup.tar.gz正在被写入于是停下来警告backup.tar.gz is the same as the archive并且跳过了它。我当时没注意警告按时长判断备份完成了。直到恢复时才发现归档内少了一个刚生成的报表文件而这个报表恰好就放在备份前半小时。从那以后我的铁律是归档输出路径必须与源目录不在同一层级或者是明确的外部路径比如/data/backups。如果非要放在同一目录也必须用--excludebackup.tar.gz主动排除绝不要把命运交给 tar 自己去猜。8.4 关于 tar 的选择性结论tar 不是最智能的备份工具也不是最高效的压缩工具更不是最安全的加密工具。但它有一个无可替代的优点几乎每一台 Unix/Linux 机器上都预装行为高度一致不存在额外的依赖管理问题。在许多不需要图形界面、不需要增量版本管理、不需要上传云端的场景里一两行 tar 命令比装一个备份软件干净得多。我的看法是如果你只需要备份一个目录、发布一个压缩包、给同事传递一组文件那么花三十分钟把 tar 常用参数吃透比花半天研究专用备份工具的配置更值得——因为后者往往在计划任务、日志轮转、存储策略这些环节上反而需要投入更多维护精力。而 tar 命令就像一把瑞士军刀虽然不如专业钳子好用但它带在身上永远不亏。最后再说一个我自己坚持的小习惯脚本里凡是出现 tar优先写全参数名而不是缩写比如--listed-incremental胜过-g--exclude胜过-X。牺牲一点输入速度换来的是半年后重新看脚本时每一行命令都能被同事直接读明白。命令行工具的便捷性永远排在可维护性之后——这是我用所有命令行工具换来的最贵的一条经验今天一并写给你。
阅读完成 · 觉得有帮助?