Ссылки: жёсткие и символические

Разбираемся, чем жёсткая ссылка отличается от символической, как обе связаны со счётчиком ссылок в inode, и когда какую применять без сюрпризов.

Жёсткая ссылка — ещё одно имя для того же inode. Символическая (мягкая) ссылка — отдельный маленький файл, внутри которого записан путь к цели.

Зачем это знать на практике

Симлинки повсюду: /usr/bin/python ведёт на python3.11, активная версия приложения переключается сменой одной ссылки, текущий лог app.log указывает на файл с датой. Жёсткие ссылки лежат в основе того, как работают аппаратные снапшоты бэкапов и почему mv внутри раздела мгновенный. Перепутать их типы — значит однажды удалить «всего лишь ссылку» и потерять данные или, наоборот, поломать обновление приложения.

Жёсткие ссылки

Создаём вторым именем для существующего файла:

echo "важные данные" > original.txt
ln original.txt hardlink.txt          # ln без -s = жёсткая ссылка

ls -li original.txt hardlink.txt
# 262145 -rw-r--r-- 2 user user 14 ... original.txt
# 262145 -rw-r--r-- 2 user user 14 ... hardlink.txt

Оба имени делят один и тот же номер inode (262145) и одно содержимое. Цифра 2 после прав — это счётчик ссылок: на данный inode указывают два имени. Удаление одного имени просто уменьшает счётчик; данные исчезают, только когда счётчик дойдёт до нуля.

rm original.txt
cat hardlink.txt          # «важные данные» — файл жив, осталось второе имя
ls -l hardlink.txt        # счётчик ссылок теперь 1

Ограничения жёстких ссылок

  • Нельзя пересекать границу файловой системы: номер inode уникален только внутри своей ФС, поэтому жёсткая ссылка на файл с другого раздела невозможна.
  • Обычно нельзя создавать на каталоги (иначе в дереве возникли бы циклы) — это запрещено ядром.
  • Все жёсткие ссылки равноправны: нет «главного» имени, есть один inode с несколькими именами.

Жёсткие ссылки в бэкапах

Самое яркое практическое применение — инкрементальные резервные копии в стиле rsync --link-dest и Time Machine. Идея в том, что неизменённые между бэкапами файлы не копируются заново, а связываются жёсткой ссылкой со своей копией из предыдущего снимка. На диске такой файл лежит в одном экземпляре, но «виден» в десятках каталогов-снимков как полноценная копия. В итоге каждый ежедневный бэкап выглядит как целое дерево, а место занимают лишь реально изменившиеся файлы:

# вчерашний снимок — в backup/day1, делаем сегодняшний
rsync -a --link-dest=/backup/day1 /data/ /backup/day2/
# одинаковые файлы в day2 — жёсткие ссылки на day1 (нового места не заняли)
du -sh /backup/day1 /backup/day2     # второй каталог почти ничего не добавил

Удалить старый снимок безопасно: пока на файл указывает хоть одна жёсткая ссылка из более свежего бэкапа, данные сохранятся. Этот же приём отвечает на вопрос, почему mv в пределах одного раздела мгновенный — переименование лишь меняет запись «имя → inode» в каталоге, не трогая блоки данных.

Символические ссылки

Симлинк создаётся ключом -s. Это самостоятельный файл-указатель: у него свой inode, а содержимым служит текст пути к цели.

ln -s /var/www/releases/v2 /var/www/current

ls -l /var/www/current
# lrwxrwxrwx 1 user user 22 ... /var/www/current -> /var/www/releases/v2

Тип l в начале и стрелка -> выдают симлинк. Он может указывать на что угодно: файл, каталог, на другой раздел и даже на ещё не существующий путь. Деплой без простоя часто сводится к атомарному переключению такой ссылки:

ln -sfn /var/www/releases/v3 /var/www/current   # переключить на новую версию
# -f перезаписать, -n не идти внутрь существующего симлинка-каталога

Чем они отличаются

СвойствоЖёсткаяСимволическая
Свой inodeнет (общий с целью)да (отдельный)
Меж разделаминельзяможно
На каталогнельзяможно
Если удалить цельданные живы (ещё есть имя)ссылка «битая», ведёт в никуда
Влияет на счётчик ссылокданет

Как это работает под капотом

В каталоге имя всегда хранится как запись «имя → inode». Жёсткая ссылка — это просто ещё одна такая запись, ссылающаяся на тот же inode. Поэтому все жёсткие ссылки абсолютно равноправны: нет «оригинала» и «копии», есть один inode с несколькими именами, и поле Links в stat считает их количество.

Символическая ссылка — это файл особого типа, чьё «содержимое» — строка пути. Когда программа открывает симлинк, ядро читает эту строку и переходит по ней к цели (это называется разыменованием). Если цель — тоже симлинк, переход повторяется, но число прыжков ограничено (защита от бесконечных циклов, при превышении — ошибка ELOOP, «Too many levels of symbolic links»). Если по записанному пути ничего нет — ссылка «висячая» (dangling), и обращение вернёт «No such file or directory», хотя сам файл-ссылка существует.

readlink /var/www/current          # показать, КУДА ведёт симлинк (один шаг)
readlink -f /var/www/current       # разрешить всю цепочку до реального файла
namei -l /var/www/current/index.php # разобрать путь по компонентам с типами

Частые ошибки

  • Битый симлинк после перемещения цели. Симлинк с абсолютным путём «отвалится», если цель переехала. Найти все висячие ссылки в дереве: find /path -xtype l.
  • Относительный путь считается от расположения ссылки, а не от текущего каталога. ln -s ../data link резолвится относительно каталога, где лежит link; перенесёте ссылку — она сломается.
  • Копирование симлинка. cp по умолчанию копирует ЦЕЛЬ, а не саму ссылку. Чтобы сохранить ссылки как ссылки, нужен cp -P (или cp -a для архивного копирования).
  • «Удалил всего лишь ссылку». С жёсткой ссылкой данные не пропадут, пока есть другое имя, а вот rm по симлинку-каталогу со слешем (rm dir/) может повести себя неожиданно — будьте аккуратны.

Итоги

  • Жёсткая ссылка (ln) — дополнительное имя того же inode; повышает счётчик ссылок, данные живут, пока счётчик > 0.
  • Символическая ссылка (ln -s) — отдельный файл с путём внутри; может вести куда угодно, но ломается, если цель исчезла.
  • Жёсткие ссылки не пересекают разделы и не делаются на каталоги; симлинки — могут и то, и другое.
  • Инструменты: ls -li (inode и счётчик), readlink -f (куда ведёт), find -xtype l (битые ссылки).
  • Атомарное переключение симлинка (ln -sfn) — стандартный приём деплоя без простоя.
Проверьте себя
1. Чем жёсткая ссылка принципиально отличается от символической?
AЖёсткая ссылка — это ещё одно имя того же inode, а символическая — отдельный файл, хранящий путь к цели
BЖёсткая ссылка работает только для каталогов, а символическая — только для файлов
CСимволическая ссылка увеличивает счётчик ссылок в inode, а жёсткая нет
DМежду ними нет разницы, это синонимы команды ln
2. Вы создали жёсткую ссылку hardlink.txt на файл original.txt, затем выполнили rm original.txt. Что произойдёт с данными?
AДанные потеряны: удаление оригинала стирает содержимое
BДанные сохранятся — на тот же inode по-прежнему указывает имя hardlink.txt, счётчик ссылок стал 1
Chardlink.txt превратится в битую символическую ссылку
DФайл переедет в /tmp на восстановление