ln 是 Linux 文件系統層面的「別名機制」,通過 **硬鏈接** 與 **符號鏈接 (軟鏈接)** 兩種截然不同的實現,分別服務於 **空間節省/原子替換** 與 **跨文件系統/目錄鏈接/版本切換** 等不同場景。本文從 inode 本質、行為差異、實戰陷阱、運維模式四個維度徹底講透。
| 維度 | 硬鏈接 ln src dst | 符號鏈接 ln -s src dst |
|---|---|---|
| inode 號 | 相同 (指向同一 on-disk inode) | 不同 (新建 inode,數據區存儲目標路徑字串) |
| 文件類型 | 普通文件 (無特殊標識) | 符號鏈接文件 (l 類型,ls -l 顯示 ->) |
| 跨文件系統 | 不可 (inode 僅在單 FS 內唯一) | 可 (路徑字串無 FS 邊界) |
| 目標類型 | 僅文件 (目錄需 root + -d 且極危險) | 文件/目錄/不存在路徑/設備節點 |
| 目標刪除後 | 數據實體保留 (鏈接計數 -1,歸零才回收) | 成為**懸空鏈接** (dangling),讀取報 No such file |
| 修改內容 | 所有硬鏈接**即時可見** (同一數據塊) | 修改目標文件內容可見;替換目標文件 (mv/覆寫) 行為視編輯器而定 |
| 權限/屬性 | 與源文件完全一致 (同一 inode) | 鏈接本身權限固定 lrwxrwxrwx,實際受目標文件權限控制 |
| stat 輸出 | Links 計數 > 1 | Links = 1,文件類型 symbolic link |
ln [OPTIONS] TARGET LINK_NAME
ln [OPTIONS] TARGET... DIRECTORY
# 核心參數
-s, --symbolic # 創建符號鏈接 (軟鏈接)
-f, --force # 目標存在時強制移除後重建
-i, --interactive # 覆蓋前詢問
-n, --no-dereference # 目標為符號鏈接時視為普通文件 (不遞歸)
-b, --backup[=CONTROL] # 覆蓋前備份 (suffix/version-control)
-v, --verbose # 顯示操作詳情
-t, --target-directory=DIR # 指定目標目錄 (批量創建)
# 應用發布目錄結構
/opt/app/
releases/
v1.0.0/
v1.1.0/
v1.2.0/
current -> releases/v1.1.0 # 符號鏈接
# 發布腳本片段
NEW_RELEASE="/opt/app/releases/v1.2.0"
CURRENT_LINK="/opt/app/current"
# 原子切換 (rename 保證原子性)
ln -sfn "$NEW_RELEASE" "$CURRENT_LINK"
# -f 強制覆蓋,-n 視鏈接為文件不遞歸,-s 符號鏈接
# 瞬間完成,無中間狀態,回滾同理 ln -sfn v1.1.0 current
# 日誌歸檔去重
/var/log/app/
access.log # 實體文件
access.log.20240101 # 硬鏈接指向同一 inode
access.log.20240102 # 硬鏈接
# 刪除任一硬鏈接,數據不丟失;僅當所有硬鏈接刪除且無進程持有才回收磁盤
# 適用:備份快照、鏡像倉庫去重、構建產物多環境共享
# 多服務共享同一配置文件
/etc/nginx/nginx.conf -> /etc/app/config/nginx.conf
/etc/openresty/nginx.conf -> /etc/app/config/nginx.conf
# 修改源配置,所有服務 reload 即生效
/usr/lib/libfoo.so -> libfoo.so.2
/usr/lib/libfoo.so.2 -> libfoo.so.2.4.1
/usr/lib/libfoo.so.2.4.1 # 實體
# 編譯時 -lfoo 找 .so,運行時 ld.so 找 .so.2 (SONAME)
| 操作 | 預期 | 實際後果 | 正確做法 |
|---|---|---|---|
ln -s /dir/link 目標為目錄 | 進入 link 見目錄內容 | 正常工作 | 正確,推薦目錄用符號鏈接 |
ln /dir/link 硬鏈接目錄 | 報錯 hard link not allowed for directory | root 加 -d 可強行,但會破壞 FS 結構 (循環、.. 指向錯誤) | 絕不硬鏈接目錄 |
mv target new_target 移動符號鏈接目標 | 鏈接自動指向新位置 | 鏈接變懸空 (存的是舊路徑字串) | 用 mount --bind 或調整部署流程 |
cp -L link dest 複製符號鏈接 | 複製鏈接文件 | 複製**目標文件內容** (解引用) | 需保留鏈接用 cp -P / cp -d |
tar czf pkg.tar.gz link 打包符號鏈接 | 打包鏈接 | 默認打包目標文件 (解引用) | tar -h / --dereference 顯式控制 |
rsync link/ dest/ 同步符號鏈接 | 同步鏈接 | 默認同步目標內容 | rsync -l (links) 保留符號鏈接,-L 解引用 |
| 刪除源文件後硬鏈接數據丟失 | 數據保留 | 數據保留 (只要硬鏈接計數 > 0) | 理解硬鏈接 = 共享 inode,放心刪源 |
# 判斷文件類型
file link_name
# /path/link: symbolic link to /real/target
# /path/hard: regular file
# 查找所有硬鏈接 (同一 inode)
find / -xdev -inum $(stat -c %i target_file) 2>/dev/null
# 查找斷開的符號鏈接
find / -xtype l 2>/dev/null
# 或 find -L / -type l
# 統計硬鏈接數
stat target_file | grep Links
# Links: 3
硬鏈接 = 同 inode,不跨 FS,不鏈目錄,刪源數據在;
符號鏈接 = 存路徑,跨 FS 隨意鏈,刪源變懸空,部署切換首選。
ls -l 看首字符:- 普通/硬鏈,l 符號鏈;stat 看 Links 數。
原子切換 ln -sfn,備份打包留意 -h/-L,rsync 加 -l 保鏈接。
徹底理解 ln 兩種鏈接本質,讓文件系統操作從「試錯」進化為「確定性工程」。