Ссылки: жёсткие и символические
Разбираемся, чем жёсткая ссылка отличается от символической, как обе связаны со счётчиком ссылок в 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) — стандартный приём деплоя без простоя.