Что такое жёсткая ссылка — и почему rm на самом деле не удаляет файл

Команда rm не удаляет файл. Она удаляет имя. Файл живёт дальше, если у него есть другое имя.
Звучит как игра слов, но проверяется за десять секунд. И объясняет, почему одна правка в node_modules может тихо поменять все твои проекты.
Что такое жёсткая ссылка
Жёсткая ссылка — это ещё одно имя того же файла. Не копия. Не ярлык. Два равноправных имени одних и тех же байтов на диске.
Файл в Linux и macOS состоит из двух частей:
- Сами данные и паспорт файла: размер, права, где лежат байты. Паспорт называется inode, у него есть номер.
- Имя в папке. Это всего лишь строчка «вот такое имя → вот такой номер inode».
Имя и файл — разные вещи. И имён может быть несколько.
Представь сундук и бирки на верёвках. Сундук один, бирок сколько угодно. Срежешь бирку, а сундук стоит. Его унесут, только когда срежут последнюю.
Как это выглядит
Создай файл и дай ему второе имя:
$ echo hello > a.txt
$ ln a.txt b.txt
$ ls -li a.txt b.txt
26106486 -rw-r--r-- 2 root root 6 Nov 10 09:11 a.txt
26106486 -rw-r--r-- 2 root root 6 Nov 10 09:11 b.txt
Первая колонка — номер inode, он совпадает. Двойка после прав — счётчик имён.
Теперь допиши через одно имя и прочитай через другое. Потом удали первое:
$ echo world >> b.txt
$ cat a.txt
hello
world
$ rm a.txt
$ cat b.txt
hello
world
Ничего не пропало. rm убрал бирку и уменьшил счётчик. Байты освобождаются, когда счётчик дойдёт до нуля и файл никто не держит открытым. Отсюда и классическая загадка: удалил гигантский лог, а место на диске не вернулось. Программа всё ещё держит файл через файловый дескриптор.
У жёстких ссылок есть два ограничения:
- На папку обычно нельзя:
ln: hard link not allowed for directory. - Нельзя на другой диск:
Invalid cross-device link. Номер inode имеет смысл только внутри одной файловой системы.
Ловушка: связь рвётся незаметно
Вот что удивляет. Сделай ссылку и поправь файл через sed -i:
$ ln b.txt c.txt
$ sed -i 's/hello/hi/' c.txt
$ cat b.txt
hello
world
$ ls -li b.txt c.txt
26106486 ... b.txt
26106487 ... c.txt
Номера разошлись. sed -i не меняет файл на месте. Он пишет новый файл и подставляет его под старым именем. Многие редакторы при сохранении делают то же самое, чтобы не потерять данные при сбое.
Вывод простой: если два имени вдруг перестали быть одним файлом, что-то сохранило новый файл вместо старого.
Где ты уже с этим встречался
Открой любой проект на pnpm. Твой менеджер пакетов хранит каждый файл пакета один раз в общем хранилище и раздаёт его по проектам. На Linux по умолчанию это жёсткие ссылки. Вот файл из реального проекта:
$ stat -c '%h %n' node_modules/.pnpm/[email protected]/node_modules/is-set/package.json
19 node_modules/.pnpm/[email protected]/node_modules/is-set/package.json
У этого одного файла 19 имён. Десять проектов с одним React занимают место один раз. Отсюда и экономия диска.
А теперь последствие. Допустим, ты или твой ИИ-агент «быстро поправили» файл внутри node_modules. Документация pnpm говорит прямо: при жёсткой ссылке файл в node_modules и есть файл в хранилище, поэтому правка меняет его во всех проектах, которые его используют.
На macOS pnpm сначала пробует клонирование, а там правка других проектов не трогает. Значит, один и тот же приём ломает соседей только на Linux, и как раз на сервере или в CI.
Правило для жизни:
- Нужно поправить зависимость — бери
pnpm patch, а не редактируйnode_modules. - Отдаёшь задачу агенту — запрети ему трогать
node_modulesпрямо в инструкции. - Сомневаешься — проверь счётчик:
stat -c %h файлна Linux,stat -f %l файлна macOS. Больше единицы — у файла есть другие имена. Найти их поможетfind . -samefile файл.
Чем жёсткая ссылка отличается от символической?
Символическая ссылка — отдельный маленький файл с путём внутри. Удалишь оригинал, и она поведёт в никуда. Жёсткая — равноправное имя тех же данных, ей оригинал не нужен.
Жёсткая ссылка подходит для резервной копии?
Нет. Данные одни и те же: испортишь файл через одно имя, он испорчен и через другое. От случайного rm она спасёт, а от правки не спасёт.
Почему у папки счётчик ссылок 2?
Одно имя — запись в родительской папке, второе — точка . внутри самой папки. Каждая подпапка добавляет ещё одно, через свою ...
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





