坦白说,用了这么多年Linux,tar大概是除了cd和ls之外,我敲得最多的命令之一。不管你是刚接触命令行的新手,还是天天跟服务器打交道的老人,tar都是绕不过去的基础工具。它负责把一堆文件打包成一个归档文件,也能从归档里把文件还原出来,配合上压缩算法之后,传输、备份、分发都靠它。
很多教程把tar讲得太零碎了,网上一搜全是“tar -czvf是什么意思”之类的碎片答案,真正遇到问题的时候还是不知道怎么组合。这篇我打算从最基础的用法一路讲到处理大规模数据时的高阶技巧,全程用实操案例说事。适合刚入门的读者一步步跟练,也适合有经验的开发者查漏补缺。
1. 先搞清楚tar到底在干什么
1.1 打包和压缩是两回事
很多人最早接触tar的时候,都以为它是个“压缩命令”,其实严格来说它是个“归档命令”。归档的意思是,把多个文件和目录合并成一个单独的文件,方便传输和存放。而压缩是在归档的基础上,用算法把文件体积变小。
用一个生活化的类比来说,把衣服叠好装进一个行李箱,这个过程是归档;用真空压缩袋把棉被抽气压扁,这个过程才是压缩。tar负责装箱,gzip、bzip2、xz这类工具负责抽气。它们俩经常一起出现,因为tar内置了调用压缩工具的参数,于是大家习惯性地把tar当成一个压缩命令来用。
理解这个区别很重要,因为它决定了你遇到一些诡异问题时的排查方向。比如说你写了一个tar命令去解压某个文件,结果提示“compressed with unknown algorithm”,这时候如果你知道tar本身不认识这个压缩格式,需要指定对应的解压参数,就不会一头雾水。
1.2 为什么tar在Linux生态里地位这么高
Unix系统的哲学是“每个程序只做好一件事”,tar和压缩工具的分工协作,正是这种哲学的体现。在Linux世界里,几乎所有的软件源码包都以.tar.gz或.tar.xz的形式分发,系统初始化脚本里的备份逻辑也大量依赖tar。更重要的是,tar可以保留文件的权限、属主、时间戳等元信息,这在备份和迁移场景里是硬需求——你用zip压缩再解压,可能所有文件都变成了当前用户的权限,但用tar打包,解压出来还能保持原来的权限和属主信息。
另外,tar命令有一堆非常灵活的参数组合,可以直接接管道、远程传输、增量备份,这些能力叠加起来,让它在自动化场景里几乎无法替代。学会了tar,你的Linux技能树才算真正点了基础分支。
1.3 本机环境说明
后面所有示例在我本地验证过,环境是CentOS 7.9和Ubuntu 20.04双系统,GNU tar版本是1.29/1.30。大部分命令在macOS上也能直接用,但还是有一点区别,具体差异我在对应位置会提醒。
2. 最常用的参数组合:看这一节就够了
2.1 记住这几个操作用的字母
tar的参数可以分成两类:一类是你要执行的操作,一类是你要附加的选项。操作类的字母有几个,请你重点记住这几个:
-c:创建归档,也就是打包。-x:解包,把归档拆出来。-t:列出归档里的文件列表,不解包。-r:向已有归档追加文件。-u:更新归档,仅添加比归档里更新的文件。
附加选项里面,最常用的是-f,表示操作对象是一个文件而不是标准输入输出。很多老遇到“No such file or directory”的新手,就是因为忘了写-f。然后就是压缩格式相关的-z(gzip)、-j(bzip2)、-J(xz)。
一个非常容易混淆的点:tar -czvf、tar -xzvf这些写法里,字母前面的-其实可以省略。GNU tar支持tar czvf这种写法,也支持tar -czvf,这两种在Linux上都能跑。但老牌的Unix系统上可能会有差异,所以我的建议是养成写短横线的习惯,跨平台更安全。
2.2 打包一个目录
假设你的项目目录叫myapp,里面有代码、模板、配置文件,现在要把它打包成myapp.tar.gz:
tar -czvf myapp.tar.gz myapp/拆开来看:-c表示创建,-z表示用gzip压缩,-v表示显示进度(把正在处理的文件名打到屏幕上),-f指定生成的归档文件名。命令的最后是你想打包的路径。
如果你打包的是当前目录下的内容,而不是目录本身,要注意路径写法。tar -czvf myapp.tar.gz .会把当前目录所有内容打包,不包含外层目录名。tar -czvf myapp.tar.gz myapp/则会把myapp这个目录本身也放进归档。这个区别在你解压的时候体现得很明显:前者解压出来是散落的一堆文件,后者解压出来是一个完整的myapp/目录。
2.3 解包并还原
解包呢,就是把上面命令里的-c换成-x:
tar -xzvf myapp.tar.gz默认会在当前目录下解出归档里保存的所有文件。如果你想解压到指定目录,加一个-C参数:
tar -xzvf myapp.tar.gz -C /tmp/restore/-C的意思是切换目录,相当于先执行cd /tmp/restore/再解压。注意这个目录必须已经存在,tar不会自动帮你创建不存在的目标目录,这是新手最容易踩的一个坑。
2.4 只查看归档里的内容,不解包
要确认一个tar包里的文件结构,不需要解压,用-t:
tar -tzvf myapp.tar.gz-t列出归档里的所有条目,加上-v会显示权限、属主、大小、时间等详细信息,相当于看到一份归档的目录树。这个操作在你想从一堆老包里找某一个配置文件时特别有用,省得解压出来翻一遍。
2.5 只解压归档里的某一个文件
有时候你只需要归档里的一个文件,比如日志包里的某一天日志,完全没必要全部解压。tar支持指定文件名解压:
tar -xzvf logs.tar.gz 2024/01/15/access.log注意这里的路径要跟归档里的路径完全一致,你可以先用-t查看确切的路径再去解压。归档里如果有几百个文件,只解压一个的速度会快非常多。
3. 参数选型背后的原理:为什么有的命令慢得离谱
3.1 gzip、bzip2、xz怎么选
上面提到了三种压缩格式,这里展开说说它们之间的取舍。gzip是历史最悠久、兼容性最好的压缩方式,压缩速度快,压缩率中等。xz的压缩率最高,能把文件压得更小,但压缩和解压过程都非常慢,对CPU的消耗也大。bzip2介于两者之间,但在这个领域的位置比较尴尬,虽然压缩率也不差,但速度比gzip慢,压缩率又赶不上xz,所以实际使用频率越来越低。
选哪个取决于你的场景。临时传个文件,用gzip就够了;做长期归档、希望占空间最小,用xz;传输大文件想省带宽当然也考虑xz,但要注意压缩时间可能比传输时间还长。举一个我经历过的例子,一份20GB的数据库备份文件,用gzip压缩用了6分钟,生成的文件是3.2GB;用xz压缩用了40多分钟,生成的文件是1.9GB。如果只是临时转存,多花三倍时间换那1.3GB的省量,完全划不来。
直接用tar的-J参数来走xz:
tar -cJvf backup.tar.xz /data/3.2 压缩级别怎么调
压缩工具都支持调整压缩级别,通常范围是1到9。数字越大压缩率越高,耗时和CPU占用也越高。gzip的默认级别是6,xz的默认级别是6(但xzgzip的最高是9)。tar里可以通过--use-compress-program传递额外的参数,也可以直接用环境变量。
我的习惯是:日常打包用默认级别就够了,不折腾。但如果要传大文件,我会把gzip级别调到1或2,因为压缩率变化不大,速度却能快上好几倍:
tar -cvf - /data/ | gzip -1 > backup.tar.gz这里用管道的方式,先把tar的输出送到gzip,再重定向到文件。理由是tar的-z参数没办法直接指定压缩级别,但单独调用gzip就能灵活控制。
3.3 归档格式的兼容性问题
tar的配置文件格式有好几种,GNU tar在老系统上可能会默认生成一种叫“GNU格式”的归档,它在文件路径过长、文件过大时用了扩展头处理,但新版本tar读老格式没问题,反过来老版本读新格式可能会警告。如果你的归档要分发给各种老旧系统使用,尽量用--format=posix或--format=ustar,保兼容性。
tar --format=posix -czvf backup.tar.gz /data/不过这个需求越来越少了,现在的Linux发行版基本都是GNU tar 1.30以上,兼容性早就不是问题。除非你接的特殊设备,否则不用太操心。
4. 实战场景:搭建一个靠tar支撑的自动化备份链路
4.1 按日期滚动备份
做运维的都清楚,备份文件必须带时间戳,不然恢复的时候根本分不清哪个是最新的。我习惯用date命令动态生成文件名:
#!/bin/bash backup_dir="/backup" target="/var/www/myapp" date_suffix=$(date +%Y%m%d_%H%M%S) tar -czvf "${backup_dir}/myapp_${date_suffix}.tar.gz" "${target}"也可以用$(date +%Y%m%d)只保留日期,一天备份一次。放在cron里每天凌晨执行,配合日志清理策略,这就是最简单的备份轮转方案。在cron里执行时有个坑——cron的环境变量跟交互式shell不一样,tar的路径建议写全,或者脚本开头先source /etc/profile。
4.2 增量备份的正确姿势
日常备份如果每次都全量打包,数据量大了之后不仅浪费空间,耗时也会越来越长。tar支持一种基于快照文件的增量备份模式:
# 第一次:全量备份 tar -czvf backup_full.tar.gz -g snapshot.snar /data/ # 第二次:增量备份 tar -czvf backup_inc1.tar.gz -g snapshot.snar /data/ # 第三次:继续增量 tar -czvf backup_inc2.tar.gz -g snapshot.snar /data/-g指定一个快照文件,tar会记录每个文件的访问状态和修改时间。第一次执行时快照不存在,所以是全量备份;第二次、第三次执行时,tar对比快照文件,只把新增或修改过的文件打进去。恢复的时候要按顺序先恢复全量,再依次恢复每个增量包。
增量备份的注意点在于,快照文件snapshot.snar必须小心保管,丢了的话后面的增量包就恢复不了了。这个文件很小,但它的作用就像链表的节点——断了一个,后面全断。所以备份策略里,快照文件本身也应该被单独保存和保护。
4.3 用管道实现远程备份
很多时候数据不在你手头这台机器上,你想把A服务器的数据打包到B服务器,可以先ssh过去跑tar,再把输出流拉回来或推过去。
ssh user@192.168.1.101 "tar -czvf - /data/project" > local_backup.tar.gz这里的-表示把tar的输出写到标准输出,而不是某个文件。ssh在远端执行命令,把命令产生的标准输出传输到本地,本地把这个流重定向成文件。这比先打包再传文件少了一步盘上读写,磁盘IO压力小。
反过来,把本地数据传到远端并解压:
tar -czvf - /data/project | ssh user@192.168.1.102 "cat > /backup/project.tar.gz"或者干脆直接解到远端:
tar -czvf - /data/project | ssh user@192.168.1.102 "tar -xzvf - -C /backup/restore/"这种玩法是tar和管道协作的精髓。理解了这种思路之后,你会发现tar不仅能跟ssh组合,还能跟其他命令组合,变成一条简洁的数据流水线。
4.4 排除不需要打包的文件
打包一个大目录时,通常要跳过一些不需要归档的文件或者目录。比如项目里的node_modules、cache、logs,这些目录又大又没必要备份。tar提供了两个实用的排除参数:
tar -czvf project.tar.gz --exclude="project/node_modules" --exclude="project/cache" /home/user/project/注意,排除路径的写法有讲究。如果你打包的是/home/user/project/,那么排除项要写project/node_modules,也就是归档内的相对路径。如果你直接在路径里带上绝对路径写--exclude="/home/user/project/node_modules",很多时候也不生效,这个坑我踩过好多次。最稳妥的方式是切换父目录打包,然后用相对路径排除:
cd /home/user && tar -czvf project.tar.gz --exclude="project/node_modules" --exclude="project/cache" project/另外,还有一种用通配符的排除方式:
tar -czvf project.tar.gz --exclude="*.log" /home/user/project/会跳过所有以.log结尾的文件。如果排除规则多,建议写成一个排除文件,用-X参数读入:
tar -czvf project.tar.gz -X exclude_list.txt /home/user/project/exclude_list.txt里每行一个排除模式,以#开头的是注释。模板如下:
# 排除日志 *.log # 排除缓存目录 project/cache project/tmp这个文件化管理方式最适合长期稳定的备份任务,规则全在文件里,改起来也方便。
4.5 分卷压缩:打包大文件到多个小卷
如果你要把一个大目录打包后刻录到光盘或者传到U盘,但U盘单个文件限制4GB,这时候就需要把归档拆分成多个小文件。tar本身不做分卷,需要配合split命令:
tar -czvf - /data/bigdata/ | split -b 2G - bigdata.tar.gz.part.输出文件会是bigdata.tar.gz.part.aa、bigdata.tar.gz.part.ab、bigdata.tar.gz.part.ac这样依次排列。合并的时候用cat拼回来:
cat bigdata.tar.gz.part.* > bigdata.tar.gz tar -xzvf bigdata.tar.gz顺序必须保持正确,split按aa、ab、ac排列,cat用通配符依次读取没问题。如果要更保险,可以用split -d配合数字后缀,不过通配符展开已经够稳了。
5. 排查与异常处理:tar用多了就懂的那些坑
5.1 路径里的“./”让解包目录变得奇怪
有些归档解压出来,你会看到文件都带着./前缀,比如./etc/nginx/nginx.conf。这是因为打包时用了相对路径。这种包解压是没问题的,但看着乱。避免的办法是打包时尽量在目标目录的父目录操作,用相对路径打包。
如果你收到这种带./的包想规整处理,可以用--strip-components裁掉路径层级。假设一个包里的路径是opt/myapp/config/nginx.conf,你只想解压出config/nginx.conf,不想要前两级目录:
tar -xzvf backup.tar.gz --strip-components=2这是解决“解压出来一堆嵌套目录”问题的利器。要注意数字2表示裁掉前2级路径,按实际需要调节。
5.2 一个文件在打包时发生变更,导致校验问题
生产环境里,你打包一个正在运行的目录时,很可能有进程正在写文件,导致出现“file changed as we read it”的警告。tar默认会继续打包,但会在退出码里标出异常。处理办法有两个:一是打包前先停服务,二是用tar的--ignore-failed-read(但注意选项行为有限),或者干脆接受警告,只要退出码不是1就还能用。
有个比较极端的方案是结合--warning=no-file-changed参数:
tar -czvf backup.tar.gz --warning=no-file-changed /var/lib/mysql/意思是不把这个警告视为异常。可以用,但我还是建议数据库这类文件先用工具做快照,再用tar打包快照目录,更稳妥。靠tar的warning控制只是权宜之计。
5.3 解压时提示“Permission denied”
权限问题是初次使用tar最常踩的坑。归档里的文件可能属于某个用户,你当前用户没有权限写入目标目录,解压就会报Permission denied。常见的场景是用普通用户解压由root创建的包到/usr/local这类目录。解决方式要么用sudo,要么解压到自己的家目录再用sudo移动。还有一点,如果你以root身份解压别人打包的包,会很自然地改变文件属主,这在恢复环境时要多留意。
5.4 “Cannot open: No such file or directory”是怎么回事
这个提示通常有两个原因:一个是目标目录不存在,一个是归档里解压出的路径包含不存在的父目录。前者用-C之前先确保目录存在;后者一般不会发生,tar会自动创建路径中的目录。真正需要担心的是,如果归档中有绝对路径,比如/home/user/file.txt,解压时会直接写到根目录下的绝对路径,不再受-C目录的影响。从这个角度说,打包的时候用相对路径是更安全的习惯。
5.5 检查tar包是否完整
在传完一个大包之后,最好先确认一下归档文件没有损坏。一个快速的验证方式是:
tar -tzf backup.tar.gz > /dev/null如果归档有损坏,这个命令会报错。如果是gzip格式的包,你还可以先单独检查gzip完整性:
gzip -t backup.tar.gzgzip -t专门测试gzip压缩流的完整性,exit code为0表示没有损坏。先把压缩层验证一遍,再做tar层验证,双保险。
6. 进阶玩法:tar跟其他工具的组合技
6.1 按文件大小、时间筛选后打包
有时候你只想把某个目录下最近7天内修改的文件打包,传统做法是先find再tar。find的输出作为参数传给tar有两种主流方式。
第一种是借助xargs:
find /data/project -name "*.log" -mtime -7 -print0 | xargs -0 tar -czvf logs.tar.gz用-print0和-0是为了避免文件名里有空格导致的切词问题。第二种是tar自身的-T参数,从一个文件读取待打包文件列表:
find /data/project -name "*.log" -mtime -7 > filelist.txt tar -czvf logs.tar.gz -T filelist.txt注意-T的参数是包含文件列表的文件,每行一个文件。这两种方式我个人更推荐第二种,因为事后可以保留filelist.txt,作为备份清单备查。而且-T还能配合进程替换(<(...))直接在命令行里生成临时列表:
tar -czvf logs.tar.gz -T <(find /data/project -name "*.log" -mtime -7)这段在bash和zsh里都能跑,非常干净。
6.2 用tar做磁盘间迁移
服务器上数据目录迁移到另一块磁盘时,用cp -a或者rsync都行,但tar在数据量特别大、文件特别多的时候表现也相当稳。硬拷贝一大片文件到新盘,inode和权限不容易完全对齐,而且碎片多。有一种老派的迁移手法:
cd /old/data && tar -cf - . | tar -xf - -C /new/data这通过管道把一个目录树完整复制到另一个目录,保留权限、属主、时间戳,而且复制过程中不需要中间文件。相当于用tar流作为文件系统的搬运工,源目录一边读,目标目录一边写。跟cp相比,tar在遇到tar能处理的特殊文件时会透明地保留元数据,在内网迁移场景里非常实用。
6.3 边打包边加密
敏感数据打包之后,最好再做个加密。有几种组合方式,最简单的是tar输出管道给gpg:
tar -czvf - /data/secret | gpg -c > secret.tar.gz.gpg解包时反向操作:
gpg -d secret.tar.gz.gpg | tar -xzvf -这样打包、压缩、加密一步到位,整个过程不落盘明文。gpg -c用对称加密,会提示输入密码,适合单机备份。如果想要非对称加密,也可以调整gpg的收件人参数,思路完全一样。这套组合技在传输敏感文件时能让队友惊叹你竟然还会这招。
6.4 与find配合做定期清理
备份目录会越积越大,光备份不清理等于慢性自杀。一个很成熟的惯例是:查找超过7天的备份文件并删除,同时配合定时任务。比如:
find /backup -name "*.tar.gz" -mtime +7 -delete这里的-delete直接删文件,慎用。更稳的写法是先用-print看看列表,确认无误再换成-delete。实际生产环境里我通常会把这句话放在独立脚本里,并额外限制删除范围,避免手滑删掉不该删的东西。
6.5 归档超大文件时观察进度
用-v会刷屏一样地把每个文件名打印出来,但如果文件特别巨大,比如几百GB的单个文件,你其实看不到太多有效信息。这时候可以不用-v,直接看文件大小增长:
ls -lh backup.tar.gz或者用更高级的方式,配合pv命令监控流量:
tar -czvf - /data/ | pv -s $(du -sb /data/ | awk '{print $1}') > backup.tar.gzpv会显示传输速度、剩余时间、进度条。-s参数指定总字节数,从du -sb拿目录大小。这种方式在备份几十GB的目录时体验极好,你至少知道进度在哪,而不必盲目猜测命令什么时候能结束。
7. 一些我个人的使用心得
tar这个命令,用熟了以后你会慢慢体会到它在Unix工具箱里的分量。它不复杂,参数就那几个,但它像万金油一样能跟几乎任何工具组合,形成强大的数据流处理链。很多看起来需要专门软件才能做到的事,一个tar管道就搞定了。
我个人有两点建议。第一,常用命令里的参数不要死记硬背,先弄清楚-c、-x、-t、-f、-z这五个字母的关系,其余的都是组合变化。第二,养成相对路径打包的习惯,减少解压时踩路径的坑。最后,不管用tar做了什么操作,尽量在脚本里加上退出码判断,因为tar静默失败的场景虽然少,但一旦发生,影响的可能就是整个备份链路。