Hard Link、Symbolic Link 與 find 指令指南

Hard Link、Symbolic Link 與 find 指令指南

Linux 檔案系統中,檔名、inode 與實際資料可以簡化成:

檔名 → inode → 實際資料內容

Hard Link 與 Symbolic 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。

共享相同 inode

兩個檔名實際上是同一份檔案資料。

修改其中一個:

echo "hello" >> file-hard.txt

另一個也會看到相同內容:

cat file.txt
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

為避免目錄結構形成循環,Linux 通常不允許一般使用者替目錄建立 Hard Link。

ln existing-directory directory-hard-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 這個路徑。

Symbolic Link 本身也是一個獨立檔案:

Symbolic Link inode
    ↓
儲存目標路徑字串
    ↓
目標檔案 inode
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 的工作目錄。


項目 Hard Link Symbolic Link
中文名稱 硬連結 符號連結、軟連結
建立方式 ln source target ln -s source target
inode 與原檔相同 有自己的 inode
儲存內容 直接指向相同 inode 儲存目標路徑
修改內容 所有 Hard Link 都會看到變更 透過路徑存取目標
原始檔刪除 其他 Hard Link 仍可使用 變成 Broken Link
跨檔案系統 不可以 可以
連結目錄 一般不允許 可以
是否有原始與副本 沒有真正的原始與副本之分 有 Link 與 Target 的關係
常見用途 多個檔名共享相同資料 快捷入口、設定檔、版本切換

使用:

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"

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 表示不要跨越到其他檔案系統。


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'

-Pfind 的預設行為。

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

find -P ~/.agents/skills -type 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 的情況

例如 OpenCode skills:

find -L ~/.agents/skills \
  -mindepth 2 \
  -maxdepth 2 \
  -type f \
  -name SKILL.md
~/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

可能連整個大型儲存空間都被掃描。


-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

行為是:

  1. 跟隨 ~/current,因為它是命令列指定的起始路徑。
  2. 進入 ~/projects/project-a
  3. 不跟隨裡面的 vendor Symbolic Link。

-H 適合:


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 後不進入目標目錄。

可以使用:

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

在預設 -P 模式下:

find -P /path/to/search -type l

可以找到所有 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-querywiki-ingestwiki-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. 常用指令整理

ln source.txt hard-link.txt
ln -s source.txt symbolic-link.txt
ln -s /actual/directory ~/directory-link

查看 inode

ls -li file.txt

查看詳細資訊

stat file.txt
readlink symbolic-link.txt
readlink -f symbolic-link.txt
find -P /path -type l
find -P /path -xtype l
find -P /path -type f -name '*.md'
find -L /path -type f -name '*.md'
find -H /path/to/symlink -type f
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
     將搜尋過程遇到的連結視為實際目錄或檔案並跟隨。

延伸閱讀