ln -s 建的软链接,挪个位置就失效;硬链接和软链接的区别,问十个人有八个说不清。这两个问题的根源是同一个概念——inode。把它理解了,ln 的所有行为都变得顺理成章。

这是 Linux 命令系列的一篇,承接 Linux 常见命令与工具地图ln(link)用来给文件创建链接——同一份内容的另一个访问入口,分硬链接和软链接(符号链接)两种,行为差别很大。

inode:文件名和文件内容是分开存的

文件系统里,一个文件其实是两部分:inode 存文件的实际内容和元数据(大小、权限、时间戳),目录里的文件名只是一个指向 inode 的入口。名字和内容是分离的——这就是理解链接的全部前提。

1
2
3
4
5
6
7
8
ls -i a.txt
# -i:显示文件的 inode 编号
# 83502402 a.txt

ls -l a.txt
# -rw-r--r-- 2 baizhe staff 6 ...
# 第二列的数字就是"链接数"——有多少个名字指向这个 inode
# ls/stat 那篇文章里没展开的这一列,谜底在这

stat 那篇 输出里的 Inode 字段、ls -l 的第二列,说的都是这套机制。

硬链接:同一个 inode 的多个名字

1
2
3
4
5
6
7
8
ln a.txt b.txt
# 语法:ln 已有文件 新链接名——目标在前,链接名在后
# b.txt 是指向同一个 inode 的另一个名字,不是拷贝

ls -li a.txt b.txt
# 83502402 -rw-r--r-- 2 ... a.txt
# 83502402 -rw-r--r-- 2 ... b.txt
# inode 编号完全相同,链接数都变成了 2——两个名字,一份内容

因为两个名字完全平等地指向同一份内容,”删除”的语义也变了:

1
2
3
4
5
rm a.txt
cat b.txt
# 输出 hello——内容还在!
# rm 删的从来不是文件内容,只是目录里的一个名字(链接数减 1)
# 链接数归零、且没有进程持有句柄时,内容才真正被释放

这和 lsof 那篇 讲的”rm 掉的文件还占着磁盘”是同一个原理——名字没了不等于内容没了。

硬链接有两个硬性限制:不能跨文件系统(inode 编号只在单个文件系统内有意义,另一块盘上无从指起),不能对目录建(会造成目录树成环)。要跨盘、要链目录,就得用软链接。

软链接:存着路径的独立小文件

1
2
3
4
5
6
7
ln -s a.txt link.txt
# -s:symbolic,创建软链接(符号链接)
# link.txt 是一个独立的小文件,有自己的 inode,内容就是一串路径字符串

ls -l link.txt
# lrwxr-xr-x ... link.txt -> a.txt
# 权限位开头的 l 表示这是软链接,-> 显示它指向哪里

软链接本质是”存着一个路径的文件”,访问它时系统顺着路径再找真正的目标。所以它没有硬链接的限制——可以跨文件系统、可以指向目录——代价是多了一层间接:目标没了,链接就悬空了:

1
2
3
4
rm a.txt
cat link.txt
# No such file or directory——目标被删,软链接变成"悬空链接"(dangling)
# 链接文件本身还在,只是指过去扑了个空;硬链接不存在这个问题

软链接最大的坑:相对路径

软链接里存的路径如果是相对的,它相对的是”链接所在的目录”,不是你执行 ln 命令时所在的目录。这是软链接失效的头号原因:

1
2
3
4
5
6
7
8
9
10
11
# 当前目录下有 original.txt,想在子目录 sub/ 里建个链接指向它:
ln -s original.txt sub/link2.txt
# 直觉上没问题——original.txt 就在当前目录啊
# 实际上悬空了:链接存的字符串是 "original.txt",
# 解析时相对的是链接所在的 sub/ 目录,即 sub/original.txt——不存在

cat sub/link2.txt
# No such file or directory

readlink sub/link2.txt
# 输出 original.txt——readlink 显示链接里存的原始路径,验证问题的最快方法

两种正确写法:

1
2
3
4
5
6
ln -s /full/path/to/original.txt sub/link2.txt
# 解法一:目标写绝对路径,链接放哪都不会歪——脚本里建链接推荐这种

ln -s ../original.txt sub/link2.txt
# 解法二:按"从链接所在目录出发"来写相对路径
# 相对路径的好处是整个目录树挪走后链接关系依然成立

软链接的两个典型场景

1
2
3
4
5
6
7
ln -s /opt/app/releases/v2.3.1 /opt/app/current
# 版本切换:程序永远访问 current,发新版只需要把链接指向新目录
# 切换是原子的、回滚只要把链接指回去——大量部署工具的标准做法

mv /var/lib/docker /data/docker && ln -s /data/docker /var/lib/docker
# 腾磁盘空间:把占地大的目录挪到空间充裕的盘,原位置留个软链接
# 应用无感知,路径照旧能访问

硬链接的日常场景少一些,最常见的是备份工具的增量快照——同一份没变过的文件在多个快照目录里用硬链接共享,几十份”全量备份”实际只占一份多的空间。

常用操作速查表

命令/概念 说明
ln 目标 链接名 硬链接:同 inode 的另一个名字,语法目标在前
ln -s 目标 链接名 软链接:存路径的独立文件,可跨盘、可链目录
ls -i / ls -l 第二列 查看 inode 编号 / 指向该 inode 的链接数
硬链接删除行为 rm 只删名字,链接数归零内容才释放
软链接删除行为 删目标后链接悬空;删链接不影响目标
硬链接限制 不能跨文件系统、不能对目录
软链接相对路径 相对于链接所在目录解析,不是执行命令的目录
readlink 链接 查看链接里存的原始路径,排查悬空第一步
ls -l 开头的 l 权限位首字母 l 即软链接,-> 显示指向

写到这里

一句话总结两种链接:硬链接是”同一个 inode 的多个平等名字”,软链接是”存着路径的独立小文件”。删除行为、跨盘限制、悬空问题,全都能从这两句话推出来。日常用得最多的是软链接的版本切换和目录搬迁,记住相对路径那个坑(相对于链接所在目录),基本就不会再踩雷了。