Тема 02

Файловая система и работа с файлами

Файловая система и работа с файлами

Тема 1 показала, как открыть терминал, выполнить первую команду и найти справку по ней. Теперь команды начинают работать с файлами, а значит, нужно понимать, где в системе что лежит и как к этому месту обратиться. Файловая система (англ. file system) — способ хранения файлов и каталогов на диске; операционная система показывает её пользователю как единое дерево каталогов, даже если на самом деле файлы лежат на нескольких физических дисках.

Примеры темы выполнены в учебном проекте «Северный узел», который создаёт скрипт make-tree.sh из архива окружения лабораторной работы 2. Чтобы повторить примеры, достаточно распаковать архив и создать проект в отдельном каталоге:

unzip ~/Downloads/lr-env.zip -d ~/lab-02
mkdir -p ~/lab-02-demo
bash ~/lab-02/lr-env/make-tree.sh ~/lab-02-demo/project

Команды раздела выполняются по порядку: последующие примеры опираются на каталоги и файлы, созданные в предыдущих. Вывод получен в Ubuntu под WSL2; имя пользователя заменено на student, а даты и номера inode у читателя будут другими.

Тема строится от структуры к действиям. Сначала рассмотрим, как устроено дерево каталогов Linux и что означают пути. Затем изучим навигацию и разберём каждую колонку вывода ls -l. Далее перейдём к операциям — созданию, копированию, перемещению и удалению — и к правилам, по которым оболочка раскрывает шаблоны имён. Раздел о жёстких и символических ссылках объясняет, почему два имени могут указывать на один и тот же файл, и опирается на понятие inode. Завершают тему поиск файлов командой find и оценка занятого места командами du и df.

Иерархия каталогов и пути

Единое дерево и стандарт FHS

В Windows каждый диск получает собственную букву: C:, D:. В Linux такого деления нет. Существует одно дерево, его корень обозначается косой чертой /, а все остальные каталоги и устройства подключаются к нему в виде ветвей. Диск, флешка или сетевой ресурс занимают место в дереве, которое называется точкой монтирования (англ. mount point). Этот принцип восходит к системе UNIX, описанной Ритчи и Томпсоном, где файловая система уже строилась как дерево с единым корнем 2.

Какие каталоги должны быть в дереве и что в них лежит, описывает стандарт иерархии файловой системы (англ. Filesystem Hierarchy Standard, FHS). Последняя версия, 3.0, фиксирует назначение основных каталогов и требования к ним 5. Благодаря стандарту программа, которая ищет конфигурацию в /etc, или библиотеку в /usr/lib, работает одинаково в Ubuntu, Debian или Fedora. Отличия между дистрибутивами касаются в основном того, какие пакеты установлены, а не расположения базовых каталогов.

На рисунке 1 показаны основные каталоги первого уровня, которые видны сразу после входа в систему.

Иерархия каталогов Linux: назначение основных каталогов первого уровня
Рисунок 1. Иерархия каталогов Linux по стандарту FHS

Каталоги /bin, /sbin и /lib в современных дистрибутивах часто являются символическими ссылками на /usr/bin, /usr/sbin и /usr/lib. Это решение называют объединением /usr (англ. usr merge): программы и библиотеки собраны в одном месте. Проверим это на системе, где выполняется текст:

ls -ld /bin /lib /sbin
readlink /bin
lrwxrwxrwx 1 root root 7 Jan  7  2025 /bin -> usr/bin
lrwxrwxrwx 1 root root 7 Jan  7  2025 /lib -> usr/lib
lrwxrwxrwx 1 root root 8 Jan  7  2025 /sbin -> usr/sbin
usr/bin

Первая буква строки l означает символическую ссылку; стрелка показывает, куда она ведёт. Команда readlink выводит цель ссылки без лишнего. Подробнее о ссылках — в разделе о inode ниже.

Назначение основных каталогов

Каждый каталог верхнего уровня отвечает за определённый класс данных, и это помогает понимать, где искать нужное. Каталог /etc содержит настройки системы в виде текстовых файлов: сетевые параметры, список пользователей, конфигурацию служб. Каталог /var хранит данные, которые меняются во время работы системы, — журналы в /var/log, очереди печати, базы данных служб; администрирование этих каталогов подробно описано в руководстве Нэмета и соавторов 7. Каталог /tmp предназначен для временных файлов; его содержимое обычно очищается при перезагрузке, а права на него устроены так, что каждый может создавать там файлы. Права доступа будут разобраны в теме 4.

Каталог /home занимает особое место: в нём находятся домашние каталоги пользователей, например /home/student. Именно сюда попадают файлы, которые вы создаёте в своей работе, и именно здесь по умолчанию открывается новый терминал. Каталог /root — домашний каталог администратора системы; он вынесен из /home намеренно, чтобы его можно было защитить отдельно.

Каталоги /dev и /proc не содержат обычных файлов на диске. В /dev лежат файлы устройств: диски (/dev/sda), терминалы, генератор случайных чисел и «чёрная дыра» /dev/null. В /proc ядро показывает сведения о себе и о запущенных процессах. Проверим оба каталога:

