接手《Linux文件管理》这个系列的时候,我特意把“下篇”的选题范围往系统层面压。上篇大家普遍会把ls、cd、cp、mv、rm、mkdir这些高频操作讲透,但实际到了生产环境或者稍微复杂的个人服务器场景,你会发现真正卡人的往往不是命令记不记得住,而是文件背后那一层“看不见的结构”:为什么删了文件磁盘空间却不变?为什么cp一个十几 GB 的目录会慢得离谱?为什么明明给了rwxrwxrwx权限,服务还是起不来?这些问题不把文件系统的底层逻辑搞明白,用再多的命令都是隔靴搔痒。
所以这一篇我打算换个讲法,不按命令清单铺开,而是按“真实操作中会遇到的麻烦”来组织内容:inode 与链接、权限与特殊属性、磁盘空间回收、文件查找、压缩归档、rsync 同步与备份。你可以把这篇当成一份“踩坑笔记 + 排障手册”,每段都照着实际场景复现过,命令可以直接抄。
1. inode 与链接:文件管理里最容易被忽略的底层单元
1.1 inode 到底是什么,为什么要先懂它
很多新手会有个误解,觉得“文件”就是路径加文件名,/home/user/report.pdf这个字符串指向硬盘上的一段数据。但 Linux 文件系统里真正的概念是:文件名只是目录里的一个条目,它指向 inode;inode 里存着权限、属主、时间戳、数据块的指针。真正读文件时,内核拿文件名去目录里查 inode 编号,再通过 inode 找到数据块。
用大白话说:inode 是文件的“身份证号”,目录是“户口本”,数据块是“房子”。你改文件名只是改了户口本上登记的名字,身份证号和房子都没变。
用ls -i就能看到文件和 inode 的对应关系:
$ ls -i /etc/hosts 131079 /etc/hostsstat看更详细的信息:
$ stat /etc/hosts File: /etc/hosts Size: 221 Blocks: 8 IO Block: 4096 regular file Device: fd01h,21 Inode: 131079 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)注意Links这一项,这就是硬链接数量。理解 inode 之后,很多现象就好解释了:
cp会创建新的 inode,源文件和目标文件是两条独立生命。mv如果在同一文件系统内,只是把目录项从一个目录移动/改名为另一个目录项,inode 不变,所以速度极快。- “磁盘空间没释放”常和 inode 有关:文件被删除只是删了目录项,只要还有进程持有这个文件的句柄,inode 就还活着,数据块就一直被占用。
文件系统还有一个指标叫inode 耗尽。df -i可以看到:
$ df -i /home Filesystem Inodes IUsed IFree IUse% Mounted on /dev/vda1 3276800 218123 3058677 7% /home我在小内存 VPS 上见过/var/spool/postfix/maildrop堆了上百万个小文件,把 inode 全部吃完,结果df -h显示还有 20GB 空闲,但touch任何文件都报No space left on device。排查时先看df -i能省很多时间。
1.2 硬链接与软链接:用法、差异和误区
链接是文件管理的第二高频概念,但在生产环境里用错的人不少。
硬链接(Hard Link):本质是在目录里加一条指向同一个 inode 的目录项。多个文件名指向同一个 inode,Links数会增加。特点:
- 只能在同一个文件系统内创建,不能跨分区或跨设备。
- 不能对目录创建硬链接(
ln dir link会直接拒绝,这是设计上的安全限制,防止目录出现环)。 - 删除任何一个硬链接,只要还有别的链接存在,数据就不丢;全部删完,inode 才释放。
- 硬链接没有“源文件”和“目标文件”的区分,彼此平等。
软链接(Symbolic Link,符号链接):相当于 Windows 的快捷方式,它是一个独立的小文件,保存的是目标路径的字符串。特点:
- 可以跨文件系统、可以指向目录。
- 目标被删除后,软链接就成了“悬空链接”(dangling link),
ls -l能看到目标名变成红底白字。 - 它有自己的 inode,所以
rm删除软链接只是删除这个“快捷方式”,不会动目标。
创建命令很简单:
# 硬链接 ln /data/logs/app.log /data/logs/app_20240101.log # 软链接 ln -s /data/apps/nginx/conf/nginx.conf /etc/nginx/nginx.conf实际操作中我建议注意两点。第一,软链接的路径要尽量用绝对路径。你写相对路径不是不能用,但一旦将来软链接文件被移动,链接就断了。比如在/home/user/tools/下创建ln -s bin/start.sh run.sh,这个bin/start.sh是相对于软链接所在目录解析的,不是相对于当前命令行所处的目录。用绝对路径永远最稳。
第二,对软链接使用rm要小心。rm link删的是链接本身,但很多人习惯写rm link/(带尾部斜杠),如果 link 指向目录,有些 shell 会把它解析成目标目录,结果出现rm: cannot remove 'link/': Is a directory或者误删目录内容。我一般会先file link看看类型再操作。
1.3 实际创建和维护链接的经验
生产环境里最常见的就是用软链接切版本、切配置:
# 部署新版本 ln -sfn /data/app/releases/v2.1.0 /data/app/current # 重新加载服务 systemctl reload myapp这里的ln -sfn三个参数建议记牢:-s软链接,-f覆盖已存在的目标,-n把目标当作普通文件处理(避免链接指向目录时出现歧义)。我见过有人只用-sf,结果目标是个目录时,命令会在目录里面再生成一层链接,路径立即乱掉。
批量创建硬链接时,注意别跨文件系统。比如家目录在/home挂载点上,/tmp如果是独立 tmpfs,那ln /home/user/foo /tmp/foo会报Invalid cross-device link。这时只能用软链接或cp。
2. 权限与特殊属性:文件管理的安全边界
2.1 rwx 数字法和 umask 的底层逻辑
每次讲权限都要重复一遍基础,但这里我要换个角度讲“为什么要这样设计”。rwx分别对应读、写、执行,但“执行权限”在目录上不是“运行程序”的意思,而是“能否进入目录”。这也是新手最容易踩的坑:给了文件chmod 777 file,想从目录外面访问它,结果连目录的x权限都没有,照样Permission denied。
数字表示法把r=4, w=2, x=1相加,本质是二进制位。rwxr-xr--就是 111 101 100,对应 754。理解这一点后,看到任何三位数字你都能心算出来。
但生产环境里真正影响文件初始权限的是umask。umask是“要屏蔽掉的权限位”,新文件默认权限 = 0666 减去 umask,新目录默认权限 = 0777 减去 umask。常见值022表示去掉组和其他人的写权限,所以新文件是 0644,新目录是 0755。查看当前值:
$ umask 0022这个值改了之后只影响当前 shell,想持久化要写到~/.bashrc或/etc/profile。我自己的习惯是服务器上统一umask 027,这样团队新创建的文件默认对组内可读、组外完全不可见,避免一堆 0644 文件泄露给其他人。
2.2 SUID、SGID、Sticky Bit:特殊权限位别搞混
这三兄弟里,Sticky Bit(粘滞位)在/tmp上最典型:目录权限显示为drwxrwxrwt,末尾的t就是它。作用是:目录内的文件,只有文件属主、目录属主或 root 才能删除。如果没有它,/tmp这种 777 目录里任何用户都能删掉别人的临时文件,系统早就乱套了。设置命令是chmod +t /tmp或chmod 1777 /tmp。
SUID(Set User ID)是针对可执行文件的。普通用户执行passwd能修改/etc/shadow,就是因为passwd文件有 SUID,执行时进程的有效用户 ID 临时变成 root。看到这种带s的权限(-rwsr-xr-x)就要警惕,尤其是不明来源的可执行文件,一旦有漏洞,等于把 root 权限拱手交给普通用户。日常巡检时我常用这一条找出所有带 SUID 的文件:
find / -type f -perm /4000 -ls 2>/dev/nullSGID(Set Group ID)有两个作用:作用于可执行文件时,进程继承文件所属组;作用于目录时,目录内新建的文件会自动继承目录的所属组。这个特性在做项目共享目录时特别有用。团队开发时:
mkdir -p /data/project chown root:devteam /data/project chmod 2770 /data/project这样 devteam 组里的任何人在该目录下创建文件,文件组都会自动变成 devteam,其他人不会因为umask或创建者主组不同就访问不了,省去反复chgrp的麻烦。
2.3 chattr 防篡改与 ACL 细粒度授权
有些文件即使 root 也有必要防一手,最典型的就是 DNS 配置、登录脚本和后门类文件。chmod管的是“谁能访问”,chattr管的是“谁能改 inode 属性”,两者不在一个层面。
给文件加不可变属性:
chattr +i /etc/hosts # 去掉 chattr -i /etc/hosts加了+i之后,连 root 都不能修改、删除、重命名该文件。这招对防勒索软件和误删有奇效。但有两个现实问题:chattr不是所有文件系统都支持,ext4、xfs、btrfs 可以,但 tmpfs、FUSE 挂载的部分虚拟文件系统会直接报错;改/etc/hosts这类文件时要记得先去掉属性再改,否则明明内容改了却保存不了,先用lsattr查一下能避免一头雾水。
ACL(Access Control List)适合“我要给某个特定用户开权限,但不想动组的权限”的场景。getfacl查看,setfacl设置:
setfacl -m u:deploy:rwx /data/app/config setfacl -m g:devteam:r-x /data/app/logs/ getfacl /data/app/config设置了 ACL 后,ls -l权限位末尾会出现一个+:
-rw-r-----+ 1 root root 1024 Feb 5 10:00 config.ini注意 ACL 里的权限会被文件 mode 中的 mask 挡住。比如文件原本0640,mask 是r--,那你给某个用户rwx也不会生效,实际权限是 ACL 权限与 mask 做“与”运算后的结果。调试时先getfacl看 mask 这一行,很多“设置了但没效果”的怪问题其实都出在这。
2.4 权限审计:怎么快速发现危险文件
我每隔一段时间会在服务器上跑一次权限自查,核心几条命今收藏好:
# 找到所有全局可写且带执行权限的文件(容易被植入恶意脚本) find / -type f -perm -0002 -a -perm -0001 -ls 2>/dev/null # 找到所有没有属主的文件(可能是残留用户删除后留下的) find / -nouser -o -nogroup -ls 2>/dev/null # 找到普通用户主目录下被改成全局可写的敏感文件 find /home/ -type f -perm -002 -ls 2>/dev/null正常系统里这些输出应该极少。如果突然冒出来一批/var/www/html下的 777 文件,那基本可以断定站点被拿下了,下一步就是备份、清马、查日志。
另外提一句getcap。现在很多服务用 POSIX capabilities 给二进制文件授权,不直接挂 SUID。比如让普通用户用 80 端口监听而不给 root:
setcap 'cap_net_bind_service=+ep' /usr/bin/myservice getcap /usr/bin/myservice这个东西排查时很容易漏,ls -l看不出任何异常,但程序确实获得了额外能力。安全基线检查时记得加上getcap -r /usr/bin/ 2>/dev/null扫一遍。
3. 磁盘空间明明不够,文件却“删不掉”:空间回收与 WSL 特例
3.1 为什么删除文件后空间没释放
这个坑太经典了。rm -rf /var/log/syslog之后,df -h显示/还是占用 95%。这时千万别再删别的文件,先查谁还握着这个文件的句柄:
lsof | grep deletedlsof会列出所有被标记为deleted但仍被进程打开的文件。常见来源是 rsyslog 或应用日志进程,删了日志但进程没 reopen,空间就一直在。解决方式优雅一点的做法是让进程重新加载配置:
systemctl restart rsyslog # 或者用 kill -USR1 向 rsyslogd 发送信号,让它 reopen 日志文件如果进程不方便重启,最直接的办法是把文件内容清空而不是删除,但注意清空要保证进程确实在 reopen 读写位置:
: > /var/log/syslog不过有些日志进程用 O_APPEND 打开文件,清空后继续从末尾写,空间确实能释放。而用 O_WRONLY 且维护了位置指针的进程,清空后位置还是原来的偏移,下次写入会变成稀疏空洞,空间并不会真正释放。所以最稳妥的方案还是让进程 reopen。
3.2 WSL 里的磁盘空间“只增不减”怎么破
热门搜索里 WSL(Windows Subsystem for Linux)删除文件后空间没释放的问题,我特意测试过。WSL 2 用的是 ext4 虚拟磁盘,放一个固定大小的ext4.vhdx文件在 Windows 里。你在 WSL 里删文件,ext4 文件系统内部是有空间的,但这个 vhdx 文件本身不会自动收缩,Windows 看到的磁盘占用一点不会少。
解决方法是先卸载 WSL,再用 diskpart 压缩虚拟磁盘。操作流程:
- 在 Windows 命令行下彻底关闭 WSL:
wsl --shutdown打开开发人员模式以使用 diskpart(或管理员权限的 PowerShell 或 cmd)。
找到发行版对应的 VHDX 路径,一般在:
C:\Users\%USERNAME%\AppData\Local\Packages\...\LocalState\ext4.vhdx使用 diskpart 压缩:
diskpart select vdisk file="C:\Users\xxx\AppData\Local\Packages\...\LocalState\ext4.vhdx" attach vdisk readonly compact vdisk detach vdisk exit- 重新运行
wsl验证。
压缩原理是 diskpart 会把 vhdx 内部未使用的块标记并释放,所以压缩前先确保 WSL 里已经清理过日志、包缓存(apt clean、journalctl --vacuum-size=100M这种)才有意义,否则缩不了多少。
3.3 du 和 df 不一致、稀疏文件与文件空洞
有时du -sh和df显示的占用对不上,这不一定是 bug。du统计的是“文件真正占用的数据块”,df统计的是“整个文件系统已用块”。两者不一致通常有三个原因:
- 删除文件但进程还在用(上一节的情况),
du在目录里按名字统计,看不到已删除的文件;df会把这个空间的占用记在头上。 - 文件有空洞(稀疏文件)。比如一个 10GB 的文件,中间全是 0,文件系统可能不会真的分配物理块。
ls -lh看的是逻辑大小,du -h看的是实际块大小,两者的差距可能就是空洞。 - 保留块。ext 系列文件系统默认给 root 留 5% 的空间,
df显示的使用率其实是按可用块反推的,和小文件多的场景有出入。
判断一个文件是否稀疏,可以用:
du -h file ls -lh file stat -c '%b blocks, block size = %B' file如果du的大小远小于逻辑大小,说明是稀疏文件。做备份镜像时要注意:tar 默认会保留稀疏文件的稀疏性,但cp file.img /backup/默认会把空洞展开成真实 0,结果备份体积暴涨。用cp --sparse=always或在 tar 时加--sparse能解决。
4. 文件查找:find、locate 的正确打开方式
4.1 find 的表达式结构,别再当“命令拼接”用了
很多人用 find 是套模板一样在拼参数,一旦要组合条件就懵。其实find的表达式是一套逻辑语言:find [路径] [测试项] [操作项]。测试项描述“什么样的文件”,操作项描述“找到后干什么”。默认操作是-print,也就是打印路径。
测试项可以组合,-a是“与”、-o是“或”、!是“非”。完整的逻辑可以用括号分组,但括号在 shell 里要转义:
# 找 /var/log 下 7 天前的 .log 或 .gz 文件 find /var/log \( -name "*.log" -o -name "*.gz" \) -mtime +7按时间条件找文件是高频需求。需要注意-mtime +7不是“7 天以前”,而是“到当前时刻往前数 7 个 24 小时的周期之前”,边界上容易有偏差。要精确到分钟可以用-mmin:
# 最近 30 分钟内被修改过的文件 find /data -mmin -30 # 超过 60 天未被访问的文件 find /data -atime +60按大小找文件也常用:
# 大于 100MB 的文件 find / -type f -size +100M # 正好 1024 字节 find / -type f -size 1024c-size后缀c表示字节、k表示 KiB、M表示 MiB、G表示 GiB。注意文件大小向上取整到块大小,所以-size 1024c匹配的可能是逻辑大小在 (1024, 2048] 区间的文件,细节控要留意。
4.2 -exec 和 xargs,哪个更快、哪个更安全
找到文件后要批量操作,有两个选择:-exec和管道给xargs。
# 方式一:-exec find /data/logs -name "*.log" -exec gzip {} \; # 方式二:xargs find /data/logs -name "*.log" | xargs gzip执行逻辑差别很大。-exec会把每个匹配到的文件分别作为参数运行一次命令,效率低;但胜在安全,文件名里有空格、换行、引号都不会出问题。xargs默认按空白和换行把输入拆成参数,遇到文件名带空格就会拆错,必须配合-print0和xargs -0:
find /data -name "*.log" -print0 | xargs -0 -P 4 gzip-P 4是并发 4 个进程,能极大提高大批量压缩任务的效率。但并发操作有时会导致输出错乱,打印日志时建议加-xargs -n1 -P4来控制。
另外xargs还有个隐患:目标命令参数数量有上限。如果文件非常多,xargs 会自动分多批执行,但某些命令(比如rm -rf)第二批执行时会可能遇到第一批已经处理的文件,报“No such file or directory”但并不影响正确性。如果要严格保证一批执行完再执行下一批,用xargs -n1或者直接写 while 循环:
find /data -name "*.log" -print0 | while IFS= read -r -d '' file; do gzip "$file" done这个循环写法是处理“文件名包含各种奇怪字符”的最稳方案,建议重点记住。
4.3 locate 为什么快,又为什么会“查到早就删掉的文件”
locate的原理是查一个预先生成的数据库(/var/lib/mlocate/mlocate.db),所以查询极快。但数据库不是实时更新的,默认由updatedb定时跑,所以你删了文件后一小时内locate还能查到路径。
使用前先更新:
sudo updatedb locate nginx.conflocate适合快速回忆“某个文件放在哪个目录”,find适合做逻辑复杂的排障。两者不是竞争关系,各管一摊。
另外提一个定位可执行文件的需求:which和whereis。whereis还会找手册页和源码路径,比which的信息更全。不过要查一个 shell 内置命令(比如cd、echo),which是查不到的,得用type cd。
5. 压缩解压与归档:格式选型和解压中文乱码
5.1 tar 加压缩算法的八股用法
归档和管理能力是文件管理的必备技能。tar 最常用的三套组合必须顺手:
# 打包并压缩成 gzip tar -czvf backup.tar.gz /data/app # 解压到指定目录 tar -xzvf backup.tar.gz -C /tmp/restore # 只查看内容不解压 tar -tzvf backup.tar.gz排序差异是新手最爱犯的错:-create的 c、-gzip的 z、-verbose的 v、-file的 f 是有顺序的,f后面必须直接跟文件名,所以tar -cvzf backup.tar.gz合法,tar -cvfz backup.tar.gz会报错。
选择合适的压缩算法是个权衡问题。gzip 速度中等、压缩率一般;bzip2 压缩率更高但慢;xz 压缩率最高但非常吃 CPU。我实测过一个 2GB 的文本日志目录:gzip 用 40 秒压到 180MB,xz 用将近 5 分钟压到 120MB。如果只是日常备份,gzip 就够了;真正做冷存储,我会用 xz 但加-T 0启用多线程。
5.2 7z、分卷与加密备份:不同场景怎么选
除 tar 之外,7z也是绕不开的场景。尤其涉及 Windows 和 Linux 混传文件时,7-Zip 格式兼容性好,压缩率也很能打。常用命令:
# 压缩目录 7z a backup.7z /data/app # 解压到指定目录 7z x backup.7z -o/tmp/restore # 测试压缩包完整性 7z t backup.7z注意:如果解压命令带了x,会保留压缩包内的目录结构;如果只是想抽出某一个文件,可以7z e backup.7z config.ini,e会把所有文件平铺到当前目录,不保留层级关系。
做分卷压缩时:
7z a -v100m backup.7z /data/app会生成backup.7z.001、backup.7z.002等分卷,解压时只要对着第一卷执行7z x backup.7z.001,它会自动读取后续分卷。
加密备份也是重点。直接存备份文件在服务器上并不安全,我用 7z 加密时会指定 AES-256:
7z a -p -mhe=on backup.7z /data/app-p会交互式提示输入密码,-mhe=on表示连文件名列表都加密。这样除解压者自己,别人拿到压缩包连里面有哪些文件都看不到。唯一要提醒的是:密码忘了就是真没了,7z 没有找回途径。
5.3 解压文件乱码:zip 与 7z 的编码问题
热门搜索里“linux 解压文件乱码”是非常普遍的问题,尤其是国内环境。原因是 Windows 下压缩工具(老版 WinRAR、某些国产压缩软件)对中文文件名用 GBK/GB18030 编码,而 Linux 系统默认 UTF-8,解压时文件名被按照 UTF-8 解释,自然就变成乱码了。
zip 格式没有标准的编码声明位置,所以问题最严重。最直接的解决方式是安装unzip后指定编码:
sudo apt install unzip convmv # convmv 用于转码文件名 unzip -O GBK file.zip -d /tmp/outunzip的-O参数能指定文件名编码。如果你的 unzip 版本偏老不支持-O,可以用 Python 的 zipfile 搭配编码处理,或者用7z x试试,7-Zip 对中文编码的处理比 unzip 好不少。
已经解压出来且名字乱掉了,可以批量转码文件名:
convmv -f GBK -t UTF-8 --notest -r /tmp/out/convmv会重命名文件,--notest表示真正执行而不是只预览。跑完后再ls,中文文件名就正常了。
对于 7z 压缩包,不太容易遇到严重的编码问题,因为 7-Zip 较新版本会在头信息里声明编码,但遇到老 7-Zip 打包的内容同样可能乱码。先7z l backup.7z看看文件名列表是否正常,再决定要不要解压后转码。
5.4 压缩格式实测对比和选择建议
我拿同一份多类型数据做过一轮比较,结果供参考:
| 格式 | 压缩耗时(2GB 文本+日志) | 压缩后大小 | 说明 |
|---|---|---|---|
| tar + gzip | 约 45 秒 | 182MB | 速度与压缩率均衡,最通用 |
| tar + bzip2 | 约 3 分钟 | 158MB | 压缩率高,速度偏慢 |
| tar + xz | 约 5 分 30 秒 | 121MB | 压缩率最高,适合冷存储 |
| 7z(默认 LZMA2) | 约 4 分钟 | 117MB | 与 xz 接近,且支持加密分卷 |
日常备份选择:当天要恢复的用 gzip,异地冷备可以用 xz 或 7z,7z 的加密加分卷能力让它成为外发数据的最佳选择。另外记一个实用小技巧:pigz、pbzip2、pxz分别是 gzip、bzip2、xz 的多线程版本,打包时配合管道能把压缩时间缩短好几倍:
tar -cf - /data/app | pigz -p 4 > backup.tar.gz6. rsync 与同步备份:让文件管理的边界延伸到另一台机器
6.1 rsync 的核心机制:为什么它比 cp 高级
管理一台机器上的文件是基础功,跨机器同步是进阶功。rsync之所以是行业标准,核心是它的增量同步机制:对比源和目标文件的差异,只传输变化的部分。支持本地目录同步、SSH 远程同步,也可以通过 rsync daemon 同步。
经典用法:
# 本地目录同步 rsync -av /data/app/ /backup/app/ # 推送到远程服务器 rsync -avz -e ssh /data/app/ user@192.168.1.10:/data/app/ # 从远程拉取 rsync -avz -e ssh user@192.168.1.10:/data/app/ /data/app/这里目录尾部斜杠app/的差别要专门讲:rsync -av /data/app /backup/会把app这一层目录整体复制过去,得到/backup/app;而rsync -av /data/app/ /backup/会把app目录下的内容直接同步到/backup/内。写错一条斜杠,目标路径就和我们预想的不一样,非常容易踩。
-a是归档模式,包含递归和保留大部分属性;-v是输出详情;-z是传输时压缩。前两个参数我默认每次都会加,-z在局域网高速传输或文件本身是压缩格式时可以不加,因为压缩反而浪费 CPU。
6.2 rsync 增量备份:删除、排除和校验
配套的参数组合才是 rsync 的进阶效率所在:
# 目标端删除源端已经删掉的文件(保持两边完全一致) rsync -av --delete /data/app/ /backup/app/ # 排除不需要同步的目录 rsync -av --delete --exclude='cache/' --exclude='*.tmp' /data/app/ /backup/app/ # 用 checksum 代替“大小+时间”判断是否变化 rsync -avc /data/app/ /backup/app/--delete是双刃剑。它能让备份完全镜像源目录,但如果没有加--backup,源目录里的误删立刻会在备份里复现。所以我建议生产环境备份不要直接用--delete,而是配合--backup-dir把被删的文件先挪到带时间戳的目录里:
mkdir -p /backup/archive/$(date +%F) rsync -av --delete --backup --backup-dir=/backup/archive/$(date +%F) /data/app/ /backup/app/rsync 默认判断“文件是否变化”是看大小和修改时间,大多数场景够用,但如果你怀疑某台机器时间不对,或者文件内容变了但 mtime 被刻意改回去,这时候加-c强制用 checksum 判断最可靠。代价是大目录扫描很慢,因为每个文件都要重新读一遍算哈希。
6.3 增量备份与定时任务组合
配合cron做定时备份是标准动作。我习惯把备份任务写成脚本再挂 crontab,而不是直接在 crontab 里写一长串命令,这样可读性和维护性好很多。
#!/bin/bash # /usr/local/bin/backup_app.sh set -euo pipefail SRC="/data/app" BK="/backup/app" YESTERDAY_ARCHIVE="/backup/archive/$(date -d 'yesterday' +%F)" LOGFILE="/var/log/backup_app.log" mkdir -p "$BK" "$YESTERDAY_ARCHIVE" rsync -av \ --delete \ --backup \ --backup-dir="$YESTERDAY_ARCHIVE" \ --exclude='cache/' \ --exclude='*.log' \ "$SRC/" "$BK/" >> "$LOGFILE" 2>&1 if [ $? -eq 0 ]; then echo "$(date '+%F %T') backup success" >> "$LOGFILE" else echo "$(date '+%F %T') backup FAILED" >> "$LOGFILE" exit 1 fi写set -euo pipefail是我强烈建议的习惯:脚本中任何一步失败都会退出,避免中途报错后还继续执行下面的同步命令,导致半完整的备份被当成完整备份。
配合的 crontab:
30 2 * * * /usr/local/bin/backup_app.sh这样每天凌晨 2:30 执行增量同步,被删除或修改前的旧文件会自动放入带日期的归档目录,相当于做了一层“时间点保护”。真正出问题时,先从最新的app恢复,缺的文件再回翻归档目录。
写在最后
这一篇的内容分布比较“系统”,线程从 inode 一路延伸到 rsync,更像是把平时排障和备份中反复用到的东西串起来。我自己最深的体会是:文件管理的很多疑难杂症,表面上是命令不会用,实际上是模型出了问题。比如磁盘满但找不到大文件,是因为没理解“删除文件释放空间要看进程持有”;解压乱码,是因为没理解“文件名编码是压缩包内部信息,解压根系导出这层信息”;cp同步慢到怀疑人生,是因为没意识到它不做增量对比。
后续如果你想继续往下扩展,我建议重点研究两个方向:一是 LVM 和文件系统快照,它能把文件管理从“手动同步”升级到“秒级快照”;二是用 inotifywait 监控目录变化、结合 rsync 做准实时同步,这是很多文件服务类应用的常见底座。等有空我再把这两块单独拉出来写。