Learning Platform
Глоссарий Troubleshooting
Урок 06.04 · 18 мин
Начальный
lnSymlinksHard LinksInodes

Hard links и symlinks: ln

Linux позволяет дать файлу два имени — это link (ссылка). Есть два типа: hard link и symbolic link (symlink). Они работают по-разному, и понимание разницы критично для Junior DE: вы будете использовать симлинки для версионирования (current -> 2026-05-13), для хитрого мердж конфигов, для дедупликации в архивах.

В этом уроке разберём оба типа, с механикой на уровне inode.


Inode — что хранится про файл

Inode: структура метаданных файла на уровне ядра

Перед links нужно понять inode. В Linux каждый файл — это:

  1. Inode (index node) — структура с метаданными: размер, права, владелец, временные метки, указатели на блоки данных.
  2. Имя файла — запись в директории, которая мапит «имя -> inode-номер».

То есть, файл и имя — это отдельно. Имя — это «бирка», прикреплённая к inode.

Inode и имена файлов
Директория /home/userЭто просто список 'имя -> inode', записанный в виде специального файла
report.csv -> inode 12345Запись в директории: имя report.csv привязано к inode 12345
Inode 12345Структура с метаданными: size=10240, perms=0644, owner=user, mtime=...
Блоки данныхУказатели на блоки на диске, в которых лежит реальный контент файла

Inode-номер можно увидеть:

$ ls -i
12345 report.csv
12346 notes.txt
67890 projects/

$ stat report.csv
  File: report.csv
  Size: 10240       Blocks: 24         IO Block: 4096   regular file
Device: 8,1     Inode: 12345       Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/    user)   Gid: ( 1000/    user)
Access: 2026-05-13 14:00:00
Modify: 2026-05-13 14:00:00
Change: 2026-05-13 14:00:00

Links: 1 — это количество имён, которые ссылаются на этот inode. По умолчанию — 1.


ln source target создаёт hard link — второе имя для того же inode:

$ echo "original content" > original.txt
$ ls -i original.txt
12345 original.txt

$ ln original.txt copy.txt
$ ls -i
12345 copy.txt    12345 original.txt
# Один и тот же inode!

$ stat original.txt
  ...
  Inode: 12345       Links: 2     # ← теперь 2

$ cat copy.txt
original content       # тот же контент

$ echo "modified" >> copy.txt
$ cat original.txt
original content
modified               # ← изменение через copy.txt видно в original.txt

Это потому, что физически это один файл с двумя именами. Блоки на диске — одни.

Hard link: два имени, один inode
original.txtИмя в директории, мапится на inode 12345
copy.txtДругое имя в директории (даже в другой папке), мапится на ТОТ ЖЕ inode 12345
Inode 12345 (Links: 2)Один inode. В нём счётчик Links — сколько имён на него ссылаются
Блоки данных (одни)Физические данные — одна копия. Размер дискового места — не удваивается

Когда удаляете один из hard links (rm copy.txt), inode НЕ удаляется — Links декрементится с 2 до 1. Файл остаётся доступен через другое имя.

Inode удаляется только когда Links достигает 0 (все имена удалены) И ни один процесс не держит файл открытым.

$ ln original.txt copy.txt
$ stat copy.txt | grep Links
Links: 2

$ rm copy.txt
$ stat original.txt | grep Links
Links: 1

$ cat original.txt
original content
modified                  # все данные на месте
  1. Только в пределах одного filesystem. Hard link не может пересекать mount points (потому что inode-номера разные на разных fs).
  2. Только для файлов, не для директорий. Запрещено системой (иначе можно создать циклы в графе FS).
  3. Размер не дублируется. Это главная фишка: 100GB файл с hard link не съест 200GB.
# Попытка создать hard link на директорию
$ ln /etc/ /tmp/etc-link
ln: /etc/: hard link not allowed for directory

# Между разными fs — не работает
$ ln /home/user/file.txt /tmp/file.txt    # если /tmp и /home на разных fs
ln: failed to create hard link: Invalid cross-device link

Hard links и symlinks на уровне ядра: link(), symlink(), unlink()

Symbolic link — это файл-указатель на путь. Не на inode, а на путь (строку).

$ ln -s original.txt symlink.txt
$ ls -la
-rw-r--r-- 1 user user   25 May 13 14:00 original.txt
lrwxrwxrwx 1 user user   12 May 13 14:00 symlink.txt -> original.txt
# l в начале — это symlink
# -> показывает путь, на который указывает

Inode разные:

$ ls -i
12345 original.txt
67890 symlink.txt    # Свой inode

Symlink — это маленький файл, содержащий имя цели. Когда программа обращается к symlink, ядро резолвит цель и идёт по ней.