ls -l /dev/null /dev/zero
head -c 60 /proc/version; echo
cat /proc/loadavg
crw-rw-rw- 1 root root 1, 3 Oct 12 12:36 /dev/null
crw-rw-rw- 1 root root 1, 5 Oct 12 12:36 /dev/zero
Linux version 6.6.87.2-microsoft-standard-WSL2 (root@439a258
1.79 3.68 2.58 2/318 4948

Тип c в первой колонке означает символьное устройство (англ. character device). Файл /dev/null принимает любые данные и отбрасывает их; это удобно для скрытия ненужного вывода, и к этому приёму мы вернёмся в теме 5. Файлы /proc/version и /proc/loadavg имеют размер 0 байт, хотя содержимое на экране есть: это виртуальный файловый системный интерфейс, данные формирует ядро в момент чтения. Принцип «всё является файлом» (англ. everything is a file) лежит в основе этого подхода: устройства и сведения о системе доступны теми же командами, что и обычные файлы 3.

Абсолютные и относительные пути

Путь (англ. path) — строка, которая описывает положение файла в дереве. Абсолютный путь начинается с корня / и однозначно задаёт место, независимо от того, где сейчас находится пользователь. Относительный путь отсчитывается от текущего каталога и не начинается с /. Текущий каталог показывает команда pwd (англ. print working directory).

Служебные имена упрощают относительные пути. Точка . обозначает текущий каталог, две точки .. — родительский. Тильда ~ в начале пути заменяется домашним каталогом пользователя, значение которого хранится в переменной HOME. Переменные окружения подробно рассмотрены в теме 8; пока достаточно знать, что ~ и $HOME дают один и тот же путь.

cd ~/lab-02-demo/project
pwd
cd docs/img
pwd
cd ../..
pwd
cd ./src/../docs
pwd
echo ~ "$HOME"
/home/student/lab-02-demo/project
/home/student/lab-02-demo/project/docs/img
/home/student/lab-02-demo/project
/home/student/lab-02-demo/project/docs
/home/student /home/student

Последняя строка выводит два одинаковых пути: ~ и $HOME раскрываются в одно и то же значение. Заметим, что .. раскрывается на уровне файловой системы, а не текста. Путь ./src/../docs сначала переходит в src, затем поднимается вверх и попадает в docs. Если каталог src является символической ссылкой, .. ведёт себя иначе, чем подразумевает текст пути. Важнее запомнить простое правило: абсолютный путь надёжнее относительного в скриптах, потому что скрипт не знает, из какого каталога его запустят.

Отметим типичную ошибку новичков: путь с пробелом пишется без кавычек, и команда получает два разных аргумента. Пусть каталог docs содержит файл plan final.md. Команда cat docs/plan final.md будет искать два файла — docs/plan и final.md. Правильно написать cat "docs/plan final.md". Кавычки и разделение слов будут разобраны подробно в темах 5 и 13, здесь важно лишь то, что пробел в имени — обычный символ имени, а не разделитель.

Всё является файлом

Для ядра и для команд обычный текст, программа, изображение и устройство — одинаково файлы: их открывают, читают и записывают одними и теми же системными вызовами. Расширение имени в Linux ничего не означает для системы, это лишь соглашение для человека. Узнать, что на самом деле лежит в файле, позволяет команда file: она читает начало содержимого и сравнивает его с известными сигнатурами форматов 6.

cd ~/lab-02-demo/project
file README.md bin/run.sh data/image-stub.raw data/measurements-2025.csv
README.md:                  Unicode text, UTF-8 text
bin/run.sh:                 Bourne-Again shell script, Unicode text, UTF-8 text executable
data/image-stub.raw:        data
data/measurements-2025.csv: CSV text

Команда определила текст в UTF-8, сценарий оболочки (по первой строке #!/usr/bin/env bash), двоичные данные без известной сигнатуры и таблицу CSV. Тип определён по содержимому, а не по расширению: если переименовать run.sh в run.txt, file по-прежнему назовёт его сценарием.

Навигация и просмотр каталогов

Команды pwd, cd и ls

Навигация в терминале опирается на три команды. pwd печатает текущий каталог. cd (англ. change directory) переходит в другой каталог; без аргумента переходит в домашний. ls (англ. list) показывает содержимое каталога. Все три — встроенные или внешние программы, и их описание находится в справке, которая уже освоена в теме 1 4.

cd ~/lab-02-demo/project
ls
ls -F
ls -1 docs
README.md  bin  data  docs  src  tmp
README.md
bin/
data/
docs/
src/
tmp/
img
plan final.md
report 2025.txt

Вывод ls без опций не содержит скрытых файлов, то есть имён, начинающихся с точки. Опция -F добавляет к именам каталогов косую черту, а исполняемых файлов — звёздочку (здесь таких нет). Опция -1 выводит по одному имени в строке, что удобно для передачи вывода в другие программы. Но в выводе ls имена с пробелами не отличаются от разделённых, поэтому для скриптов нужны другие средства; об этом тоже будет сказано в темах 13 и 16.

Опции ls

Опции ls меняют набор и формат выводимых сведений. Некоторые из них перечислены в таблице.

ОпцияНазначение
-aпоказывать все имена, включая скрытые (начинающиеся с точки) и служебные . и ..
-Aто же, что -a, но без . и ..
-lдлинный формат: тип, права, владелец, размер, дата, имя
-hразмеры в понятной форме (4.0K, 300K), работает вместе с -l
-tсортировка по времени изменения, новые сверху
-Sсортировка по размеру, крупные сверху
-Rрекурсивно, вместе с содержимым всех подкаталогов
-dпоказать сам каталог, а не его содержимое
-iвывести номер inode, см. раздел о ссылках

Опции можно комбинировать: -lh, -lt, -la. Порядок букв не важен. Посмотрим каталог с данными, отсортированный по размеру, с читаемыми размерами:

cd ~/lab-02-demo/project
ls -lhS data
total 428K
-rw-r--r-- 1 student student 300K Jun  1 12:00 image-stub.raw
-rw-r--r-- 1 student student 117K May 20  2025 measurements-2025.csv
drwxr-xr-x 2 student student 4.0K Jun  1 12:00 archive
-rw-r--r-- 1 student student   35 Jun  1 12:00 run log.txt

Первая строка — total 428K — показывает суммарный объём блоков на диске, а не сумму размеров файлов. Это различие станет понятным в разделе о du. Заметим также, что -S ставит крупные файлы в начало, но каталог archive с размером 4.0K оказался ниже файла image-stub.raw: размер каталога — это размер его служебной таблицы имён, а не сумма вложенных файлов.

ls -lR --time-style=+%F tmp
tmp:
total 12
-rw-r--r-- 1 student student  7 2023-02-10 a.tmp
-rw-r--r-- 1 student student  7 2023-02-10 b.tmp
-rw-r--r-- 1 student student 49 2026-10-12 c.log

Скрытый каталог .cache здесь не показан, потому что -R без -a не выводит имена, начинающиеся с точки. Опция --time-style=+%F задаёт формат даты в виде ГГГГ-ММ-ДД; это дополнительная возможность GNU-версии ls 4.

Разбор вывода ls -l

Длинный формат ls -l — основной инструмент для понимания файлов. Каждая строка содержит семь полей, и важно уметь читать каждое из них. Рассмотрим строку для файла measurements-2025.csv:

cd ~/lab-02-demo/project
ls -l data/measurements-2025.csv
-rw-r--r-- 1 student student 119120 May 20  2025 data/measurements-2025.csv

Поля этой строки описаны в таблице, а рисунок 2 показывает соответствие колонок и значений.

КолонкаПримерЗначение
тип и права-rw-r--r--первый символ — тип (- файл, d каталог, l ссылка); далее девять символов прав для владельца, группы и остальных
число ссылок1для файла — число имён, указывающих на его inode; для каталога — 2 плюс число подкаталогов
владелецstudentпользователь, которому принадлежит файл
группаstudentгруппа, к которой относится файл
размер119120размер в байтах (для символической ссылки — длина пути в ней)
датаMay 20 2025время последнего изменения содержимого; для свежих файлов выводятся часы и минуты вместо года
имяdata/measurements-2025.csvимя, которое передано команде; при -l для одного файла путь не сокращается
Разбор строки ls -l по колонкам
Рисунок 2. Колонки строки вывода ls -l

Колонки «тип и права» и «владелец» подробно разбираются в теме 4, поэтому здесь ограничимся общим смыслом. Важно другое: поле «число ссылок» для обычного файла обычно равно единице, но не всегда. Если у файла два имени, оно будет равно двум. Мы увидим это в разделе о жёстких ссылках. Поле «размер» тоже требует осторожности: это логический размер, а не объём блоков на диске. Для пустого файла размер равен нулю, и он всё равно занимает inode.

Колонка даты показывает время изменения содержимого (англ. modification time). Существуют ещё время доступа и время изменения самого inode, и команда stat их выводит. Для поиска по давности файлов используются опции find -mtime, которые разберём ниже.

Создание, копирование, перемещение и удаление

Создание каталогов и файлов

Для создания каталога используется mkdir (англ. make directory). Если родительского каталога нет, mkdir без опций сообщает об ошибке. Опция -p создаёт всю цепочку и не ругается, если каталог уже существует. Попробуем создать путь с промежуточными каталогами без -p, а затем с ним:

mkdir -p ~/lab-02-demo/work
cd ~/lab-02-demo/work
mkdir reports/2025/q1
mkdir -p reports/2025/q1
ls -R reports
mkdir: cannot create directory 'reports/2025/q1': No such file or directory
reports:
2025

reports/2025:
q1

Сообщение No such file or directory — типичный симптом первой ошибки: родитель не существует. Опция -p решает проблему, но её стоит использовать осознанно: она скрывает опечатки в имени каталога, которые без -p были бы сразу заметны.

Пустой файл создаёт touch. Если файла нет, он появляется с нулевым размером; если есть — обновляется время изменения, а содержимое не меняется. Это удобно для проверки, что каталог доступен на запись, и для тестов со временем:

cd ~/lab-02-demo/work
touch "reports/notes 2025.txt" reports/.hidden
ls -la reports
total 12
drwxr-xr-x 3 student student 4096 Oct 12 13:21 .
drwxr-xr-x 3 student student 4096 Oct 12 13:21 ..
-rw-r--r-- 1 student student    0 Oct 12 13:21 .hidden
drwxr-xr-x 3 student student 4096 Oct 12 13:21 2025
-rw-r--r-- 1 student student    0 Oct 12 13:21 notes 2025.txt

Имя .hidden стало скрытым из-за точки в начале. Имя notes 2025.txt содержит пробел, поэтому при создании оно взято в кавычки. Без кавычек touch создал бы два файла: notes и 2025.txt.

Копирование и перемещение

Копирование выполняет cp (англ. copy). Для одного файла достаточно двух аргументов: источник и цель. Если цель — существующий каталог, файл попадает внутрь. Копирование каталога требует опции -r (рекурсивно). Без неё cp отказывается работать с каталогом, чтобы не скопировать случайно дерево целиком:

cd ~/lab-02-demo/work
cp -r ../project/src backup-src
ls -R backup-src
cp ../project/src backup-err
echo "exit=$?"
backup-src:
lib
main.py
utils.py

backup-src/lib:
config.ini
parser.py
cp: -r not specified; omitting directory '../project/src'
exit=1

Код возврата 1 означает ошибку; о кодах возврата подробно пойдёт речь в теме 5. Перемещение и переименование выполняет mv (англ. move). В Linux переименование — тот же mv: если цель находится в том же каталоге, это просто смена имени. Перемещение между каталогами одного раздела диска фактически меняет запись в каталоге, а данные не копируются; на разных дисках mv выполняет копирование и затем удаление.

cd ~/lab-02-demo/work
mv "reports/notes 2025.txt" reports/notes-2025.txt
ls reports
2025
notes-2025.txt

По умолчанию cp и mv молча перезаписывают существующий файл с тем же именем, что и цель: так велит традиция UNIX — программа не задаёт вопросов, если её попросили сделать ровно то, что она делает. Опция -i (англ. interactive) заставляет их спрашивать подтверждение перед перезаписью, а -n запрещает перезапись совсем 4.

Удаление и его опасность

Удаление файла делает rm (англ. remove). Пустой каталог удаляет rmdir; для непустого каталога нужна опция -r, которая удаляет всё содержимое. Команда rm -rf удаляет всё без вопросов и не сообщает о несуществующих путях, и именно она становится причиной самых тяжёлых потерь данных у начинающих. Поэтому полезно видеть, как ведут себя команды при ошибках:

cd ~/lab-02-demo/work
rmdir reports/2025/q1
rmdir reports
echo "exit=$?"
rm reports
rm -r reports/2025
ls reports
rmdir: failed to remove 'reports': Directory not empty
exit=1
rm: cannot remove 'reports': Is a directory
notes-2025.txt

Пустой каталог q1 команда rmdir удалила молча, а каталог reports, где остались 2025 и файл, — не удаляет и предупреждает об этом. Итак, rmdir не удаляет непустой каталог и предупреждает об этом. rm без -r отказывается трогать каталог. Эти защиты полезны, и их стоит уважать. Команда rm -r с заранее известным путём безопасна; опасна она тогда, когда путь получен из переменной, которая оказалась пустой. Отдельный раздел темы 15 посвящён этой ошибке. Здесь достаточно правила, которое стоит применять во всех лабораторных работах:

Перед любым удалением по шаблону сначала выводим список, который команда собирается удалить, командой echo или ls, и только потом выполняем удаление. Пусть нужно удалить временные файлы *.tmp в каталоге tmp:

cd ~/lab-02-demo/project
echo tmp/*.tmp
ls -l tmp/*.tmp
tmp/a.tmp tmp/b.tmp
-rw-r--r-- 1 student student 7 Feb 10  2023 tmp/a.tmp
-rw-r--r-- 1 student student 7 Feb 10  2023 tmp/b.tmp

Список совпал с ожиданием, значит, можно выполнять удаление. Без предварительного просмотра результат был бы тем же, но если бы шаблон совпал с чем-то неожиданным, это осталось бы незамеченным. Команда echo ничего не меняет и показывает именно то, что получит программа после раскрытия шаблона, поэтому она надёжнее многих «умных» проверок.

Шаблоны имён и раскрытие

Глоббинг: кто раскрывает шаблон

Шаблоны имён (англ. globbing или filename expansion) позволяют написать *.csv вместо перечисления всех файлов с таким расширением. Главный вопрос этого раздела: кто выполняет замену шаблона на список имён. Ответ — оболочка (англ. shell), а не программа, которой передаются аргументы 1. Программа printf или ls видит уже готовый список и не знает, что был шаблон.

На рисунке 3 показан путь команды от строки, которую ввёл пользователь, до аргументов программы.

Раскрытие шаблона оболочкой до запуска программы
Рисунок 3. Раскрытие шаблона docs/* оболочкой

Проверим на практике. Команда echo печатает свои аргументы через пробел, поэтому сразу видно, что получилось:

cd ~/lab-02-demo/project
echo *
echo *.csv
echo data/*.csv
echo tmp/?.tmp
echo "*"
README.md bin data docs src tmp
*.csv
data/measurements-2025.csv
tmp/a.tmp tmp/b.tmp
*

Первая строка показывает, что * раскрыт в список всех имён текущего каталога, кроме скрытых (имена с точкой шаблон * не совпадают, это правило оболочки по умолчанию). В строке *.csv шаблон в текущем каталоге не нашёл совпадений, поэтому оболочка передала строку как есть — echo напечатал её буквально. Такое поведение называется «сохранением несовпавшего шаблона»; о нём будет сказано ниже. Знак ? совпадает ровно с одним символом. Двойные кавычки отключают раскрытие шаблона: "*" — это обычная звёздочка.

Чтобы увидеть, что именно получает программа, удобно использовать «счётчик» аргументов. Выведем каждый аргумент в угловых скобках:

cd ~/lab-02-demo/project
printf '<%s>\n' docs/*
ls "docs/*"
echo "exit=$?"
<docs/img>
<docs/plan final.md>
<docs/report 2025.txt>
ls: cannot access 'docs/*': No such file or directory
exit=2

printf получил три аргумента, и имя с пробелом пришло как один аргумент: раскрытие не делит результат по пробелам. Команда ls "docs/*" получила строку с символом звёздочки; такого файла нет, и ls сообщил об ошибке. Код возврата 2 сообщает об ошибке: такого пути нет, и команда не нашла ничего, что могла бы вывести.

Ту же мысль можно проверить на языке Python, который видит свои аргументы напрямую:

cd ~/lab-02-demo/project
python3 -c "import sys; print(sys.argv[1:])" data/*.csv src/*.py
python3 -c "import sys; print(sys.argv[1:])" "src/*.py"
['data/measurements-2025.csv', 'src/main.py', 'src/utils.py']
['src/*.py']

В первом случае оболочка раскрыла оба шаблона до запуска Python. Во втором шаблон был закавычен, и Python получил строку буквально. Так работает любая программа: она не различает, пришёл ли аргумент из шаблона или был введён напрямую.

Если шаблон не совпал ни с одним файлом, по умолчанию bash передаёт его как есть. Это удобно для отладки, но опасно в скриптах. Опция nullglob меняет поведение: несовпавший шаблон исчезает. Проверим:

cd ~/lab-02-demo/project
echo tmp/nomatch*.xyz
shopt -s nullglob
echo tmp/nomatch*.xyz
echo "(пусто)"
tmp/nomatch*.xyz

(пусто)

Команда shopt -s nullglob меняет поведение текущей оболочки. Во второй строке echo напечатал пустую строку, потому что аргументов не осталось, а затем (пусто). Опция действует до конца сеанса оболочки, поэтому в следующих примерах ею лучше не пользоваться.

Классы символов и фигурные скобки

Помимо * и ?, оболочка понимает классы символов: [abc] совпадает с одним символом из набора, [a-z] — с одним символом из диапазона, а [!abc] — с символом вне набора. В этой теме шаблон [0-9] удобен для поиска файлов с номерами:

cd ~/lab-02-demo/work
touch run-1.log run-2.log run-10.log run-a.log
echo run-[0-9].log
echo run-[!0-9].log
run-1.log run-2.log
run-a.log

Шаблон run-[0-9].log не совпал с run-10.log, потому что класс задаёт ровно один символ. Фигурные скобки работают иначе: это раскрытие скобок (англ. brace expansion), которое выполняется раньше всех остальных раскрытий и не зависит от наличия файлов. Оно просто генерирует строки.

mkdir -p ~/lab-02-demo/work/reports
cd ~/lab-02-demo/work
echo {a,b,c}.txt
echo file{1..5}.log
echo {01..10..3}
echo pre{A,B}post
echo {x}
echo "$HOME/{docs,src}"
echo $HOME/{docs,src}
a.txt b.txt c.txt
file1.log file2.log file3.log file4.log file5.log
01 04 07 10
preApost preBpost
{x}
/home/student/{docs,src}
/home/student/docs /home/student/src

Фигурные скобки с запятыми дают список вариантов, диапазон {1..5} — числа, а с шагом {01..10..3} — числа с нулями и шагом 3. Строка {x} без запятой или диапазона остаётся неизменной. Последние две строки важны: двойные кавычки отключают раскрытие скобок, а без кавычек оно сработало. Знание этого порядка нужно для предсказания результата. Команда создания нескольких каталогов с помощью скобок выглядит так:

cd ~/lab-02-demo/work
mkdir -p reports/{2024,2025}/{q1,q2}
find reports -type d | sort
reports
reports/2024
reports/2024/q1
reports/2024/q2
reports/2025
reports/2025/q1
reports/2025/q2

Здесь шаблоны не использовались: скобки раскрылись в четыре пути, которые mkdir -p создал по одному. Команда find с опцией -type d перечисляет созданные каталоги, а sort упорядочивает их; о find речь пойдёт дальше.

Имена с пробелами и типичные ошибки

Файлы с пробелами, скобками, кириллицей и знаками $, *, & встречаются повсеместно. Их легко создать, а работать с ними сложнее всего. Типичные ошибки можно свести к трём. Первая — забыть кавычки вокруг переменной или пути с пробелом; мы уже видели, как cat docs/plan final.md ищет два файла. Вторая — использовать ls в скриптах для перебора имён: вывод ls не разделяет имена однозначно, если в них есть пробелы или перевод строки. Третья — путать шаблон и строку: rm *.tmp удаляет файлы, а rm "*.tmp" пытается удалить файл с именем, в котором есть звёздочка.

Надёжный способ работать с пробелами в командной строке — подставлять имя в кавычках или использовать шаблон, который оболочка раскроет сама. Для скриптов правила изменятся: там мы будем использовать "$var" и циклы по шаблонам, а не ls. Пока достаточно привычки: любой путь с пробелом берём в двойные кавычки.

Inode и ссылки

Что такое inode

Имя файла хранится не в самом файле, а в каталоге. Каталог — это таблица соответствий «имя — номер», и номер указывает на inode (англ. index node) — структуру, в которой хранятся метаданные файла: тип, права, владелец, размер, времена, а также адреса блоков на диске, где лежат данные. Эта организация восходит к файловой системе UNIX, описанной в 1974 году, где inode уже были основной единицей хранения 2. Подробности описаны в справке inode(7) 6.

Из этого устройства следуют важные следствия. Во-первых, у файла может быть несколько имён, если несколько записей каталога указывают на один и тот же inode. Во-вторых, имя файла и его содержимое — разные вещи: переименование не трогает данные. В-третьих, inode имеет ограниченное число, поэтому файловая система может закончиться по числу inode, даже когда свободного места на диске много.

Номер inode показывает опция -i команды ls. Создадим учебный каталог с тремя связанными файлами.

mkdir -p ~/lab-02-demo/links && cd ~/lab-02-demo/links
printf 'строка 1\nстрока 2\n' > original.txt   # > записывает вывод в файл, тема 5
ln original.txt hard.txt
ln -s original.txt soft.txt
ln -s no-such-file.txt broken.txt
ls -li
437858 lrwxrwxrwx 1 student student 16 Oct 12 13:16 broken.txt -> no-such-file.txt
437855 -rw-r--r-- 2 student student 30 Oct 12 13:16 hard.txt
437855 -rw-r--r-- 2 student student 30 Oct 12 13:16 original.txt
437856 lrwxrwxrwx 1 student student 12 Oct 12 13:16 soft.txt -> original.txt

Заметим, что hard.txt и original.txt имеют один и тот же номер inode, 437855, и в колонке числа ссылок стоит 2. Символические ссылки soft.txt и broken.txt имеют собственные номера. Рисунок 4 показывает эту схему.

Два имени указывают на один inode, символическая ссылка хранит путь
Рисунок 4. Жёсткие и символические ссылки в каталоге и в inode

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

Жёсткая ссылка (англ. hard link) — это второе имя того же inode. Создаётся командой ln без опций. Жёсткая ссылка равноправна с исходным именем: нет «оригинала» и «копии». Файл удаляется только тогда, когда исчезают все его имена. Команда stat выводит подробности и позволяет проверить это:

cd ~/lab-02-demo/links
stat original.txt
stat -c '%i %h %s %n' original.txt hard.txt soft.txt
  File: original.txt
  Size: 30        	Blocks: 8          IO Block: 4096   regular file
Device: 830h/2096d	Inode: 437855      Links: 2
Access: (0644/-rw-r--r--)  Uid: ( 1000/ student)   Gid: ( 1000/ student)
Access: 2026-10-12 13:16:41.156347566 +0300
Modify: 2026-10-12 13:16:41.156347566 +0300
Change: 2026-10-12 13:16:41.240347562 +0300
 Birth: 2026-10-12 13:16:41.156347566 +0300
437855 2 30 original.txt
437855 2 30 hard.txt
437856 1 12 soft.txt

Команда stat печатает поля inode в человекочитаемом виде. Опция -c с форматом %i %h %s %n выводит номер inode (%i), число жёстких ссылок (%h), размер (%s) и имя (%n). Поля Links: 2 и Inode: 437855 совпадают с выводом ls -li. Строка Change показывает время последнего изменения самого inode, например смены прав или числа ссылок; оно может отличаться от времени изменения содержимого.

Удалим исходное имя и проверим, что данные сохранились:

cd ~/lab-02-demo/links
rm original.txt
cat hard.txt
ls -li
строка 1
строка 2
437858 lrwxrwxrwx 1 student student 16 Oct 12 13:16 broken.txt -> no-such-file.txt
437855 -rw-r--r-- 1 student student 30 Oct 12 13:16 hard.txt
437856 lrwxrwxrwx 1 student student 12 Oct 12 13:16 soft.txt -> original.txt

После удаления original.txt данные остались доступны через hard.txt, а счётчик ссылок вернулся к единице. Файл будет удалён окончательно, когда исчезнет последнее имя, — то есть тогда, когда счётчик ссылок станет нулевым. Есть два ограничения жёстких ссылок. Они не могут пересекать границы файловой системы, потому что номер inode уникален только внутри одного раздела. Жёсткую ссылку на каталог обычно создать нельзя.

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

Символическая ссылка (англ. symbolic link, или symlink) — отдельный файл с собственным inode, содержимое которого — путь к другому файлу. Создаётся командой ln -s. Цель не обязана существовать и может находиться на другом разделе. Когда программа открывает ссылку, ядро берёт записанный путь и ищет цель заново. Поэтому символическая ссылка становится «битой», если цель удалена или переименована.

В выводе ls -l символическая ссылка узнаётся по первой букве l и стрелке ->. Ранее мы видели, что soft.txt занимает 12 байт — это длина строки original.txt, а не размер файла. Проверим поведение битой ссылки:

cd ~/lab-02-demo/links
cat broken.txt
echo "exit=$?"
ls -l broken.txt
file hard.txt soft.txt
cat: broken.txt: No such file or directory
exit=1
lrwxrwxrwx 1 student student 16 Oct 12 13:16 broken.txt -> no-such-file.txt
hard.txt: Unicode text, UTF-8 text
soft.txt: broken symbolic link to original.txt

Команда file распознала soft.txt как сломанную ссылку на original.txt, которого уже нет. Команда readlink выводит только цель ссылки, не разыменовывая её. Опция -f возвращает полный путь к реальному файлу, если он существует:

cd ~/lab-02-demo/links
ln -s hard.txt link.txt
readlink link.txt
readlink -f link.txt
ls -l link.txt
hard.txt
/home/student/lab-02-demo/links/hard.txt
lrwxrwxrwx 1 student student 8 Oct 12 13:25 link.txt -> hard.txt

Здесь readlink link.txt выводит относительный путь, записанный в ссылке, а readlink -f превращает его в абсолютный и разрешает все промежуточные ссылки. Заметим, что readlink -f выводит путь, даже если последний его компонент не существует; чтобы получить пустой вывод и ненулевой код возврата для битой ссылки, используется readlink -e. Относительная запись hard.txt интерпретируется относительно каталога, где лежит ссылка, а не относительно текущего каталога оболочки. Это частая причина ошибок в скриптах.

Команда stat

Команда stat уже встречалась, но её стоит выделить отдельно, потому что она показывает то, что скрыто от ls -l: номер inode, число ссылок, время изменения inode, размер блоков на диске. Поле Blocks выражено в блоках по 512 байт, а IO Block — размер блока ввода-вывода файловой системы, обычно 4096 байт. Эти значения понадобятся при разборе размеров в следующем разделе.

Поиск, место на диске и WSL

Поиск файлов командой find

Команда find ищет файлы в дереве по условиям: имени, типу, размеру, времени изменения, глубине вложенности. Синтаксис: find путь условия действие. Путь — отправная точка обхода, условия — фильтры, действие по умолчанию — печать имени 6. Все условия объединяются логическим И, если не указано иное. Основные условия перечислены в таблице.

УсловиеСмысл
-name шаблонимя совпадает с шаблоном (с учётом регистра); шаблон в кавычках
-iname шаблонто же, без учёта регистра
-type f, -type d, -type lобычный файл, каталог, символическая ссылка
-size +100kразмер больше 100 КиБ (размер округляется вверх до целых КиБ); -size -1k — округлённый размер меньше 1, то есть только пустые файлы; без знака — точно
-mtime -1изменён менее одних суток назад; -mtime +365 — более 365 суток назад
-maxdepth Nне спускаться глубже N уровней от пути
-emptyпустой файл или пустой каталог
-newer файлизменён позже, чем указанный файл

Сначала простой поиск по имени и типу:

cd ~/lab-02-demo/project
find . -name '*.csv'
find . -type d
find . -maxdepth 1 -type f
./data/measurements-2025.csv
.
./tmp
./src
./bin
./docs
./.config
./data
./tmp/.cache
./src/lib
./docs/img
./data/archive
./README.md
./.env

Шаблон '*.csv' взят в кавычки, чтобы оболочка не раскрыла его в текущем каталоге. Вывод find не отсортирован: порядок определяется файловой системой, поэтому в скриптах его сортируют явно. Опция -maxdepth 1 ограничивает обход текущим каталогом, и в списке остаются только его непосредственные файлы.

Теперь условия по размеру и времени:

cd ~/lab-02-demo/project
find . -type f -size +100k
find . -type f -size -1k
find . -type f -mtime -1 | sort
find . -type f -mtime +365 | sort
./data/image-stub.raw
./data/measurements-2025.csv
./docs/img/.keep
./src/main.py
./tmp/c.log
./data/archive/old-1.bak
./data/archive/old-2.bak
./data/measurements-2025.csv
./tmp/a.tmp
./tmp/b.tmp

Условие -size -1k выбирает только пустой файл .keep, потому что размер округляется вверх до единиц по 1 КиБ. Условие -mtime -1 ловит файлы, изменённые менее суток назад: это main.py и c.log, которые генератор задал «три часа назад». Условие -mtime +365 выбирает старые файлы: копии 2023 года и CSV-файл, датированный маем 2025 года. Значение -mtime округляется вниз до целых суток, поэтому граница «365» зависит от времени суток, а не от календарной даты.

Условия можно комбинировать операторами -o (или), ! (не) и скобками, которые нужно экранировать:

cd ~/lab-02-demo/project
find . -name '*.tmp' -o -name '*.bak' | sort
find . -type f -name '*.*' ! -name '*.tmp' | sort
find . -type f \( -name '*.csv' -o -name '*.py' \) | sort
./data/archive/old-1.bak
./data/archive/old-2.bak
./tmp/a.tmp
./tmp/b.tmp
./.config/app.conf
./.env
./README.md
./bin/run.sh
./data/archive/notes.txt
./data/archive/old-1.bak
./data/archive/old-2.bak
./data/image-stub.raw
./data/measurements-2025.csv
./data/run log.txt
./docs/img/.keep
./docs/plan final.md
./docs/report 2025.txt
./src/lib/config.ini
./src/lib/parser.py
./src/main.py
./src/utils.py
./tmp/.cache/session.dat
./tmp/c.log
./data/measurements-2025.csv
./src/lib/parser.py
./src/main.py
./src/utils.py

Скобки \( … \) экранированы обратным слешем, потому что для оболочки круглые скобки — элемент синтаксиса (запуск подоболочки), а find должна получить их как обычные аргументы. Во втором примере заметим, что в find, в отличие от оболочки, звёздочка совпадает и с точкой в начале имени: шаблон -name '*.*' выбрал не только файлы с расширением, но и скрытые .env и .keep, а условие ! -name '*.tmp' отбросило из этого списка лишь временные файлы. Такие подробности и составляют основную трудность поиска: результат всегда точно соответствует условиям, но условия нужно продумать.

Действие над найденным выполняет -exec или -delete. Здесь важно соблюдать правило из раздела об удалении: сначала посмотреть список. Действие -print печатает путь явно, а -delete удаляет. Пример с предварительным просмотром:

cd ~/lab-02-demo/work
cp -r ../project/tmp ./tmp-copy
find tmp-copy -name '*.tmp' -print
find tmp-copy -name '*.tmp' -delete
ls -a tmp-copy
tmp-copy/b.tmp
tmp-copy/a.tmp
.
..
.cache
c.log

Сначала печать подтвердила, что найдены именно *.tmp в копии, затем -delete удалил их, а ls -a показал остаток. Опция -exec команда {} + передаёт найденные имена команде пачками; она эффективнее, чем -exec ... \;, потому что запускает программу меньше раз. Имена с пробелами find передаёт корректно, в отличие от ls в цикле.

Для быстрого поиска по имени во всей системе существует также команда locate (в Ubuntu её даёт пакет plocate, устанавливается командой sudo apt install plocate). Она не обходит дерево, а ищет в заранее построенной базе имён, которая обновляется раз в сутки или командой sudo updatedb. Поэтому locate отвечает мгновенно, но не видит файлов, созданных после последнего обновления базы, и не умеет отбирать файлы по размеру или времени. Для учебных задач курса достаточно find.

Размер файлов: du и df

Размер файла и место, которое он занимает на диске, — разные величины. Логический размер (англ. apparent size) — число байт, которое видит программа. Занятое место — число блоков файловой системы, выделенных под файл. Для обычного файла они почти совпадают, но разреженный файл (англ. sparse file) может занимать меньше места, чем его размер: под области, в которые никогда ничего не записывалось («дыры»), блоки не выделяются, а при чтении они дают нули. Проверим это на файле размером 10 МиБ, который создан командой truncate без записи данных:

mkdir -p ~/lab-02-demo/sp && cd ~/lab-02-demo/sp
truncate -s 10M sparse.img
ls -lh sparse.img
du -h sparse.img
du -h --apparent-size sparse.img
-rw-r--r-- 1 student student 10M Oct 12 13:25 sparse.img
0	sparse.img
10M	sparse.img

Команда ls -lh показывает 10 МиБ, du — ноль байт, потому что на диске не выделено ни одного блока, а du --apparent-size возвращает логический размер. Теперь создадим файл размером 1 МиБ, заполненный нулями, но уже записанными на диск:

cd ~/lab-02-demo/sp
head -c 1M /dev/zero > zeros.img
ls -lh zeros.img
du -h zeros.img
-rw-r--r-- 1 student student 1.0M Oct 12 13:25 zeros.img
1.0M	zeros.img

Команда head -c 1M /dev/zero записывает реальные нули, поэтому они занимают место. Так же устроен файл data/image-stub.raw в учебном проекте: du показывает его полный размер 300 КиБ.

Команда du (англ. disk usage) суммирует занятое место по каталогам. Опции -h (читаемый вид), -s (только итог), --max-depth=N (глубина вывода) и --apparent-size (логический размер) покрывают большинство задач.

cd ~/lab-02-demo/project
du -h --max-depth=1 .
du -sh data
du -ah data | sort -rh | head -4
24K	./tmp
24K	./src
8.0K	./bin
16K	./docs
8.0K	./.config
444K	./data
536K	.
444K	data
300K	data/image-stub.raw
120K	data/measurements-2025.csv
16K	data/archive

Итог 536K для всего каталога больше суммы видимых файлов, потому что каждый каталог и каждый файл занимают хотя бы один блок. Символ | передаёт вывод du команде sort (конвейеры — тема 5), а сортировка sort -rh ставит наибольшие элементы в начало: это удобный способ найти, что занимает место. Команда df (англ. disk free) показывает свободное место на файловой системе целиком:

cd ~/lab-02-demo/project
df -h .
df -hT /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdd       1007G   93G  864G  10% /
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/sdd       ext4 1007G   93G  864G  10% /

Команда df показывает раздел, на котором лежит указанный путь, поэтому df . и df / могут совпадать. Опция -T добавляет тип файловой системы (ext4). Если свободное место на диске закончилось, df покажет 100 процентов в столбце Use%, а программы начнут сообщать «No space left on device».

Файлы Windows в WSL2

Если работа идёт в Windows через WSL2 (подсистему Linux, описанную в документации Microsoft 8), диски Windows доступны в каталогах /mnt/c, /mnt/d и так далее. Команда ls /mnt показывает их:

ls /mnt
c
d
wsl
wslg

Работа с файлами в /mnt/c возможна, но она идёт через мост между файловыми системами Linux и Windows, поэтому операции с большим количеством мелких файлов выполняются заметно медленнее, чем в домашнем каталоге /home внутри WSL. Для лабораторных работ рекомендуется держать свои файлы в домашнем каталоге Linux.

Итоги темы

В этой теме дерево каталогов Linux предстало как единая структура с устойчивым назначением каталогов, описанным стандартом FHS, а путь — как способ адресации этого дерева. Навигация опирается на pwd, cd и ls, а вывод ls -l даёт семь полей, из которых для обычного файла важны тип, число ссылок, размер и время изменения. Операции создания, копирования и перемещения просты, но удаление требует дисциплины: список целей сначала выводится командами echo или ls, а только потом выполняется rm.

Оболочка раскрывает шаблоны *, ?, классы [...] и фигурные скобки до запуска программы, и поэтому программа не знает, откуда пришли её аргументы. Имя файла хранится в каталоге и указывает на inode, где лежат метаданные и адреса данных. Жёсткие ссылки — это несколько имён одного inode, символические ссылки — отдельные файлы, хранящие путь. Команда find отбирает файлы по имени, типу, размеру и давности, а du и df показывают занятое и свободное место.

Следующая тема переходит от файлов к их содержимому: просмотр и редактирование текстовых файлов начинаются с понятия строки и кодировки.

Литература

  1. Shotts W. The Linux Command Line: A Complete Introduction. 2nd ed. — No Starch Press, 2019. Открытая версия: https://linuxcommand.org/tlcl.php
  2. Ritchie D. M., Thompson K. The UNIX Time-Sharing System. — Communications of the ACM, 1974, Vol. 17, No. 7, С. 365–375, DOI: 10.1145/361011.361061.
  3. Kernighan B. W., Pike R. The UNIX Programming Environment. — Prentice Hall, 1984.
  4. Free Software Foundation. GNU Coreutils Manual. — https://www.gnu.org/software/coreutils/manual/
  5. Linux Foundation. Filesystem Hierarchy Standard 3.0. — 2015. https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.html
  6. The Linux man-pages project. — https://man7.org/linux/man-pages/
  7. Nemeth E., Snyder G., Hein T. R., Whaley B., Mackin D. UNIX and Linux System Administration Handbook. 5th ed. — Addison-Wesley, 2017.
  8. Microsoft. Документация WSL. — https://learn.microsoft.com/windows/wsl/