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 — невозможен.
Hard link — второе имя того же inode
Когда вы делаете ln file1 file2, kernel не копирует данные. Он создаёт новую запись в директории с именем file2, ссылающуюся на тот же inode, что и file1. Оба имени равноправны — никаких «оригинал/копия».
# Создаём файл и 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
Hard links не всемогущи:
- Не работают для директорий.
ln /dir /dir2запретно — иначе можно создать цикл в файловом дереве, и find/rm с -r зависнут. - Не работают между разными ФС. Inode-номера уникальны только в пределах ФС.
ln /home/file /tmp/fileфейлится сEXDEVесли /home и /tmp — разные ФС. - Не сохраняются при копировании.
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), разные ФС
Symlink — путь как содержимое
ln -s target name создаёт symbolic link — небольшой файл специального типа, содержимое которого — путь к target. Symlink не ссылается на inode; он содержит строку «вот этот путь смотри».
# Создаём 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
Dangling symlinks — норма
Поскольку 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 или symlink
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 security и -L vs -P
При проверке прав 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