Symlink: указатель на путь
symlink.txt (inode 67890)Свой inode. Внутри inode хранится строка 'original.txt' — путь до цели
указывает на путь
'original.txt'Текст пути. НЕ inode-номер, а строка. Резолвится при обращении
original.txt (inode 12345)Реальный файл с данными
# Создать symlink
$ ln -s /var/data/raw/2026-05-13 ~/current-data

# Содержимое цели доступно через имя symlink
$ ls ~/current-data
file1.csv  file2.csv

# Что внутри symlink (на что указывает)
$ readlink ~/current-data
/var/data/raw/2026-05-13

# Полностью развернуть (для chains)
$ readlink -f ~/current-data
/var/data/raw/2026-05-13   # после резолва всех вложенных symlinks

Что происходит при удалении цели

$ ln -s original.txt symlink.txt
$ cat symlink.txt
original content

$ rm original.txt
$ cat symlink.txt
cat: symlink.txt: No such file or directory
# Symlink остался, но цель пустая -> битый symlink

$ ls -la symlink.txt
lrwxrwxrwx 1 user user 12 May 13 14:00 symlink.txt -> original.txt
# Symlink на месте, но битый

«Битый» symlink (dangling symlink) — это symlink, цель которого больше не существует. Их можно найти:

$ find /etc -xtype l        # все битые symlinks

Hard link vs Symlink
Hard linkВторое имя для того же inode
vs
Symbolic linkОтдельный файл-указатель на путь
Same fs толькоНе может пересекать mount points
Any fs / networkSymlink может указывать куда угодно: другой fs, network share, несуществующий путь
Файлы толькоЗапрещено создавать на директории
Файлы и папкиSymlink может указывать на что угодно, включая папки
Цель удалена -> имя живётInode остаётся доступным пока есть хоть один hard link
Цель удалена -> битыйSymlink — лишь имя пути, путь больше не существует — symlink бесполезен
ls видит как обычный файлls -l показывает Links: N, но иначе как обычный
ls видит явноls -l: первый символ l, и стрелочка ->

Сценарий 1: версионирование current

Классика: ваш ETL генерирует данные в datestamp-папки. Приложение должно читать «текущие» данные — но имя папки меняется ежедневно.

# Структура
/var/data/2026-05-11/
/var/data/2026-05-12/
/var/data/2026-05-13/        # сегодня
/var/data/current -> 2026-05-13   # symlink

# Приложение читает /var/data/current/file.csv
# Не нужно знать дату — symlink резолвится в текущую

ETL-скрипт после успешной генерации обновляет symlink атомарно:

$ ln -sf "/var/data/$(date +%Y-%m-%d)" /var/data/current

-f (force) — перезаписать, если symlink уже существует. Это атомарная операция — приложение не увидит «промежуточного» состояния.

Сценарий 2: версии бинарников

Python, Node.js, Java — у вас часто несколько версий:

$ ls -la /usr/bin/python*
lrwxrwxrwx 1 root root  9 May  1 10:00 /usr/bin/python -> python3
lrwxrwxrwx 1 root root 11 May  1 10:00 /usr/bin/python3 -> python3.13
-rwxr-xr-x 2 root root 5M May  1 10:00 /usr/bin/python3.13

python -> python3 -> python3.13. Можно «переключиться» на python3.12, переставив symlink:

$ sudo ln -sf /usr/bin/python3.12 /usr/bin/python3

(На самом деле в Ubuntu используется update-alternatives для этого, но идея — symlinks.)

Сценарий 3: dotfiles

DE-разработчики часто держат свои конфиги в git-репо (~/dotfiles/) и symlink-ят их в HOME:

$ ls ~ -la | head
.bashrc -> /home/user/dotfiles/.bashrc
.vimrc -> /home/user/dotfiles/.vimrc
.config/starship.toml -> /home/user/dotfiles/.config/starship.toml

Менеджеры типа stow это автоматизируют:

$ cd ~/dotfiles
$ stow bash       # создаст symlinks для всех файлов из ~/dotfiles/bash в ~/

Hard links используются реже, но есть характерные сценарии:

Сценарий 1: дедупликация в архивах

rsync --link-dest: incremental backups через hard links.

# Первый бэкап — полный
$ rsync -a /var/data/ /backup/2026-05-11/

# Следующий — только изменённые файлы, остальные как hard links
$ rsync -a --link-dest=/backup/2026-05-11 /var/data/ /backup/2026-05-12/

В /backup/2026-05-12/ все НЕизменённые файлы — это hard links на файлы из 2026-05-11. Disk usage минимален: только изменения занимают новое место. Каждый день — «полный» бэкап, но компактный.

Подробнее в модуле 12 про rsync.

Сценарий 2: атомарная замена файла

Атомарная замена через ln + rename:

