Основы Linux: shell, файлы и пайпы
Файловая система, команды, которые вы набираете каждый день, и как связывать их между собой.
За 30 секунд
Linux — операционная система, на которой работает почти любой веб-сервер. Общаетесь вы с ней через shell: вводите команду, она запускает небольшую программу и выводит текст в ответ. Разберитесь, где лежат файлы, выучите десяток повседневных команд и поймите, как пайпы связывают их в цепочки, — и большинство проблем на сервере вы сможете находить и чинить сами.
Почему я это использую
- Я работаю с Linux каждую неделю: все серверы, которые я веду для своих продуктов и для клиентов, — это машины на Ubuntu, которыми я управляю по SSH.
- Когда ломается продакшен, ответ почти всегда лежит в лог-файле или в конфиге. Хорошее знание shell позволяет найти его за минуты, а не за часы.
- Shell — единственный инструмент, который одинаково работает на дешёвом VPS, внутри Docker-контейнера и в CI-пайплайне, поэтому время, вложенное в него, окупается везде.
- Небольшие цепочки вроде grep, sort и uniq отвечают на вопросы, для которых иначе понадобился бы скрипт или дашборд.
Как это работает
01Что такое Linux и дистрибутив
Строго говоря, Linux — это только ядро: центральная программа, которая общается с железом, планирует процессы и управляет памятью. Дистрибутив объединяет это ядро с пакетным менеджером, системными утилитами, конфигурацией по умолчанию и графиком релизов. На серверах вы в основном встретите два семейства. Семейство Debian (Debian, Ubuntu) использует пакеты .deb и apt. Семейство Red Hat (RHEL, Rocky Linux, AlmaLinux, Fedora) — пакеты .rpm и dnf. Для серверов выбирайте релиз с долгосрочной поддержкой (LTS): LTS-версии Ubuntu выходят раз в два года и получают пять лет стандартных обновлений безопасности, Debian stable с расширением LTS поддерживается около пяти лет, а RHEL-совместимые релизы — примерно десять. Почти всё в этом уроке одинаково работает в любом дистрибутиве; основные различия — названия пакетов, пакетный менеджер и расположение некоторых конфигов.
- Ядро: сердце ОС. Дистрибутив: ядро плюс утилиты, пакеты и настройки по умолчанию.
- Debian / Ubuntu: apt и .deb. RHEL / Rocky / Alma: dnf и .rpm.
- Shell: программа, которая читает ваши команды. На большинстве серверов по умолчанию стоит Bash.
02Иерархия файловой системы: что где лежит
В Linux одно дерево каталогов, начинающееся с / (корня). Букв дисков нет: дополнительные диски подключаются (монтируются) куда-то внутрь этого дерева. Структура следует стандарту Filesystem Hierarchy Standard, так что, выучив её один раз, вы сориентируетесь на любом сервере. Полезное правило: конфигурация живёт в /etc, всё, что меняется во время работы системы (логи, кэши, базы данных, сайты), — в /var, программы — в /usr, а люди — в /home. Всё является файлом, включая устройства (/dev) и живую информацию ядра (/proc, /sys), поэтому так много инструментов сводится к чтению и записи текста.
- /etc: конфигурация системы и сервисов (nginx, ssh, cron, пользователи).
- /var: изменяемые данные. /var/log — логи, /var/www — сайты (по соглашению), /var/lib — базы данных и состояние приложений.
- /usr: установленные программы и библиотеки. /usr/local — то, что вы ставите вручную.
- /home/your_user: личные файлы. /root — домашний каталог пользователя root.
- /tmp: временные файлы, часто очищаются при перезагрузке. /opt: опциональные самодостаточные приложения.
- /dev, /proc, /sys: устройства и живая информация о ядре и процессах.
03Навигация и работа с файлами
Горстка команд покрывает большую часть повседневной работы. pwd показывает, где вы находитесь, cd перемещает, ls выводит список. Пути, начинающиеся с /, абсолютные; всё остальное отсчитывается от текущего каталога. Одна точка означает «здесь», две точки — родительский каталог, тильда — ваш домашний каталог. Осторожнее с rm: корзины нет, и rm -r удаляет целое дерево, ничего не спрашивая. Хорошая привычка — сначала выполнить ls на том же самом пути и никогда не вставлять команду rm с переменной, которую вы не проверили. find — швейцарский нож для поиска файлов по имени, размеру, возрасту или типу.
pwd # where am I?
ls -lah /var/log # long list, all files, human sizes
cd /etc/nginx && ls # move, then list
cd ~ ; cd - # go home, then back to the previous dir
mkdir -p ~/backups/2026 # create a path, including parents
cp -a site/ site-backup/ # copy recursively, keep owners and times
mv old.conf old.conf.bak # move or rename
rm -i notes.txt # delete, asking first
rm -r build/ # delete a directory tree (no undo!)
find /var/www -name "*.log" -size +100M # big log files
find /tmp -type f -mtime +7 # files older than 7 days
04Чтение файлов, пайпы и перенаправление
cat выводит файл целиком, less позволяет листать его и искать (нажмите / для поиска, q для выхода), head и tail показывают начало или конец, а tail -f продолжает следить за файлом по мере появления новых строк — идеально, чтобы смотреть лог, пока вы воспроизводите баг. Настоящая сила — в пайпах. У каждой программы есть стандартный ввод, стандартный вывод и стандартный поток ошибок. Символ пайпа | отправляет вывод одной программы на вход следующей, и маленькие инструменты складываются в ответ. Перенаправление отправляет вывод в файлы: > перезаписывает, >> дописывает, а 2> отдельно перехватывает ошибки. 2>&1 сливает ошибки с обычным выводом.
less /var/log/syslog # scroll and search
tail -n 100 -f /var/log/nginx/error.log # follow live
# Top 10 client IPs in an access log
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
# Save output, append to a file, capture errors
df -h > disk.txt
date >> deploy.log
./deploy.sh > deploy.out 2>&1
# Count 500 errors
grep -c '" 500 ' /var/log/nginx/access.log
05grep, awk и sed за пять минут
Эти три инструмента решают большинство задач с текстом. grep находит строки, совпадающие с шаблоном: -i игнорирует регистр, -r ищет по каталогам, -n показывает номера строк, -v инвертирует совпадение, а -E включает расширенные регулярные выражения. awk разбивает каждую строку на поля ($1, $2…) и идеально подходит, чтобы вытаскивать колонки из логов. sed редактирует поток текста, чаще всего для поиска и замены. Всегда сначала запускайте sed без -i, чтобы посмотреть результат, а при редактировании на месте сохраняйте резервную копию через -i.bak. Для всего, что длиннее одной строки awk или sed, небольшой скрипт на нормальном языке обычно понятнее.
grep -rn "DB_HOST" /var/www/app/config # where is it set?
grep -i "error" app.log | grep -v "deprecated"
grep -E "timeout|refused" app.log
awk -F: '{print $1}' /etc/passwd # first column, ":" separator
awk '$9 >= 500 {print $7}' access.log # paths that returned 5xx
sed 's/http:/https:/g' links.txt # preview a replacement
sed -i.bak 's/^#Port 22/Port 22/' my.conf # edit in place, keep .bak
06Редакторы, справка и переменные окружения
На сервере файлы редактируют прямо в терминале. nano — самый дружелюбный: сочетания клавиш перечислены внизу (Ctrl+O сохраняет, Ctrl+X выходит). vim есть везде, и его стоит знать хотя бы на уровне выживания: нажмите i, чтобы вводить текст, Esc, чтобы закончить, затем :wq — сохранить и выйти, или :q! — выйти без сохранения. Забыли флаг — используйте command --help для краткой справки или man command для полного руководства. Переменные окружения — это именованные значения, которые программы наследуют от shell, например PATH (где искать команды) и HOME. Задать переменную на текущую сессию можно через export, сделать постоянной — в ~/.bashrc. Приложения должны читать секреты из переменных окружения или env-файла, но никогда не из кода.
nano /etc/hosts # simple editor
vim /etc/hosts # i = insert, Esc, :wq = save & quit
ls --help | less # quick help
man rsync # full manual (q to quit)
echo $PATH # where commands are found
export APP_ENV=production
printenv | grep APP_ # show matching variables
which node # which binary runs?
07Ваш первый shell-скрипт
Shell-скрипт — это текстовый файл с командами, которые выполняются сверху вниз. Первая строка (shebang) указывает, какой интерпретатор использовать. Начинайте каждый bash-скрипт с set -euo pipefail: -e останавливает скрипт на первой упавшей команде, -u считает неустановленные переменные ошибкой, а pipefail заставляет пайплайн падать, если упала любая его часть. Без этого скрипт может продолжить работу после сбоя и натворить бед, например удалить файлы не в том каталоге, потому что переменная оказалась пустой. Берите переменные в кавычки, делайте файл исполняемым через chmod +x и проверяйте его shellcheck, если есть возможность его поставить.
#!/usr/bin/env bash
set -euo pipefail
# Archive a directory with today's date
SRC="/var/www/app/storage"
DEST="$HOME/backups"
STAMP="$(date +%F)"
mkdir -p "$DEST"
tar -czf "$DEST/storage-$STAMP.tar.gz" -C "$SRC" .
echo "Saved $DEST/storage-$STAMP.tar.gz"
# Delete archives older than 14 days
find "$DEST" -name "storage-*.tar.gz" -mtime +14 -delete
Когда применять
- Управление любым сервером, VPS или контейнером: shell — общий интерфейс для всех.
- Разбор проблем в продакшене: логи, конфиги и запущенные процессы — всё это текст.
- Автоматизация рутины — бэкапов, очистки, шагов деплоя — короткими скриптами.
- Быстрые вопросы к логам (топ IP, количество ошибок) без построения дашборда.
Когда не стоит
- Большая или сложная автоматизация: когда скрипт перерастает страницу, переходите на Python, Node или инструмент вроде Ansible.
- Правка продакшен-конфига вслепую: сначала скопируйте его и проверьте (например, nginx -t), и только потом перезагружайте сервис.
- Повседневная работа на десктопе, где GUI просто быстрее; за то, что всё делаешь в терминале, призов не дают.
Альтернативы
- zsh / fish
Более дружелюбные интерактивные оболочки с лучшим автодополнением. Отлично для ноутбука, но серверные скрипты всё равно пишите на bash или POSIX sh.
- Скрипты на Python или Node
Лучше подходят для логики, JSON, API и обработки ошибок, когда задача больше нескольких команд.
- Ansible
Описывает настройку сервера в виде повторяемых плейбуков вместо разовых команд, набранных руками.
- Панели управления хостингом
Панели вроде Plesk или cPanel прячут shell за интерфейсом. Удобно, но когда что-то ломается, shell всё равно нужен.
Лучшие практики
- Выполняйте ls на пути, прежде чем делать на нём rm, и избегайте rm -rf с непроверенными переменными.
- Делайте копию конфига (cp file file.bak) перед правкой и проверяйте его перед перезагрузкой сервиса.
- Для больших файлов используйте less и tail -f вместо cat: cat на логе в 2 GB зальёт весь терминал.
- Начинайте скрипты с set -euo pipefail и всегда берите переменные в кавычки: "$VAR".
- В скриптах и cron-задачах предпочитайте абсолютные пути: они не зависят от текущего каталога.
- Освойте man и --help: они быстрее и точнее, чем полузабытые статьи из блогов.
- Храните секреты в переменных окружения или защищённых env-файлах, а не в скриптах и не в истории shell.
Частые ошибки
- Запуск rm -rf $DIR/ с пустой переменной, который превращается в rm -rf / и сносит систему.
- Использование > вместо >>: файл перезаписывается, а не дополняется.
- Правка файла через sed -i без предварительного просмотра изменений и без резервной копии.
- Забывают, что пути и имена файлов в Linux чувствительны к регистру: Config.php — это не config.php.
- Вставка команд из интернета под root без понимания, что делает каждая часть.
Глубже
08Что на самом деле делает shell, когда вы нажимаете Enter
Прежде чем запустится какая-либо программа, bash переписывает строку в фиксированном порядке: brace expansion, tilde expansion, затем подстановка параметров, команд и арифметики, затем word splitting, затем globbing имён файлов и, наконец, удаление кавычек. Только после этого он ищет команду: сначала алиасы, функции и builtin-команды (cd, export, read), затем каждый каталог из PATH, а результаты кэшируются в хэш-таблице. Для внешней программы shell делает fork своей копии, и дочерний процесс вызывает exec, заменяя себя программой; родитель ждёт и сохраняет код завершения в $?. Это объясняет классические сюрпризы: cd обязана быть builtin, потому что дочерний процесс не может сменить каталог родителя, переменная с пробелами без кавычек превращается в несколько аргументов, а glob, который ничему не соответствует, передаётся буквально. type и command -v точно говорят, что будет запущено.
type cd ls grep # builtin, alias or file?
command -v python3 # path that will be executed
hash -r # forget cached paths after moving a binary
false; echo $? # 1: last exit status
f="my file.txt"
ls $f # WRONG: two arguments, "my" and "file.txt"
ls "$f" # right: one argument
set -x # print each command after expansion (debug)
09Inode, жёсткие и символические ссылки
Имя файла — это не сам файл. Данные и метаданные (владелец, права, временные метки, размер, указатели на блоки) хранятся в inode, который идентифицируется номером; каталог — это просто таблица, сопоставляющая имена с номерами inode. Жёсткая ссылка — второе имя того же inode, поэтому оба имени равноправны, а данные живут, пока не удалена последняя ссылка и ни один процесс не держит файл открытым. Жёсткие ссылки не могут пересекать границы файловых систем или указывать на каталоги. Символическая ссылка — крошечный файл с путём, который может указывать куда угодно, даже в никуда (битая ссылка). Эта модель объясняет, почему mv в пределах одной файловой системы мгновенный (меняется лишь запись в каталоге), а между файловыми системами копирует данные, почему удаление открытого лога не освобождает место и почему инструменты деплоя атомарно переключают релизы, заменяя symlink «current».
ls -li /etc/hostname # inode number in the first column
stat /etc/hostname # full metadata, link count, times
ln data.csv data-copy.csv # hard link: same inode
ln -s /srv/app/releases/42 /srv/app/current.new
mv -T /srv/app/current.new /srv/app/current # atomic symlink swap
readlink -f /srv/app/current # resolve to the real path
find /srv -xtype l # dangling symlinks
10Скрипты, которые выдерживают реальные данные
Продакшен-скрипты сталкиваются с именами файлов, содержащими пробелы, переводы строк и ведущие дефисы, с прерванными запусками и частичными сбоями. Три привычки решают большую часть проблем. Первая: передавайте списки файлов, разделённые NUL: find -print0 вместе с xargs -0 или цикл while read -d '', и никогда цикл for по выводу ls. Вторая: создавайте временные файлы через mktemp и удаляйте их в trap, чтобы очистка происходила даже при сбое или прерывании скрипта. Третья: знайте, где set -e не срабатывает: внутри условий if, слева от && или || и в подстановках команд, присваиваемых через local, поэтому критичные шаги проверяйте явно. Используйте -- для завершения опций перед путями от пользователя, чтобы файл с именем -rf считался именем. Для блокировки flock не даёт двум копиям одной задачи работать одновременно.
#!/usr/bin/env bash
set -euo pipefail
tmp="$(mktemp -d)"
trap 'rm -rf -- "$tmp"' EXIT # always cleaned up
exec 9>/var/lock/report.lock
flock -n 9 || { echo "already running" >&2; exit 1; }
find /var/www/uploads -type f -name '*.jpg' -mtime -1 -print0 |
while IFS= read -r -d '' file; do
cp -- "$file" "$tmp/"
done
tar -czf /var/backups/uploads-today.tar.gz -C "$tmp" .
11Быстрая обработка текста в больших файлах
На многогигабайтном логе выбор инструмента и флагов имеет значение. Locale — скрытая цена: с UTF-8 locale sort и grep выполняют дорогие посимвольные сравнения с учётом кодировки; префикс LC_ALL=C заставляет их сравнивать байты, и это часто в разы быстрее, что вполне подходит для логов и IP. grep -F трактует шаблон как фиксированную строку и обходит движок регулярных выражений. ripgrep (rg) ищет параллельно и обычно быстрее всех на деревьях кода. Сжатые ротированные логи читайте на месте через zgrep, zcat или zless, не распаковывая их. sort может использовать больше памяти через -S и параллельные потоки через --parallel, а временные файлы пишет в /tmp, если не указать -T, что важно, когда /tmp маленький. Наконец, ставьте самый избирательный фильтр в начало конвейера, чтобы последующие этапы обрабатывали меньше строк.
LC_ALL=C grep -F ' 502 ' access.log | wc -l
zgrep -h 'POST /login' /var/log/nginx/access.log.*.gz | wc -l
LC_ALL=C sort -S 1G --parallel=4 -T /var/tmp big.txt | uniq -c
rg -n --hidden -g '!node_modules' 'API_KEY' /srv/app
# Requests per minute around an incident
awk '{print substr($4, 2, 17)}' access.log | uniq -c | sort -rn | head
Реальные ситуации
Всплеск трафика: кто нагружает сайт?
Проблема
Сайт тормозил двадцать минут, а панель хостинга показывает лишь всплеск CPU. Нужно понять, были ли это реальные пользователи, краулер или атака, имея только access-лог nginx.
Как решить
Двигайтесь от общего к частному. Посчитайте запросы по минутам с помощью awk по полю с временной меткой, чтобы найти точное окно. Затем внутри этого окна отранжируйте клиентские IP через awk '{print $1}' | sort | uniq -c | sort -rn | head, и так же — пути по полю 7. Проверьте user agent (последнее поле в кавычках) у самых активных IP; часто это один агент, например скрейпер или неправильно настроенный мониторинг. Используйте LC_ALL=C и zgrep по ротированным файлам, если окно пересекает ротацию. Если это злоупотребление, ограничьте частоту запросов или заблокируйте на nginx или в Cloudflare, а однострочники сохраните в runbook, чтобы следующее расследование заняло две минуты.
Скрипт очистки удалил не те файлы
Проблема
Ночной скрипт запускал из cron rm -rf "$BUILD_DIR"/*. Однажды ночью переменная не была задана, потому что cron не загружает profile, и скрипт удалил файлы из корня той файловой системы, где работал.
Как решить
Сначала восстановитесь из бэкапа, затем сделайте скрипт отказоустойчивым. Добавьте set -euo pipefail, чтобы незаданная переменная останавливала скрипт. Для опасных путей используйте форму ${VAR:?message}, чтобы сама подстановка прерывалась при пустом значении. Проверяйте цель: что это каталог, что он лежит под ожидаемым префиксом, например /srv/app/builds, и что это не /. Вместо glob предпочитайте find "$dir" -mindepth 1 -mtime +7 -delete и логируйте, что удаляется. Задавайте переменную внутри скрипта или crontab, а не полагайтесь на окружение, прогоните shellcheck и перед повторным включением задачи протестируйте с echo вместо rm.
Цикл ломается на именах файлов с пробелами
Проблема
Скрипт, конвертирующий загруженные изображения, работает в тестах, но в продакшене падает с «No such file or directory» на именах вроде «Summer menu.jpg».
Как решить
Скрипт использует for f in $(ls *.jpg), поэтому word splitting режет каждое имя по пробелам. Замените это на glob for f in ./*.jpg, который выдаёт каждое имя одним словом, и берите в кавычки каждое использование: "$f". Для рекурсивной работы используйте find с -print0, передавая вывод в while IFS= read -r -d '' f, или find -exec ... {} +: они безопасно передают имена, даже если в них есть переводы строк. Добавьте -- перед аргументами-файлами, чтобы имена, начинающиеся с дефиса, не читались как опции. Добавьте shopt -s nullglob, чтобы пустой каталог давал ноль итераций, а не буквальный «*.jpg». Тестируйте на каталоге-фикстуре, полном «враждебных» имён.
Шпаргалка
- Что на самом деле запустится для команды
type -a node; command -v node- Отладить скрипт построчно
bash -x ./script.sh (или set -x внутри)- Код завершения каждого этапа конвейера
echo "${PIPESTATUS[@]}"- Прервать, если переменная пуста
rm -rf -- "${DIR:?DIR is not set}"/build- Безопасный цикл по найденным файлам
find . -type f -print0 | while IFS= read -r -d '' f; do ...; done- Выполнить команду сразу для многих файлов
find /srv -name '*.log' -exec gzip {} +- Поиск в сжатых ротированных логах
zgrep -h 'error' /var/log/nginx/error.log.*.gz- Быстрые побайтовые sort и grep
LC_ALL=C sort file | LC_ALL=C grep -F 'text'- Атомарное переключение symlink
ln -sfn new_target link.tmp && mv -T link.tmp link- Проверить shell-скрипт линтером
shellcheck deploy.sh
В моих реальных проектах
- Я веду четыре продакшен-сервера на Ubuntu через SSH с входом по ключам, и на всех них читаю логи и конфиги именно через shell.
- На одном сервере я снизил заполненность диска с 91% до 66%, найдя и удалив старые логи, старые сборки и раздутые node_modules с помощью find и du.
- Продакшен-баги — например, ошибку в middleware локализации и сбой конвертации изображений в WebP — я находил, читая логи на сервере, сначала только в режиме чтения, до любых исправлений.
Вопросы и ответы
Q1Чем Linux отличается от дистрибутива вроде Ubuntu?
Linux — это ядро: основа, которая управляет железом, памятью, процессами и файловой системой. Само по себе это ещё не пригодная к использованию система. Дистрибутив упаковывает ядро вместе с пакетным менеджером, системными утилитами, конфигурацией по умолчанию, документацией и жизненным циклом поддержки. Ubuntu и Debian используют apt и пакеты .deb; RHEL, Rocky и Alma — dnf и .rpm. Для серверов практические различия — это названия пакетов, некоторые пути к конфигам, средства безопасности по умолчанию (AppArmor в Ubuntu, SELinux в RHEL) и срок поддержки каждого релиза. Я выбираю LTS-релиз, чтобы обновления безопасности приходили годами без вынужденных апгрейдов.
Q2Где искать конфигурацию и логи на Linux-сервере?
Конфигурация лежит в /etc: /etc/nginx, /etc/ssh/sshd_config, /etc/php, /etc/systemd/system для собственных юнитов. Логи живут в двух местах. Классические текстовые логи — в /var/log, например /var/log/nginx/error.log или /var/log/auth.log для входов в систему. Сервисы под управлением systemd также пишут в журнал, который читается через journalctl -u servicename. Логи приложения часто лежат внутри самого приложения, например storage/logs в Laravel. Поэтому мой порядок такой: journalctl для сервиса, затем /var/log для веб-сервера, затем собственный каталог логов приложения — и всё это с tail -f, пока я воспроизвожу проблему.
Q3Объясните пайпы и перенаправление на примере.
У каждого процесса три стандартных потока: stdin (0), stdout (1) и stderr (2). Пайп соединяет stdout одной команды со stdin следующей, и маленькие инструменты складываются вместе. Например, awk '{print $1}' access.log | sort | uniq -c | sort -rn | head выводит десять IP с наибольшим числом запросов: awk вытаскивает колонку, sort группирует одинаковые строки, uniq -c считает их, sort -rn сортирует по количеству. Перенаправление отправляет поток в файл: > перезаписывает, >> дописывает, 2> перехватывает только ошибки, а 2>&1 отправляет ошибки туда же, куда идёт stdout. Последнее важно для cron-задач, где нужно, чтобы и то, и другое попадало в один лог.
Q4Как найти, где в проекте используется определённое имя файла или настройка?
grep -rn "SETTING_NAME" /path/to/project ищет рекурсивно и выводит имена файлов с номерами строк. Я добавляю -i для поиска без учёта регистра, --include='*.php', чтобы ограничить типы файлов, и --exclude-dir=node_modules, чтобы отсечь шум. На больших кодовых базах ripgrep (rg) делает то же самое быстрее и по умолчанию учитывает .gitignore. Чтобы искать файлы по имени, а не по содержимому, я использую find /path -name 'pattern', который также умеет фильтровать по размеру, типу и возрасту, например find /var/www -name '*.log' -size +100M, чтобы найти большие логи. Связка find с -exec или xargs позволяет что-то сделать с результатами, но я всегда сначала вывожу список.
Q5Зачем начинать bash-скрипт с set -euo pipefail?
По умолчанию bash продолжает работу после упавшей команды, считает неустановленные переменные пустыми строками и смотрит только на код выхода последней команды пайплайна. Каждое из этих поведений прячет ошибки. set -e останавливает скрипт, когда команда падает. set -u превращает опечатку в имени переменной в ошибку вместо пустого значения, что предотвращает катастрофы вроде превращения rm -rf "$DIR/" в rm -rf /. set -o pipefail заставляет пайплайн падать, если упал любой его этап. Это не идеально, так как некоторые конструкции обходят -e, поэтому я всё равно беру переменные в кавычки, явно проверяю важные результаты и прогоняю shellcheck.
Вопросы уровня senior
S1Почему удаление большого лог-файла иногда не освобождает место на диске и что с этим делать?
Удаление через rm убирает только запись в каталоге. Inode и его блоки освобождаются, когда счётчик ссылок доходит до нуля и ни один процесс не держит файл открытым. Веб-сервер или приложение, открывшее лог, продолжает писать в уже безымянный inode, поэтому df всё ещё показывает место занятым, а du не может его найти — типичное расхождение df и du. lsof +L1 выводит такие файлы вместе с PID. Решения: заставить процесс переоткрыть логи (nginx -s reopen, reload или рестарт) или обрезать открытый файл через /proc/PID/fd/N. Профилактика лучше: живые логи обрезайте, а не удаляйте, и используйте logrotate с postrotate-переоткрытием или copytruncate, понимая, что copytruncate может потерять несколько строк, записанных во время копирования.
S2Когда bash-скрипт перестаёт быть правильным инструментом?
Bash отлично оркестрирует другие программы: запусти это, проверь код возврата, перемести эти файлы. Он становится рискованным, когда задаче нужны структуры данных, разбор JSON или других структурированных форматов, нетривиальная обработка ошибок, повторы с backoff, конкурентность или юнит-тесты. Его правила кавычек и разбиения легко порождают тонкие баги, а у set -e есть известные пробелы. Моё практическое правило: как только скрипт вырастает примерно за страницу, работает со структурированными данными или его будут сопровождать несколько человек, я переношу его на Python или Node, либо в инструмент конфигурации вроде Ansible, если он описывает состояние сервера. Цена — дополнительная зависимость от рантайма, тогда как bash есть везде, включая минимальные контейнеры, где может быть только POSIX sh.
S3Как сделать переключение деплоя между релизами атомарным?
Собирайте каждый релиз в отдельный каталог, например releases/2026-11-05-1200, устанавливайте там зависимости и запускайте проверки, пока старый релиз продолжает обслуживать трафик. Затем переключите один symlink, current, на новый каталог. Делать это напрямую через ln -sfn не атомарно: команда удаляет и пересоздаёт ссылку, оставляя крошечный промежуток. Создать временную ссылку и переименовать её поверх старой через mv -T — атомарно, потому что rename — одна операция файловой системы. Веб-сервер или менеджер процессов указывает на current, так что reload подхватывает новый код. Откат — такое же переключение обратно на предыдущий каталог. Следите за кэшами, хранящими разрешённые реальные пути, такими как OPcache и realpath cache в PHP: после переключения может понадобиться reload PHP-FPM.
Глоссарий
- Inode
- Запись, хранящая расположение данных файла и его метаданные. Имена файлов — лишь записи каталога, указывающие на номер inode.
- Жёсткая ссылка
- Дополнительное имя того же inode. Данные сохраняются, пока не удалены все имена и ни один процесс не держит файл открытым.
- Символическая ссылка
- Небольшой файл, содержащий путь к другому файлу или каталогу. Может пересекать файловые системы и может оказаться битой.
- fork / exec
- Как Unix запускает программы: процесс копирует себя (fork), затем копия заменяет себя новой программой (exec).
- Builtin
- Команда, реализованная внутри самого shell, например cd или export, потому что она должна менять состояние самого shell.
- Word splitting
- Разбиение bash подстановки без кавычек на отдельные аргументы по пробелам, табуляциям и переводам строк. Кавычки это предотвращают.
- Globbing
- Раскрытие шаблонов вроде *.log в подходящие имена файлов; выполняется shell до запуска команды.
- Файловый дескриптор
- Небольшое число, через которое процесс обращается к открытому файлу, pipe или сокету. 0, 1 и 2 — стандартные потоки.
- Locale
- Настройки языка и символов (LANG, LC_ALL), которые меняют то, как инструменты сортируют, сравнивают и отображают текст.
Что изучить дальше
Есть идея продукта?Давайте создадим его.
Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.