Learning Platform
Глоссарий Troubleshooting
Урок 10.03 · 20 мин
Начальный
FilesystemsHard linksSymlinksLinux

Hard links vs symlinks — одно содержимое, разные имена

Команда ln в Unix позволяет дать одному файлу несколько имён. Звучит просто, но скрывает важную дифференциацию: есть hard links и symbolic links. Они выглядят похоже в ls, но устроены принципиально по-разному, и эта разница определяет, когда какой использовать.

Hard links и symlinks — ключевые механизмы организации файловой системы. Все ваши /usr/bin/python3 -> /usr/bin/python3.13 — symlinks. Snapshot-системы (Time Machine, rsnapshot) используют hard links для эффективности. Docker layers — copy-on-write hard links под капотом. Git хранит объекты с hash-именами, а ветки — symlinks на конкретные коммиты.

В этом уроке: что физически делает ln file file2 против ln -s file symlink, как они выглядят в kernel, когда какой выбрать, и почему dangling symlinks — норма, а dangling hard link — невозможен.


Когда вы делаете ln file1 file2, kernel не копирует данные. Он создаёт новую запись в директории с именем file2, ссылающуюся на тот же inode, что и file1. Оба имени равноправны — никаких «оригинал/копия».

Hard link -- два имени, один inode
Directory /tmpДиректория содержит таблицу записей. Каждая запись -- 'имя -> номер inode'
entry: file1 -> 786432Имя 'file1' ссылается на inode 786432. Это первое имя
entry: file2 -> 786432После ln file1 file2 появилась запись 'file2' -> тот же inode 786432
Inode 786432Один inode. Содержит метаданные (размер, права, времена) и указатели на блоки данных. links_count = 2
Data blocksСодержимое файла в блоках. Не дублируется -- оба имени видят одни и те же блоки
# Создаём файл и hard link:
echo "Hello" > /tmp/a.txt
ln /tmp/a.txt /tmp/b.txt

# Оба видят то же содержимое:
cat /tmp/a.txt   # Hello
cat /tmp/b.txt   # Hello

# Тот же inode -- в этом и магия:
stat -c '%i' /tmp/a.txt /tmp/b.txt
# 786432
# 786432   <-- тот же номер!

ls -li /tmp/a.txt /tmp/b.txt
# 786432 -rw-r--r-- 2 myuser myuser 6 May 18 10:00 /tmp/a.txt
# 786432 -rw-r--r-- 2 myuser myuser 6 May 18 10:00 /tmp/b.txt
#                   ^ links_count = 2

# Изменение через одно имя видно через другое:
echo "World" >> /tmp/a.txt
cat /tmp/b.txt
# Hello
# World

# Удаление одного имени -- другое работает:
rm /tmp/a.txt
cat /tmp/b.txt
# Hello
# World  <-- inode жив, пока links_count > 0

Запомните: ln a b — это просто новое имя в директории. Никакого копирования. Очень дёшево, моментально, нет потребления диска.


Hard links не всемогущи:

  1. Не работают для директорий. ln /dir /dir2 запретно — иначе можно создать цикл в файловом дереве, и find/rm с -r зависнут.
  2. Не работают между разными ФС. Inode-номера уникальны только в пределах ФС. ln /home/file /tmp/file фейлится с EXDEV если /home и /tmp — разные ФС.
  3. Не сохраняются при копировании. cp a b создаёт новый inode, hard links теряются (если не используется cp --preserve=links).
# Hard link на директорию -- нельзя:
mkdir /tmp/dir1
ln /tmp/dir1 /tmp/dir2
# ln: /tmp/dir1: hard link not allowed for directory

# Между ФС -- нельзя:
ln /home/user/file /tmp/file
# ln: failed to create hard link: Invalid cross-device link

# /home -- ext4, /tmp -- tmpfs (in-memory), разные ФС

ln -s target name создаёт symbolic link — небольшой файл специального типа, содержимое которого — путь к target. Symlink не ссылается на inode; он содержит строку «вот этот путь смотри».

Symlink -- маленький файл с путём
Directory /tmpЗапись link -> inode 786500 (тип symlink). Этот inode -- не тот же, что у target
entry: link -> 786500Запись в директории. Имя 'link', указывает на отдельный inode типа symlink
entry: target.txt -> 786432Запись на оригинальный файл. Отдельная.
Inode 786500 (symlink)Inode типа symlink. Его 'содержимое' -- строка с путём (обычно встроена inline в inode, если короткая). Размер = длина строки
Inode 786432 (target)Inode целевого файла. Symlink не знает о нём напрямую -- знает только путь
Content: 'target.txt'Содержимое symlink-инода -- строка-путь. При open() через symlink kernel читает эту строку и рекурсивно резолвит её
Real data blocksРеальные данные. Доступны через target.txt и через link (после резолва)
# Создаём symlink:
echo "Hello" > /tmp/target.txt
ln -s /tmp/target.txt /tmp/link.txt