# Прочитать конфиг, обновить, написать атомарно
$ cp /etc/myapp.conf /tmp/myapp.conf.new
$ vim /tmp/myapp.conf.new
$ mv /tmp/myapp.conf.new /etc/myapp.conf    # rename — атомарная операция

mv под капотом — это rename(), который атомарен на одном fs. Это значит, что любой процесс, читающий /etc/myapp.conf, либо увидит старую версию полностью, либо новую — но не половинку.

Это важно для production: вы не хотите, чтобы сервис прочитал недописанный конфиг и упал.


Опции ln

$ ln source target              # hard link
$ ln -s source target           # symbolic link
$ ln -sf source target          # force (перезаписать существующий)
$ ln -v source target           # verbose
$ ln -r -s source target        # relative path в symlink (вместо абсолютного)

-r особенно полезно — создаёт symlink с относительным путём:

# Без -r — абсолютный путь
$ ln -s /var/data/2026-05-13 /var/data/current
$ readlink /var/data/current
/var/data/2026-05-13            # абсолютный

# С -r — относительный
$ ln -sr /var/data/2026-05-13 /var/data/current
$ readlink /var/data/current
2026-05-13                       # относительно расположения symlink

Относительные symlinks устойчивее к перемещению: если папку /var/data переместить, абсолютные symlinks сломаются, а относительные продолжат работать (потому что они «соседи»).


Попробуй сам

$ mkdir -p ~/linux-sandbox/lesson-links
$ cd ~/linux-sandbox/lesson-links

# Создание hard link
$ echo "original" > file1.txt
$ ls -li file1.txt
12345678 -rw-r--r-- 1 user user 9 May 13 14:00 file1.txt

$ ln file1.txt file2-hard.txt
$ ls -li
12345678 -rw-r--r-- 2 user user 9 May 13 14:00 file1.txt
12345678 -rw-r--r-- 2 user user 9 May 13 14:00 file2-hard.txt
# Same inode, Links count = 2

# Изменение через одно имя видно в другом
$ echo "modified" >> file1.txt
$ cat file2-hard.txt
original
modified

# Создание symlink
$ ln -s file1.txt file3-sym.txt
$ ls -li
12345678 -rw-r--r-- 2 user user 18 May 13 14:00 file1.txt
12345678 -rw-r--r-- 2 user user 18 May 13 14:00 file2-hard.txt
99999999 lrwxrwxrwx 1 user user  9 May 13 14:00 file3-sym.txt -> file1.txt
# Разный inode, тип l

$ cat file3-sym.txt
original
modified

# Удалить оригинал
$ rm file1.txt

# Hard link продолжает работать
$ cat file2-hard.txt
original
modified

# Symlink — нет (битый)
$ cat file3-sym.txt
cat: file3-sym.txt: No such file or directory

# Найти битые symlinks
$ find . -xtype l
./file3-sym.txt

DE-симуляция: версионирование current:

$ mkdir 2026-05-{11,12,13}
$ touch 2026-05-13/data.csv
$ ln -sf 2026-05-13 current
$ ls current/
data.csv

# Завтра — обновляем
$ mkdir 2026-05-14
$ touch 2026-05-14/data.csv
$ ln -sfn 2026-05-14 current     # -n чтобы не создать current/2026-05-14 если current — папка

$ ls -la current
lrwxrwxrwx 1 user user 10 May 13 14:00 current -> 2026-05-14

Проверка знанийKnowledge check
DE команда обновляет `~/.ssh/authorized_keys` через скрипт: `cat new_keys.txt > ~/.ssh/authorized_keys`. Junior жалуется: 'мой backup-скрипт делает hard link ~/.ssh/authorized_keys -> /backup/keys, но после обновления backup всё ещё содержит старые ключи'. Что происходит?
ОтветAnswer
Это интересный нюанс работы hard links vs > (redirection). Когда вы делаете `cat new_keys.txt > authorized_keys`, происходит НЕ перезапись inode, а: bash открывает authorized_keys на запись (O_TRUNC — обрезка существующего файла) и пишет новое содержимое. Если был hard link — он указывает на тот же inode, и backup увидит изменения. То есть hard link ДОЛЖЕН работать. Если backup НЕ обновился, скорее всего: (1) Скрипт делает `rm authorized_keys && cp new_keys.txt authorized_keys` — это создаёт НОВЫЙ inode, hard link отстаёт. (2) Используется `mv new_keys.txt authorized_keys` — rename(), который тоже меняет inode на новый. (3) Редактор (vim, nano) часто пишет в .tmp файл и атомарно rename — новый inode. Решение для надёжного backup: использовать `rsync --link-dest` для incremental backups, а не голые hard links. Hard link работает только когда содержимое модифицируется ВНУТРИ inode (echo >> file), но ломается при любых операциях, создающих новый inode (mv, многие редакторы, > с предварительным rm).

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

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

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

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

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

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