Linux 硬链接与符号链接:同一份内容,为什么改名后的结果不同
从“两个名字”与“另存一条路径”开始
同一份资料需要从两个位置访问时,复制文件、创建硬链接和创建符号链接会得到不同结果。硬链接让两个目录项指向同一个文件对象,写入任意一个名称,另一个名称读到的内容也会变化。它们地位相同,先创建的那个名字并没有额外的“原件”身份。
符号链接自身则是另一种文件对象,保存一段目标路径。通常的读取会沿这段路径找到目标,因此它可以跨文件系统,也可以在目标不存在时建立。目标改名后,旧路径不会自动更新;目标名称以后被另一个文件使用,符号链接又可能读到完全不同的内容。
用设备与 inode 判断是不是同一对象
下面在 Linux 的本地文件系统中运行,只需要 Bash 和 GNU coreutils。所有写入与删除都限定在当前目录新建的临时目录,不需要提升权限。stat 的设备号与 inode 组合用于比较对象,硬链接计数用于观察还有几个目录项指向它;不要跨文件系统只比较一个 inode 数字。
GNU stat 默认检查符号链接本身,带 -L 才跟随到目标。示例同时核对这两个视角,避免看到内容相同就误认成同一个文件。输出采用断言通过后的固定说明,不写死设备号或 inode,因为这些数值会随机器和每次创建而变化。
AI生成概念示意图,非真实界面
( set -eu work=$(mktemp -d './link-demo.XXXXXX') trap 'rm -rf -- "$work"' EXIT printf 'one\n' > "$work/source" ln -- "$work/source" "$work/hard" ln -s -- source "$work/shortcut" object_id=$(stat -c '%d:%i' -- "$work/source") [[ $(stat -c '%d:%i' -- "$work/hard") == "$object_id" ]] [[ $(stat -c '%h' -- "$work/source") == 2 ]] [[ $(stat -c '%d:%i' -- "$work/shortcut") != "$object_id" ]] [[ $(stat -L -c '%d:%i' -- "$work/shortcut") == "$object_id" ]] printf 'same object: yes; hard links: 2\n' printf 'changed\n' > "$work/hard" [[ $(cat -- "$work/source") == changed ]] [[ $(cat -- "$work/shortcut") == changed ]] printf 'write through hard: changed\n' mv -- "$work/source" "$work/moved" [[ -L "$work/shortcut" && ! -e "$work/shortcut" ]] [[ $(readlink -- "$work/shortcut") == source ]] [[ $(stat -c '%d:%i' -- "$work/moved") == "$object_id" ]] printf 'after rename: shortcut is dangling\n' printf 'replacement\n' > "$work/source" [[ $(cat -- "$work/shortcut") == replacement ]] [[ $(cat -- "$work/hard") == changed ]] [[ $(stat -c '%d:%i' -- "$work/source") != "$object_id" ]] printf 'recreated name: shortcut=replacement; hard=changed\n' rm -- "$work/shortcut" [[ $(cat -- "$work/source") == replacement ]] exec 3< "$work/hard" rm -- "$work/moved" "$work/hard" IFS= read -r saved <&3 [[ "$saved" == changed ]] printf 'open descriptor after last unlink: %s\n' "$saved" exec 3<&- printf 'all link checks passed\n' )
首次检查显示两个硬链接名称属于同一对象,计数为二;通过 hard 写入后,source 和 shortcut 都读到 changed。接着把 source 改名为 moved,shortcut 仍保存 source 这段文字,因此变成悬空链接,而 hard 与 moved 继续正常指向原对象。
同名重建会让路径重新解释
随后在 source 这个名字下新建 replacement 内容,shortcut 又能读取了,但它读到的是新对象。hard 仍然读到 changed,两者已经分离。这个实验说明,符号链接跟随的是名称解析结果,不是对某个历史文件对象的永久绑定;判断是否“恢复”必须核对实际内容和身份。
相对链接的路径以链接所在目录为起点解释,不以运行读取命令时的工作目录为起点。本文把链接和目标放在同一目录,因此 source 足够明确。若把链接单独移动到别处,这段相对路径的含义也会改变;如果整个目录一起移动且内部关系保持不变,内部相对链接通常仍能工作。
删除名称与结束对象生命周期分开看
删除 shortcut 只移除链接本身,source 的 replacement 内容还在。对硬链接也是一样,移除一个名称只是减少一个目录项,剩余名称仍能访问原对象。例子最后先打开 hard,再删除 hard 与 moved,让原对象不再有路径名称,仍能从已打开的描述符中读出 changed。
关闭这个描述符后,演示不再持有原对象的打开引用。普通文件最后一个硬链接被删除且相关打开引用都释放后,文件系统才能回收相应存储;符号链接的存在不会替目标保留这种生命线。这里验证的是读取行为,没有测量磁盘空间,也不把删除描述成安全擦除。
选择链接方式时,先问内容应不应该一起变
硬链接不是独立备份,误写会从每个名称看到,权限和所有者等文件属性也属于共享对象。它不能跨文件系统,普通使用也不应尝试给目录创建硬链接。需要一份可独立编辑的材料时,应选择符合需求的复制方案,并另行验证副本,而不是因为链接创建快就替代复制。
还要区分就地改写与替换路径。某些编辑器保存时会先写一个新文件,再把新文件放到原名称上;这时别的硬链接仍留在旧对象上,不一定跟着新内容变化。排查“同一文件为何不同步”时,应核对保存方式和对象身份,不能仅凭名称判断。
复查时按四步观察:两个名称是否共享身份、写入是否可见、目标改名后能否访问、重建同名目标后读到了什么。把这些结果与业务希望绑定的对象或路径对照,才能选出合适的链接方式。