# ls показывает -> на символе link:
ls -la /tmp/link.txt
# lrwxrwxrwx 1 myuser myuser 14 May 18 10:00 /tmp/link.txt -> /tmp/target.txt
#  ^                                                       ^
#  l -- symlink                                       что показывает

# При cat -- читается target:
cat /tmp/link.txt
# Hello

# Размер symlink = длина пути:
stat -c '%s' /tmp/link.txt
# 14  -- длина строки "/tmp/target.txt"

# inode разные:
stat -c '%i' /tmp/link.txt /tmp/target.txt
# 786501
# 786432

Поскольку symlink хранит просто строку-путь, target может не существовать. Dangling symlink (битая ссылка) — symlink, указывающий на несуществующий путь.

ln -s /tmp/nonexistent /tmp/broken
ls -la /tmp/broken
# lrwxrwxrwx 1 myuser myuser 16 May 18 10:00 /tmp/broken -> /tmp/nonexistent

cat /tmp/broken
# cat: /tmp/broken: No such file or directory

# Symlink сам существует -- цел и невредим:
stat /tmp/broken
# File: /tmp/broken -> /tmp/nonexistent
# Size: 16  Blocks: 0  -- symlink есть
# (но target отсутствует)

# Найти все dangling symlinks:
find /tmp -xtype l 2>/dev/null
# /tmp/broken

Для hard link такое невозможно: hard link напрямую ссылается на inode. Если inode удалён, hard link исчез одновременно (точнее, останется только если есть другие hard links на тот же inode — тогда контент сохраняется).


Hard link vs Symlink -- ключевые отличия
Hard linkВторое имя того же inode. Не отличить от 'оригинала'. Работает только в пределах одной ФС. Нельзя для директорий. Удаление любого имени не ломает другие
SymlinkОтдельный файл с путём. Имеет свой inode. Работает между ФС. Работает на директории. Удаление target ломает symlink (dangling)
ln a bБез -s. Создаёт hard link. Команда быстрая, ничего не копирует
ln -s a bС -s. Создаёт symlink. Просто строка с путём. Не валидирует target -- target может не существовать
Same FS onlyEXDEV если a и b в разных файловых системах (например, разные дисковые партиции, или /tmp tmpfs vs /home ext4)
Cross-FS OKSymlink может указывать на любой путь, даже на другой ФС или несуществующий путь
No directoryhard link на директории запрещён -- риск циклов в дереве
Directory OKsymlink на директорию -- норма. /var/run -> /run, /usr/lib64 -> /usr/lib и т.д.

Hard link, когда:

  • Многим именам одного файла внутри одной ФС. Например, multi-call binary BusyBox: busybox, ls, cat, cp — всё один inode с разными именами, BusyBox смотрит argv[0] и решает что делать.
  • Snapshots / backups с дедупликацией. rsync с --link-dest, rsnapshot. Backup занимает место только для изменённых файлов; неизменённые — hard links на предыдущий снимок.
  • Защита от случайного удаления. Двойной reference — если один файл удалят, второй имя цело.

Symlink, когда:

  • Между ФС. /var/lib/docker -> /mnt/big-disk/docker — разные партиции.
  • На директорию. /usr/lib64 -> /usr/lib (на 64-bit системах для совместимости).
  • Version aliases. /usr/bin/python3 -> python3.13, легко переключить.
  • Configurable paths. /etc/nginx/sites-enabled/mysite -> ../sites-available/mysite.
  • Cross-machine через mount, NFS.

В большинстве случаев на практике — symlink. Hard link используется в специфических сценариях (backups, package managers).


Реальные применения

update-alternatives в Debian/Ubuntu — система symlink-based выбора версий:

ls -la /etc/alternatives/python3
# lrwxrwxrwx 1 root root 16 May 18 /etc/alternatives/python3 -> /usr/bin/python3.13

ls -la /usr/bin/python3
# lrwxrwxrwx 1 root root 25 May 18 /usr/bin/python3 -> /etc/alternatives/python3

# Цепочка: python3 -> /etc/alternatives/python3 -> python3.13
# update-alternatives --set python3 /usr/bin/python3.11
# перенастроит /etc/alternatives/python3 на 3.11

Systemd unit aliases через symlinks:

ls -la /etc/systemd/system/multi-user.target.wants/
# Все симлинки на /lib/systemd/system/*.service
# Включение сервиса (systemctl enable) -- это создание symlink

Rsync backups с hard links:

# Создать первый снимок:
rsync -av /home/ /backup/2026-05-18/

# Создать второй снимок -- неизменённые файлы как hard link:
rsync -av --link-dest=/backup/2026-05-18/ /home/ /backup/2026-05-19/

# Если в /home/ изменился только 1 ГБ из 100 ГБ, второй снимок занимает
# только дополнительный 1 ГБ -- остальные 99 ГБ -- hard links на первый снимок.
# Каждый снимок ВЫГЛЯДИТ как полный, но физически дедуплицирован.

