Hard Link、Symbolic Link 與 find 指令指南
Hard Link、Symbolic Link 與 find 指令指南
Linux 檔案系統中,檔名、inode 與實際資料可以簡化成:
檔名 → inode → 實際資料內容
- 檔名:儲存在目錄中的名稱。
- inode:記錄檔案權限、擁有者、大小、資料區塊位置等 metadata。
- 資料內容:真正儲存在磁碟上的內容。
Hard Link 與 Symbolic Link 的主要差異,在於它們如何指向目標檔案。
1. Hard Link
Hard Link 中文通常稱為「硬連結」。
Hard Link 會建立另一個檔名,並且直接指向相同的 inode:
file.txt ──────┐
├── inode 12345 ── 實際資料
file-hard.txt ─┘
建立 Hard Link:
ln file.txt file-hard.txt
查看 inode:
ls -li file.txt file-hard.txt
可能得到:
12345 -rw-r--r-- 2 mike mike 12 Jul 8 09:00 file.txt
12345 -rw-r--r-- 2 mike mike 12 Jul 8 09:00 file-hard.txt
兩個檔案的 inode 相同:
file.txt inode 12345
file-hard.txt inode 12345
其中 link count 2 表示這個 inode 目前有兩個 Hard Link。
1.1 Hard Link 的特性
共享相同 inode
兩個檔名實際上是同一份檔案資料。
修改其中一個:
echo "hello" >> file-hard.txt
另一個也會看到相同內容:
cat file.txt
刪除其中一個檔名不影響其他 Hard Link
rm file.txt
cat file-hard.txt
file-hard.txt 仍然可以正常讀取。
rm 只會移除「檔名與 inode 的關聯」。只有當 inode 的 Hard Link 數量變成零,並且沒有任何程序正在使用該檔案時,檔案資料才會真正被釋放。
通常不能跨檔案系統
Hard Link 必須指向同一個檔案系統內的 inode,因此不能跨越不同 partition 或 mount point。
ln /home/mike/file.txt /mnt/usb/file-hard.txt
可能出現:
Invalid cross-device link
一般使用者不能建立目錄 Hard Link
為避免目錄結構形成循環,Linux 通常不允許一般使用者替目錄建立 Hard Link。
ln existing-directory directory-hard-link
通常會失敗。
2. Symbolic Link
Symbolic Link 簡稱 Symlink,中文通常稱為「符號連結」或「軟連結」。
Symbolic Link 是一個獨立檔案,檔案內容儲存另一個檔案或目錄的路徑:
file-link.txt
│
└── 儲存路徑:/home/mike/file.txt
│
└── inode 12345 ── 實際資料
建立 Symbolic Link:
ln -s file.txt file-link.txt
查看:
ls -li file.txt file-link.txt
可能得到:
12345 -rw-r--r-- 1 mike mike 12 Jul 8 09:00 file.txt
67890 lrwxrwxrwx 1 mike mike 8 Jul 8 09:01 file-link.txt -> file.txt
兩者 inode 不同:
file.txt inode 12345
file-link.txt inode 67890
file-link.txt 只儲存了 file.txt 這個路徑。
2.1 Symbolic Link 的特性
Symbolic Link 有自己的 inode
Symbolic Link 本身也是一個獨立檔案:
Symbolic Link inode
↓
儲存目標路徑字串
↓
目標檔案 inode
原始檔刪除後會成為 Broken Link
rm file.txt
cat file-link.txt
會出現:
No such file or directory
此時 Symbolic Link 仍然存在,但它指向的目標已不存在,稱為 Broken Symbolic Link。
可以跨檔案系統
因為 Symbolic Link 儲存的是路徑,而不是直接指向 inode,所以可以跨 partition 或 mount point:
ln -s /home/mike/file.txt /mnt/usb/file-link.txt
可以連結目錄
ln -s /home/mike/projects ~/projects-link
使用時:
cd ~/projects-link
可以使用相對路徑或絕對路徑
使用絕對路徑:
ln -s /home/mike/file.txt file-link.txt
使用相對路徑:
ln -s ../file.txt file-link.txt
相對路徑是相對於 Symbolic Link 所在目錄,不是建立連結時 Shell 的工作目錄。
3. Hard Link 與 Symbolic Link 比較
| 項目 | Hard Link | Symbolic Link |
|---|---|---|
| 中文名稱 | 硬連結 | 符號連結、軟連結 |
| 建立方式 | ln source target |
ln -s source target |
| inode | 與原檔相同 | 有自己的 inode |
| 儲存內容 | 直接指向相同 inode | 儲存目標路徑 |
| 修改內容 | 所有 Hard Link 都會看到變更 | 透過路徑存取目標 |
| 原始檔刪除 | 其他 Hard Link 仍可使用 | 變成 Broken Link |
| 跨檔案系統 | 不可以 | 可以 |
| 連結目錄 | 一般不允許 | 可以 |
| 是否有原始與副本 | 沒有真正的原始與副本之分 | 有 Link 與 Target 的關係 |
| 常見用途 | 多個檔名共享相同資料 | 快捷入口、設定檔、版本切換 |
4. 如何判斷 Symbolic Link
使用:
ls -l
Symbolic Link 的權限開頭是 l:
lrwxrwxrwx 1 mike mike 28 Jul 8 09:00 wiki-query -> /opt/skills/wiki-query
查看連結儲存的原始路徑:
readlink wiki-query
查看解析後的完整絕對路徑:
readlink -f wiki-query
判斷某個路徑是否為 Symbolic Link:
test -L wiki-query && echo "This is a symbolic link"
5. 如何判斷 Hard Link
Hard Link 沒有特殊檔案類型,它看起來就是一般檔案。
可以比較 inode:
ls -li file.txt file-hard.txt
或使用:
stat -c '%i %n' file.txt file-hard.txt
輸出:
12345 file.txt
12345 file-hard.txt
inode 相同,代表它們是同一個檔案的 Hard Link。
搜尋相同 inode 的所有檔案:
find /path/to/search -xdev -samefile file.txt
或先取得 inode:
inode=$(stat -c '%i' file.txt)
find /path/to/search -xdev -inum "$inode"
-xdev 表示不要跨越到其他檔案系統。
6. find 對 Symbolic Link 的處理方式
GNU find 提供三種 Symbolic Link 處理模式:
-P 不跟隨 Symbolic Link
-H 只跟隨命令列直接指定的 Symbolic Link
-L 跟隨所有 Symbolic Link
其中 -P 是預設值。
這三個選項應放在搜尋起始路徑之前:
find -L ~/.agents/skills -name SKILL.md
標準結構:
find [symlink options] [起始路徑] [搜尋條件]
例如:
find -L /path/to/search -type f -name '*.md'
7. find -P:不跟隨 Symbolic Link
-P 是 find 的預設行為。
find -P ~/.agents/skills -name SKILL.md
等同於:
find ~/.agents/skills -name SKILL.md
假設目錄結構如下:
~/.agents/skills/
└── wiki-query -> /opt/obsidian-wiki/skills/wiki-query
真正的檔案位於:
/opt/obsidian-wiki/skills/wiki-query/SKILL.md
執行:
find ~/.agents/skills -name SKILL.md
可能找不到任何結果。
原因是 find 看到 ~/.agents/skills/wiki-query 是 Symbolic Link 後,不會進入它所指向的目錄。
7.1 適合使用 -P 的情況
只想搜尋目前實際的目錄樹
find -P /srv/config -type f
避免重複掃描
如果多個 Symbolic Link 指向同一個大型目錄,使用 -L 可能會重複搜尋相同內容。
避免意外跨越到其他目錄
例如:
/project/logs -> /var/log/production
如果不想讓搜尋進入 /var/log/production,就使用預設的 -P。
尋找 Symbolic Link 本身
find -P ~/.agents/skills -type l
這會列出 Symbolic Link,而不是進入它們指向的目錄。
8. find -L:跟隨所有 Symbolic Link
-L 會跟隨搜尋過程中遇到的所有 Symbolic Link。
find -L ~/.agents/skills -name SKILL.md
假設:
~/.agents/skills/wiki-query
-> /opt/obsidian-wiki/skills/wiki-query
find -L 會進入實際目標目錄,因此可以找到:
~/.agents/skills/wiki-query/SKILL.md
8.1 適合使用 -L 的情況
搜尋內容位於 Symbolic Link 目錄內
例如 OpenCode skills:
find -L ~/.agents/skills \
-mindepth 2 \
-maxdepth 2 \
-type f \
-name SKILL.md
將 Symbolic Link 當成一般目錄使用
~/current-project -> ~/projects/project-v2
find -L ~/current-project -type f -name '*.ts'
搜尋被連結的共享目錄
/app/config -> /shared/config
find -L /app -type f -name '*.yaml'
8.2 使用 -L 的注意事項
可能產生循環
/a/b/back -> /a
目錄形成:
/a
└── b
└── back -> /a
執行:
find -L /a
GNU find 通常會偵測並顯示警告:
File system loop detected
可能搜尋到預期外的大型目錄
例如:
/project/archive -> /mnt/large-storage/archive
執行:
find -L /project
可能連整個大型儲存空間都被掃描。
9. find -H:只跟隨起始路徑的 Symbolic Link
-H 只跟隨「命令列直接指定為起始路徑」的 Symbolic Link。
例如:
find -H ~/.agents/skills/wiki-query -name SKILL.md
因為 ~/.agents/skills/wiki-query 本身是命令列直接指定的起始路徑,所以 find 會進入它指向的目錄。
但是:
find -H ~/.agents/skills -name SKILL.md
通常找不到 symlink 目錄內的 SKILL.md。
原因是命令列直接指定的是 ~/.agents/skills,而它是一個普通目錄。搜尋過程中才遇到的 wiki-query 不會被 -H 跟隨。
9.1 -H 使用範例
假設:
~/current -> ~/projects/project-a
~/projects/project-a/vendor -> /shared/vendor
執行:
find -H ~/current -type f
行為是:
- 跟隨
~/current,因為它是命令列指定的起始路徑。 - 進入
~/projects/project-a。 - 不跟隨裡面的
vendorSymbolic Link。
-H 適合:
- 起始路徑本身可能是 Symbolic Link。
- 但不想跟隨目錄樹內其他 Symbolic Link。
10. -P、-H、-L 行為比較
假設:
skills/
├── local-skill/
│ └── SKILL.md
└── wiki-query -> /opt/wiki-query
而:
/opt/wiki-query/
└── SKILL.md
使用 -P
find -P skills -name SKILL.md
結果:
skills/local-skill/SKILL.md
不會進入 skills/wiki-query。
使用 -L
find -L skills -name SKILL.md
結果:
skills/local-skill/SKILL.md
skills/wiki-query/SKILL.md
使用 -H
find -H skills -name SKILL.md
結果:
skills/local-skill/SKILL.md
因為 skills 本身不是 Symbolic Link。
若直接指定:
find -H skills/wiki-query -name SKILL.md
結果:
skills/wiki-query/SKILL.md
11. 為什麼 Shell Glob 可以找到,但 find 找不到
以下 Shell glob 通常可以解析 Symbolic Link:
ls ~/.agents/skills/*/SKILL.md
或:
for skill_file in ~/.agents/skills/*/SKILL.md; do
echo "$skill_file"
done
原因是 Shell 會展開:
~/.agents/skills/wiki-query/SKILL.md
作業系統在解析完整路徑時,會跟隨中間的 wiki-query Symbolic Link。
但:
find ~/.agents/skills -name SKILL.md
是由 find 自己遍歷目錄。因為預設採用 -P,所以它不會進入搜尋過程中遇到的 Symbolic Link 目錄。
可以簡化理解成:
Shell glob:
直接存取完整路徑,路徑解析時會處理 Symbolic Link。
find -P:
自行遍歷目錄,看到 Symbolic Link 後不進入目標目錄。
12. 尋找 Broken Symbolic Link
可以使用:
find /path/to/search -xtype l
例如:
find ~/.agents/skills -xtype l -print
也可以用 Shell 檢查:
for link in ~/.agents/skills/*; do
if [ -L "$link" ] && [ ! -e "$link" ]; then
echo "Broken link: $link -> $(readlink "$link")"
fi
done
其中:
-L "$link"
確認路徑是 Symbolic Link。
! -e "$link"
確認它指向的目標不存在。
13. -type l 與 -xtype l
尋找 Symbolic Link 本身
在預設 -P 模式下:
find -P /path/to/search -type l
可以找到所有 Symbolic Link。
尋找 Broken Symbolic Link
find -P /path/to/search -xtype l
-xtype 會根據 Symbolic Link 的目標類型判斷。對 Broken Link 而言,目標無法解析,因此 -xtype l 常用來找失效連結。
14. OpenCode Skills 實際案例
這是 OpenCode Autocomplete 指南 中「為什麼使用 Shell Glob 而不是普通 find」問題的完整原理版本。
假設 OpenCode skills 目錄如下:
~/.agents/skills/
├── wiki-query -> /home/mike/repos/obsidian-wiki/.skills/wiki-query
├── wiki-ingest -> /home/mike/repos/obsidian-wiki/.skills/wiki-ingest
└── wiki-status -> /home/mike/repos/obsidian-wiki/.skills/wiki-status
每個目標目錄中都有:
SKILL.md
使用預設 find:
find ~/.agents/skills \
-mindepth 2 \
-maxdepth 2 \
-name SKILL.md
可能沒有結果。
原因是 find 預設使用 -P,不會進入 wiki-query、wiki-ingest、wiki-status 這些 Symbolic Link 目錄。
正確指令:
find -L ~/.agents/skills \
-mindepth 2 \
-maxdepth 2 \
-type f \
-name SKILL.md \
-print
也可以使用 glob:
for skill_file in "$HOME/.agents/skills"/*/SKILL.md; do
[ -e "$skill_file" ] || continue
echo "$skill_file"
done
15. 常用指令整理
建立 Hard Link
ln source.txt hard-link.txt
建立 Symbolic Link
ln -s source.txt symbolic-link.txt
建立目錄 Symbolic Link
ln -s /actual/directory ~/directory-link
查看 inode
ls -li file.txt
查看詳細資訊
stat file.txt
查看 Symbolic Link 原始內容
readlink symbolic-link.txt
查看 Symbolic Link 最終絕對路徑
readlink -f symbolic-link.txt
尋找 Symbolic Link
find -P /path -type l
尋找 Broken Symbolic Link
find -P /path -xtype l
不跟隨 Symbolic Link 搜尋
find -P /path -type f -name '*.md'
跟隨所有 Symbolic Link 搜尋
find -L /path -type f -name '*.md'
只跟隨起始路徑的 Symbolic Link
find -H /path/to/symlink -type f
找出相同 Hard Link 的檔案
find /path -xdev -samefile file.txt
16. 使用建議
一般情況使用預設模式即可:
find /path ...
也就是:
find -P /path ...
當搜尋目標存在於 Symbolic Link 指向的目錄中,才使用:
find -L /path ...
當起始路徑本身是 Symbolic Link,但不希望跟隨內部其他連結時,使用:
find -H /path/to/symlink ...
簡單判斷:
不確定是否需要跟隨 Symbolic Link
│
├── 不需要 → 使用 -P,或省略選項
│
├── 只需要解析起始路徑 → 使用 -H
│
└── 所有 Symbolic Link 目錄都需要搜尋 → 使用 -L
17. 總結
Hard Link:
多個檔名直接指向相同 inode。
Symbolic Link:
一個獨立檔案,內容儲存另一個路徑。
find 的預設模式:
-P:不跟隨 Symbolic Link
因此遇到 Symbolic Link 目錄時,可能找不到目標目錄裡面的檔案。
需要搜尋 Symbolic Link 指向的內容時:
find -L /path -name target
只想跟隨命令列指定的起始 Symbolic Link:
find -H /path/to/symlink -name target
記憶方式:
-P = Physical
搜尋實體目錄樹,不跟隨連結。
-H = Command-line arguments
只跟隨命令列直接指定的 Symbolic Link。
-L = Logical
將搜尋過程遇到的連結視為實際目錄或檔案並跟隨。
延伸閱讀
- Process 與 CPU 基礎指南 — inode 是檔案系統層的 metadata,process 的
/proc/<pid>/fd/則是檔案描述符怎麼指回實際檔案,兩者是同一套「間接參照」概念在不同層級的應用。 - OpenCode Autocomplete 指南 — 第 14 節的 OpenCode Skills 範例,就是這篇指南建立 Command Wrapper 時實際會遇到的 Symbolic Link 場景。
- fzf 常用指令指南 —
find的輸出常透過 pipe 接 fzf 做互動式篩選(find . -type f | fzf),是否跟隨 Symbolic Link 取決於這裡介紹的-P/-H/-L。