ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux软链接与硬链接:inode原理、rm陷阱与发布切换实战

Linux软链接与硬链接:inode原理、rm陷阱与发布切换实战 1. 从一次删链接删空了目录的事故说起先把 inode 这层看穿几年前帮朋友处理过一次线上事故过程很典型发布目录/data/web/current是指向/data/web/releases/20240312的软链接运维同学想重建这个链接敲了一句rm -rf /data/web/current/然后整个 release 目录的内容被清空了。他当时的原话是我只是想删个链接。这件事的技术根因就藏在软链接与硬链接这两个概念的分野里。这篇文章要讲清楚的就是它们在 Linux 文件系统里到底是什么东西、各自能解决什么问题、创建和删除的时候命令到底做了什么、以及哪几种写法会把链接操作变成删库操作。先把结论放在最前面方便你对号入座硬链接是同一个 inode 的第二个名字软链接是一个独立文件、内容是另一个路径的字符串。这一句话几乎能推导出后面所有的差异——跨分区行不行、删源文件会不会失效、权限怎么算、ls -l第二列那个数字是什么意思。很多人学这两个概念时是背硬链接不能跨分区、软链接可以跨分区背完就忘原因就是没把 inode 这层看穿。这篇文章适合三类人刚学 Linux 命令、被ln和rm的坑教育过的初学者正在用软链接做发布切换、配置切换的运维和开发以及需要给磁盘做去重、做增量快照备份的人。文中所有命令我都实测过输出示例基于常见的 ext4 / xfs 环境Debian 系和 RHEL 系表现一致。1.1 文件名只是贴在 inode 上的一张标签在 Linux 的绝大多数文件系统里一个文件其实是两段式结构一段是inode存的是元数据——类型、权限位、属主属组、大小、三个时间戳atime/mtime/ctime、数据块指针、以及链接计数另一段是数据块存真正的内容。而你在终端里敲的那个文件名本质上是目录这个特殊文件里的一条记录文件名 - inode 号。这个映射关系一定要建立起来。目录项是名字到 inode 的映射表名字和数据是解耦的。硬链接做的手术就是往目录里再插一条记录让第二个名字也指向同一个 inode。所以硬链接不产生新的 inode不占额外的数据块只是多了一行目录记录多占的那点空间在目录文件里通常几十字节。我习惯用快递柜来类比inode 是柜子数据是你放进去的东西文件名是贴在柜门上的便签。硬链接就是在另一个柜门上再贴一张写着同样编号的便签两张便签开门看到的是同一个柜子里的同一件东西你在哪个柜门上改标签内容另一个柜门看到的也是同一个东西。软链接则不是便签它是另一张单独的纸条上面写着东西在 3 号柜你按纸条去找才去开 3 号柜。看一条实际输出感受一下$ echo hello a.txt $ ln a.txt b.txt $ ls -li a.txt b.txt 262146 -rw-r--r-- 2 user user 6 Mar 12 10:21 a.txt 262146 -rw-r--r-- 2 user user 6 Mar 12 10:21 b.txt-i打印 inode 号两个文件都是262146完全同一个 inode。第二列那个2就是链接计数link count意思是当前有两条目录记录指向这个 inode。这就是ls -l第二列最容易被忽略的含义——它不是文件个数是指向该 inode 的名字个数。1.2 目录的链接计数为什么总是大于 2顺着上面那个名字个数的逻辑往下推你会发现一个很有意思的现象一个新建的空目录链接计数是 2而不是 1。$ mkdir demo ls -ld demo drwxr-xr-x 2 user user 4096 Mar 12 10:30 demo原因是目录里天然有两个硬链接demo这个名字本身父目录里的记录以及目录内部的.。而..是父目录的硬链接。所以创建一个空目录内部有.和..两条记录.指向自己使自己的计数 1..使父目录的计数 1。当你在这个目录下再建一个子目录时子目录的..又会让本目录的计数 1所以普通目录的链接计数 2 直接子目录的个数。这也是为什么普通用户不允许给目录创建硬链接——如果允许文件系统里就会出现环find会无限递归fsck的目录树一致性检查也会崩掉。现代文件系统ext4、xfs、btrfs在内核层面直接禁止对目录做硬链接你敲ln dir1 dir2会得到ln: dir1: hard link not allowed for directory。只有文件系统自己在维护.和..时才会写这种记录。理解这一点你就知道为什么目录不能用硬链接不是一条死记硬背的规则而是文件系统自保的必然结果。1.3 软链接是一个内容为路径字符串的独立文件再看软链接这边。ln -s创建的软链接是一个真实存在的、独立的文件只不过它的 inode 类型位是lsymlink它的数据块里存的不是你的业务内容而是一段路径字符串。$ ln -s a.txt c.txt $ ls -li a.txt c.txt 262146 -rw-r--r-- 2 user user 6 Mar 12 10:21 a.txt 262147 lrwxrwxrwx 1 user user 5 Mar 12 10:21 c.txt - a.txt三个细节值得盯一下inode 号变了262147说明它有自己的 inode大小是 5正好等于字符串a.txt的长度权限显示lrwxrwxrwx这个 777 是假的是内核为了兼容老程序固定填的值软链接真正的权限永远由目标文件决定你用chmod改软链接的权限是改不动的。这个大小等于路径长度的特征很实用——当ls -l显示某个链接大小是 200 多说明它指向的路径很长反过来也能用来粗查某个链接是不是被写进了一个超长路径。同样是路径字符串还有一条更隐蔽的规则软链接里的相对路径是相对于链接所在的目录解析的不是相对于你敲命令时的当前目录也不是相对于目标所在位置。这条规则是无数本地好好的换台机器就断了的元凶后面我会用一个具体场景展开。2. 硬链接真正的价值不止省空间还有去重与快照很多人对硬链接的第一印象是省磁盘空间这个说法不算错但太浅。省空间只是表象硬链接真正的工程价值在于它提供了一种廉价的、原子性的、引用计数式的数据共享机制。你理解了引用计数就理解了为什么它能做增量备份、能做容器镜像分层、能做去重的包管理。2.1 同一份数据块的第二种访问路径硬链接指向同一个 inode就必然指向同一份数据块所以写 1GB 的内容再做 10 个硬链接磁盘占用还是 1GB只多几条目录记录。但更关键的是一致性你在任意一个名字上写入其他名字立刻看到新内容因为它们本来就是同一份数据。$ dd if/dev/zero ofbig.bin bs1M count100 $ ln big.bin big2.bin $ du -sh big.bin big2.bin 100M big.bin 100M big2.bin $ du -sh . # 目录总占用仍是 100M不是 200M 100M .注意这里du单独看每个文件都显示 100M但目录总量只有 100M——du默认对同一 inode 只计一次。这个特性在排查为什么磁盘用量和文件大小加起来对不上时很有用。另外df看的才是文件系统真实占用du在硬链接场景下会做去重这两个命令的口径差异在容器、镜像层、备份目录里经常让人怀疑人生。实操中还有一个容易踩的点编辑器和很多工具写文件是新建再重命名不是原地修改。也就是说你用 vim 改一个大文件保存可能这个 inode 已经被换掉了原来的硬链接还指向老 inode、老内容。所以硬链接适合读多写少、内容不变的场景日志归档、镜像层、备份不适合当作多个入口实时同步修改同一个文件的机制——想要那种行为用软链接指向目标文件反而更符合直觉因为软链接每次访问都会重新解析路径。2.2 引用计数的释放逻辑以及那个删了大文件磁盘没空出来的老问题硬链接删除一个名字只是把 inode 的链接计数减 1$ ln a.txt b.txt $ stat -c %h %n a.txt b.txt 2 a.txt 2 b.txt $ rm a.txt $ stat -c %h %n b.txt 1 b.txt # 数据还在因为计数还没归零只有当链接计数减到0、同时没有任何进程打开这个文件时内核才会真正释放数据块。这里就引出运维面试里高频的一道题rm掉一个大日志文件df看磁盘却没释放为什么因为文件被某个进程比如还在写的 nginx、java 进程持有文件描述符。此时链接计数已经是 0但打开计数不为 0数据块仍然被钉在磁盘上。用lsof L1或lsof | grep deleted能看到这类文件。正确的处理方式不是重启服务而是# 找出持有所删文件的进程 $ lsof L1 | grep deleted nginx 1234 root 5w REG 8,1 2147483648 262150 /var/log/nginx/access.log (deleted) # 方式一清空而不删除推荐服务无需重启 $ : /proc/1234/fd/5 # 方式二restart / reload 对应服务让它重新打开新文件 $ systemctl reload nginx这个问题的本质就是inode 的释放取决于两个计数都归零而链接计数这套机制正是硬链接的核心。所以我说硬链接不只是省空间的小技巧它是理解 Linux 文件生命周期的一把钥匙。2.3 用--link-dest做硬链接增量快照硬链接最有价值的工程用法之一是配合rsync做看起来像全量、实际只存增量的备份快照。思路很朴素每次备份前告诉rsync上一次的快照目录对没变的文件rsync不复制数据而是在新目录里创建一个指向旧快照同一 inode 的硬链接。#!/bin/bash # 每天一份快照未变化的文件用硬链接共享 SRC/data/app DST/backup/snapshots TODAY$(date %F) LAST$(ls -1 $DST | tail -n 1) mkdir -p $DST/$TODAY if [ -n $LAST ]; then rsync -a --delete --link-dest../$LAST $SRC/ $DST/$TODAY/ else rsync -a $SRC/ $DST/$TODAY/ fi rm -rf $DST/$(date -d -7 days %F) # 保留最近 7 天跑一段时间后你会看到每个快照目录du -sh看起来都是全量大小但整个$DST的总占用只比单份大一点点因为绝大多数文件是硬链接共享的。这里有两个必须注意的细节--link-dest的路径要用相对于目标目录的相对路径上面写的../$LAST就是这个原因用绝对路径在部分版本上行为不一致以及目标目录必须和--link-dest在同一文件系统上否则硬链接创建失败rsync会静默退化成真实复制你以为省了空间其实每份都是全量。验证方法就是对比任意两个快照里同一个文件的 inode 号$ ls -i /backup/snapshots/2024-03-11/app.conf /backup/snapshots/2024-03-12/app.conf 262200 /backup/snapshots/2024-03-11/app.conf 262200 /backup/snapshots/2024-03-12/app.conf # inode 相同说明共享数据同样的机制在容器镜像分层里也在用镜像的不同层共享未修改的文件本质上也是硬链接 / 写时复制的思路。理解了这一层很多为什么镜像层加起来比实际占用大的疑问也就自然解开了。3. 软链接为什么更像快捷方式路径解析的完整链路如果把硬链接比作给柜门贴第二张便签那软链接就是一张写着地址的纸条。纸条本身是个独立的东西它不共享数据所以它能做到硬链接做不到的两件事跨分区、指向目录。代价是它多了一层间接寻址多了一次路径解析也因此多了悬空和路径基准点这两类问题。3.1 一次open()背后内核对软链接做了什么当你用cat link打开一个软链接时内核做的事大致是先读到这个 symlink 类型的 inode取出它数据块里的路径字符串然后把这个字符串作为新的 lookup 起点重新走一遍路径查找直到找到最终的真实文件或目录。如果路径里还有软链接就继续嵌套解析内核有一个嵌套深度上限一般是 40 层左右超过会报ELOOP: Too many levels of symbolic links。你在系统里不小心搞出一个指向自己、或者 A-B-A 的环就会看到这个错误。$ ln -s loop loop $ cat loop cat: loop: Too many levels of symbolic links正因为每次访问都要重新解析路径软链接有硬链接没有的一个动态特性目标路径变了链接自动跟着变。这个特性是做配置切换、发布切换的基础。比如$ ls -l /usr/local/java lrwxrwxrwx 1 root root 20 Mar 12 10:40 /usr/local/java - /opt/jdk-17.0.9以后 JDK 升级到 21只需要重建这个软链接所有依赖/usr/local/java的脚本、环境变量、配置文件一个字都不用改。这是硬链接做不到的——硬链接绑死的是 inode不是路径你没法切换它指向谁只能再删再建而且一旦建成就锁死在那个 inode 上。3.2 悬空链接不是错误而是一种合法状态新手最常见的困惑删掉目标文件之后软链接还在ls里红底闪烁但rm它又删不掉其实它当然能删。悬空链接dangling link在文件系统层面完全合法——软链接只是一个含路径的文件路径存不存在跟它没关系。$ ln -s /tmp/nothing.txt ghost $ ls -l ghost lrwxrwxrwx 1 user user 16 Mar 12 10:50 ghost - /tmp/nothing.txt $ cat ghost cat: ghost: No such file or directory $ rm ghost # 正常删除删的是链接本身悬空链接在工程上有两种典型用途一是占位预先建好链接指向未来会上线的路径脚本可以统一按链接路径写二是标记下线把目标移走后保留链接起到这里曾经有东西的提示作用。但更多时候它是故障信号比如上面提到的场景本地测试时用的相对路径软链接迁移到服务器后基准点变了链接就悬空了。排查悬空链接有个很顺手的组合# 列出当前目录下所有失效的软链接 $ find . -xtype l ./conf/current ./lib/libfoo.so.1 # 看链接到底指向哪里不做解析 $ readlink ./conf/current ../releases/20240101/conf # 解析成绝对真实路径会报错说明悬空 $ readlink -f ./conf/current /home/app/releases/20240101/conf-xtype l是-type l的补充-type l找的是所有软链接而-xtype l在 find 的默认不跟随模式下找的是指向的目标类型为 l 的那些也就是断链。这个区别很多教程没讲清楚实际排查时非常省事。3.3 跨分区与指目录软链接的两大特权也是两大风险跨分区的能力来自它的本质——软链接只是个字符串文件它的 inode 和目标 inode 在不在同一个文件系统上完全无所谓。所以你可以把/data挂在一个大盘上然后在系统盘的/var/lib/app/data建一个软链接指过去应用完全无感。这在扩容、迁移数据目录时是最常见的做法# 应用默认数据目录在系统盘空间不够迁到大盘 $ systemctl stop myapp $ rsync -a /var/lib/myapp/ /data/myapp/ $ mv /var/lib/myapp /var/lib/myapp.bak $ ln -s /data/myapp /var/lib/myapp $ systemctl start myapp而硬链接在这个场景会直接失败$ ln /data/myapp/x.log /var/lib/myapp/x.log ln: failed to create hard link /var/lib/myapp/x.log /data/myapp/x.log: Invalid cross-device link风险也正来自这两个特权。软链接能指目录就意味着很多递归工具在遇到它时会有分歧rm -r不跟随软链接只删链接但rm -r link/带斜杠跟随find默认不跟随加-L才跟随tar默认打包链接本身加-h会跟随并把目标内容打进去。这些分歧就是事故的来源也是第 5 节要重点复盘的东西。4. 创建与删除的实操细节ln和rm到底做了什么命令本身很短但参数细节特别多而且很多错误默认行为会看起来成功。这一节把ln、ln -s、rm在链接场景下的行为逐个拆开重点讲清-f、-n、-T这几个参数的差异以及删除时该用什么姿势。4.1ln与ln -s的基本形式# 硬链接ln 目标 链接名 $ ln /data/a.txt /data/b.txt # 软链接ln -s 目标 链接名 $ ln -s /data/a.txt /data/c.txt # 省略链接名在当前目录以目标文件名创建链接 $ cd /tmp ln -s /data/logs $ ls -l /tmp/logs lrwxrwxrwx 1 user user 10 Mar 12 11:00 /tmp/logs - /data/logs这里有个长时间的坑ln也支持一次建多个链接ln 目标 链接1 链接2前提是链接都在同一目录但如果你把顺序搞反或者路径写成了不存在的目录ln的行为会很让人迷惑。所以我的习惯是永远显式写全两个参数路径用绝对路径宁可多敲几个字不给自己留排查时间。4.2 覆盖已有链接时-f、-n、-T的差异是事故高发区这是整篇文章我认为最值得反复强调的一段。假设/data/web/current已经是一个指向/data/web/releases/v1的软链接现在你想把它改成指向v2很自然会写$ ln -sf /data/web/releases/v2 /data/web/current如果current是指向目录的软链接在不加-n的情况下ln会认为你的目标是把链接放进 current 这个目录里于是结果变成/data/web/current/v2 - /data/web/releases/v2。你以为重建了链接实际上在目标目录里塞了一个新链接。这个行为在不同 coreutils 版本上还有差异部分版本会先删除再创建表现又不同所以依赖默认行为是不可靠的。正确的写法是加-n--no-dereference把指向目录的软链接当成普通文件来处理$ ln -sfn /data/web/releases/v2 /data/web/current $ ls -l /data/web/current lrwxrwxrwx 1 user user 26 Mar 12 11:10 /data/web/current - /data/web/releases/v2-T--no-target-directory也能解决问题它的语义是把链接名当成一个普通文件不要当目录$ ln -sfT /data/web/releases/v2 /data/web/current两者的差别很微妙-n是不跟随已存在的目录软链接-T是无论目标是不是目录都按文件处理。实操里我一般统一用-sfn因为它对链接名是软链接和链接名是真实目录两种情况都不会做出意外解引用更稳。记住一条经验所有用于发布切换、配置切换的软链接更新脚本都必须写-sfn或-sfT不要裸写-sf。顺带说一个更稳的替代思路——先建新链接再原子替换。这是零中断更新的标准做法$ ln -s /data/web/releases/v3 /data/web/.current.tmp $ mv -T /data/web/.current.tmp /data/web/currentmv在同目录内重命名是原子的nginx 之类正在读这个路径的进程不会读到链接不存在的中间态也不会出现半更新。生产环境的发布脚本我更推荐这个写法。4.3 删除链接rm删的永远是你指定的那个名字删除这块的规则其实很简单但必须说清楚因为它决定了会不会误删数据。rm link软链接删除软链接这个文件本身目标完全不受影响。rm file硬链接文件把该 inode 的链接计数减 1计数为 0 且无进程打开时才释放数据其他硬链接名不受影响。rm不能删除目录即使软链接指向目录rm symlink_to_dir也只删链接但rm -r symlink_to_dir同样只删链接因为rm遍历时不跟随符号链接。例外rm -r symlink_to_dir/结尾带斜杠会跟随软链接删掉目标目录里的内容。这就是开头那起事故的根因。# 安全只删链接 $ rm /data/web/current # 危险删的是目标目录里的内容 $ rm -rf /data/web/current/结尾那个斜杠的差别本质上是路径解析时是否强制要求它是一个目录——带斜杠时内核必须解析到真正的目录于是软链接被跟随了。这个坑不只在rm上存在chmod -R link/、chown -R link/、rsync src/ link/、cp -r link/ /tmp/都有类似行为。所以我的个人纪律是对任何可能是软链接的路径删除和递归修改前先ls -ld看一眼确认真实类型敲rm -rf时路径结尾绝不加斜杠。再加一条更实用的保险rm -rf --one-file-system可以防止跨越挂载点删除。如果链接指向的是另一个挂载点的目录这个参数能挡住一部分灾难值得写进你的常用命令里。5. 三个真实场景复盘错误是怎么一步步发生的概念讲完了但真正让人长记性的永远是事故。下面这三个场景我都亲身经历或参与排查过还原一下完整的排查链路你可以对照自己的操作习惯看看有没有相似的隐患。5.1 发布目录rm -rf加斜杠删掉的是整份 release回到开头那个事故。当时的现场是这样的/data/web/current - /data/web/releases/20240312运维想删掉旧链接重新指敲了rm -rf /data/web/current/。为什么这个命令会删掉 release 内容排查链路是这样走的先看ls -ld /data/web/current确认是软链接然后ls /data/web/releases/20240312发现目录空了再看dmesg没有触发只读重挂载排除文件系统损坏最后在 shell 历史里找到了那条带斜杠的rm -rf。结论很明确结尾斜杠让rm把软链接解析成了目录于是它在目录内部递归删除而不是删除链接本身。修复手段只有备份恢复。真正有效的预防是三件事一是把删除链接的操作统一换成语义更清楚的写法用rm -f /data/web/current不带斜杠或者干脆用unlink /data/web/current——unlink一次只能删一个文件且永远不递归用在删软链接场景上比rm -rf安全得多二是在发布脚本里用mv -T原子替换从流程上避免先删后建三是给/data/web这类目录加定期快照出事后能回滚。第三点听起来是兜底实际上是最值钱的一条因为人的操作习惯很难靠纪律 100% 约束住。5.2 灰度切换用软链接把流量从 v1 切到 v2这个场景是软链接指目录特权的正面用法值得完整走一遍。假如应用有多个版本目录前面挂一个稳定入口/data/app/ ├── releases/ │ ├── v1.0.0/ │ └── v2.0.0/ └── current - releases/v1.0.0配置里所有路径都写/data/app/current。升级时不用改任何配置$ ln -sfn /data/app/releases/v2.0.0 /data/app/current $ ls -l /data/app/current lrwxrwxrwx 1 app app 32 Mar 12 11:30 /data/app/current - /data/app/releases/v2.0.0这里有几个实操细节必须注意。第一应用如果是在启动时解析一次路径并长期持有文件描述符光切链接不够需要 reload 或重启让它重新打开文件nginx 的root指令在reload后会重新解析路径通常没问题但用了open_file_cache的话要留个心眼。第二Java 应用如果 classpath 写的是/data/app/current/lib/*切换后必须重启JVM 不会重新解析。第三版本目录的属主权限要和运行用户一致否则切过去之后应用读不到文件报的是权限错误反而容易误判成链接没生效。排查切了链接但行为没变这类问题我一般按这个顺序看ls -l current确认链接指向对不对readlink -f看解析后的真实路径lsof -p PID | grep current看进程打开的文件到底是哪个版本cat /proc/PID/root/...或直接比对进程启动时间。这四步走下来基本能定位是链接没切成功还是进程没重新加载。5.3 相对路径的基准点本地测试正常上服务器就断链最后这个坑更隐蔽因为它不会报错只是链接悄悄失效。有人这样建链接$ cd /opt/app/conf $ ln -s ../shared/app.conf app.conf这条命令创建的链接内容是../shared/app.conf解析基准是链接所在的目录/opt/app/conf所以实际指向/opt/app/shared/app.conf。看起来没问题。但如果他后来把整个/opt/app用rsync同步到另一台机器的/srv/app链接跟着过去了内容是相对路径所以在新位置指向/srv/app/shared/app.conf——这个反而还是对的。真正出问题的是另一种情况他手工把链接创建在别的位置或者把链接和目标的相对位置关系改变了# 在别处创建指向同一目标的链接相对路径的基准点变了 $ ln -s ../shared/app.conf /etc/app.conf # 这条链接实际指向 /shared/app.conf几乎必然悬空所以我的原则是跨目录、跨机器、需要长期稳定的链接一律用绝对路径。相对路径只在链接和目标会作为一个整体一起移动时才用比如源码树内部的lib/libfoo.so.1 - libfoo.so.1.2.3这种场景相对路径反而更稳因为整体搬走之后链接依然有效。验证方式很简单创建完立刻用readlink -f确认解析结果$ ln -s ../shared/app.conf app.conf $ readlink -f app.conf /opt/app/shared/app.conf如果readlink -f报No such file or directory说明链接已经是断的别等到应用启动失败才发现。6. 一张对比表管住选型以及我踩坑后总结的几条纪律概念和场景都过了一遍最后给一个可以贴在工位上的对比表以及几条我自己用血泪换来的操作纪律。选型这件事没必要纠结把下面这张表记住绝大多数场景的答案就出来了。对比维度硬链接软链接本质同一 inode 的另一个名字独立文件内容是目标路径字符串inode 号与目标相同独立与目标不同占用空间仅目录记录几十字节一个 inode 路径字符串长度跨文件系统不支持报 Invalid cross-device link支持指向目录不允许普通用户允许删除目标后其他链接仍可正常访问数据变成悬空链接访问报错权限与目标共享改一个全改显示 777实际由目标决定创建命令ln 目标 链接名ln -s 目标 链接名覆盖更新需先删后建ln -sfn 目标 链接名典型用途备份快照、镜像层去重、日志归档版本切换、配置切换、跨分区数据目录从这张表里其实能读出一条选型主线需要多个名字指向同一份不变数据就用硬链接需要一个入口动态指向可变的路径就用软链接。备份快照属于前者发布切换属于后者容器镜像层属于前者/usr/local/java、/etc/alternatives这类属于后者。系统里的/etc/alternatives就是一整套软链接切换机制Java、Python 之类多版本共存的方案本质都是它。6.1 我在实际操作中固化的六条纪律第一条创建链接前先确认目标存在ls -l看一眼不存在就大概率会造出悬空链接别指望事后发现。第二条更新指向目录的软链接一律用ln -sfn或ln -sfT禁止裸写-sf。生产发布脚本里我更推荐ln -s建临时名 mv -T原子替换的组合。第三条删除软链接用unlink它在语义上只删一个名字、从不递归比rm -rf安全一个数量级。必须用rm时路径结尾绝不加斜杠。第四条递归操作前先看类型ls -ld一眼-R类的命令chmod -R、chown -R、rm -r、rsync -a碰到软链接都要想一下是不是跟随需要严格按物理路径处理时加-Pls -P、cp -P、du -P、find -P需要跟随就明确加-L。第五条跨机器、跨挂载点的链接用绝对路径并且创建后立刻用readlink -f验证解析结果。这条能省掉大量明明链接在、应用就是读不到的排查时间。第六条把find . -xtype l加进巡检脚本定期扫一遍服务器上的断链。这东西平时不出问题一旦被应用在启动路径上引用到就是半夜的电话。顺手记一句lsof L1那是排查删了文件磁盘不释放的固定动作。最后再分享一个小技巧。如果你需要对比两个目录是不是真的共享了数据比如验证备份到底有没有用上硬链接不用逐个ls -i直接这样# 找出两个目录里 inode 相同的文件输出即共享数据 $ find /backup/snapshots/2024-03-11 -type f -printf %i %p\n | sort /tmp/day1.txt $ find /backup/snapshots/2024-03-12 -type f -printf %i %p\n | sort /tmp/day2.txt $ join -j1 (awk {print $1} /tmp/day1.txt | sort -u) (awk {print $1} /tmp/day2.txt | sort -u) | wc -l这个数字接近文件总数说明快照确实是硬链接共享的如果数字很小说明--link-dest没生效磁盘正在被全量拷贝吃掉。这套校验我在接手别人的备份脚本时用过很多次比看脚本里的参数靠谱得多——毕竟磁盘用量不会骗人。
返回列表