При проверке прав symlink kernel НЕ проверяет permissions самого symlink-а — он резолвит target и проверяет права на нём. Permissions symlink (всегда rwxrwxrwx, никто их не проверяет).

Это создаёт TOCTOU-уязвимости (time-of-check-time-of-use): программа проверила существование файла через path, перед открытием путь подменили на symlink на другой файл, открылся не тот.

Безопасные паттерны:

# Командам, работающим с file trees:
rm -r dir/          # обходит symlinks как обычные файлы
chmod -R 644 dir/   # -R по умолчанию идёт ПО symlinks ВНУТРЬ -- может изменить target!

# -L означает 'follow symlinks':
chmod -RL 644 dir/  # явно следовать симлинкам

# -P (default в большинстве cmds) -- не следовать:
chown -RP user:group dir/   # не идти внутрь через symlinks

# Защита от внезапных attacks: 
mkdir -m 0700 secure_dir
# или openat с AT_SYMLINK_NOFOLLOW флагом

В Linux есть O_NOFOLLOW флаг для open: «открой, но если path ends в symlink — ошибка». Используется в security-критичных программах.


Попробуй сам

Эксперимент с hard links:

# Создать файл, посмотреть ссылки:
echo "data" > /tmp/orig.txt
ln /tmp/orig.txt /tmp/link1
ln /tmp/orig.txt /tmp/link2

ls -li /tmp/orig.txt /tmp/link1 /tmp/link2
# Все три имеют один inode и links_count=3

# Удалить два:
rm /tmp/orig.txt /tmp/link1
cat /tmp/link2
# data  -- по-прежнему работает

stat /tmp/link2
# Links: 1  -- осталась одна ссылка

Эксперимент с symlinks:

# Создать дерево с symlinks:
mkdir -p /tmp/proj/{v1,v2,current}
echo "version 1" > /tmp/proj/v1/file
echo "version 2" > /tmp/proj/v2/file

# 'current' -- symlink, переключаемый между версиями:
ln -sf v1 /tmp/proj/current_link
cat /tmp/proj/current_link/file
# version 1

# Переключить:
ln -sf v2 /tmp/proj/current_link
cat /tmp/proj/current_link/file
# version 2

# Atomic switch! Это и есть деплой через symlinks
ls -la /tmp/proj/
rm -rf /tmp/proj

Этот паттерн — классический для blue/green deploy: новая версия раскладывается рядом, потом один symlink переключается атомарно. Если ошибка — быстрый откат через symlink-flip.

ln: создание hard link и symlink на практике

Найти dangling symlinks в системе:

# В /etc обычно много symlinks, могут быть dangling после обновлений:
sudo find /etc -xtype l 2>/dev/null

Проверка знанийKnowledge check
Бэкап-система использует rsync с --link-dest для дедупликации. Через 30 дней у вас 30 снапшотов в /backup/2026-04-18 .. /backup/2026-05-18. Каждый выглядит как полный бэкап, занимает 100 ГБ суммарно (а не 30*100). Как это работает и какие риски?
ОтветAnswer
Это работает через hard links: при rsync с --link-dest неизменённые файлы создаются как hard links на предыдущий снимок, а не как копии. Файл размером 10 ГБ, лежащий в 30 снапшотах, физически хранится один раз -- занимает 10 ГБ на диске, остальные 29 'копий' -- просто записи в директориях, ссылающиеся на тот же inode. df правильно покажет суммарно ~100 ГБ. Каждый снимок выглядит как полный (можно ls, copy, restore без зависимостей от других снимков). Риски и нюансы: 1) Inode exhaustion: каждый снимок добавляет inode-записи (хоть данные дедуплицированы), но inode-numbers расходуются за каждый отдельный файл в каждом снимке. Если в backup миллионы мелких файлов, через 30 снимков -- 30M записей. ext4 имеет фиксированное число inodes -- можно исчерпать. Лечение -- XFS (dynamic inodes) или периодическая консолидация. 2) Изменение одного файла в одном снимке копирует его -- COW не работает автоматически, rsync делает unlink+create. Поэтому если кто-то 'починит файл' в старом снимке, дедупликация разваливается. 3) Backup может быть только на той же ФС -- hard links не работают между. 4) При удалении старого снимка (rm -r /backup/2026-04-18) места освобождается только то, что не используется в других снимках. 5) du на одной snapshot покажет полный размер, а df на total -- меньший. Не путать. 6) Restore из любого снимка -- атомарен и независим, что хорошо. 7) Backup tools типа BackupPC, rsnapshot, dirvish используют именно эту схему. Современная альтернатива -- ФС с CoW и snapshots (zfs, btrfs) -- даёт те же преимущества + честный CoW.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. В чём принципиальное различие hard link и symbolic link?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5