Все уроки
Linux и серверыНачальный15 мин чтенияВ моём стеке

Основы Linux: shell, файлы и пайпы

Файловая система, команды, которые вы набираете каждый день, и как связывать их между собой.

  • Filesystem
  • Shell
  • Pipes
  • Editors
  • Help
Шаг 1/5
cat — в его роли настоящий кот — высыпает access.log в трубу. Каждый | передаёт вывод одной команды на вход следующей.

За 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 — швейцарский нож для поиска файлов по имени, размеру, возрасту или типу.

bash
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 сливает ошибки с обычным выводом.

bash
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, небольшой скрипт на нормальном языке обычно понятнее.

bash
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-файла, но никогда не из кода.

bash
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, если есть возможность его поставить.

bash
#!/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 всё равно нужен.

Лучшие практики

  1. Выполняйте ls на пути, прежде чем делать на нём rm, и избегайте rm -rf с непроверенными переменными.
  2. Делайте копию конфига (cp file file.bak) перед правкой и проверяйте его перед перезагрузкой сервиса.
  3. Для больших файлов используйте less и tail -f вместо cat: cat на логе в 2 GB зальёт весь терминал.
  4. Начинайте скрипты с set -euo pipefail и всегда берите переменные в кавычки: "$VAR".
  5. В скриптах и cron-задачах предпочитайте абсолютные пути: они не зависят от текущего каталога.
  6. Освойте man и --help: они быстрее и точнее, чем полузабытые статьи из блогов.
  7. Храните секреты в переменных окружения или защищённых 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 точно говорят, что будет запущено.

bash
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».

bash
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 не даёт двум копиям одной задачи работать одновременно.

bash
#!/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 маленький. Наконец, ставьте самый избирательный фильтр в начало конвейера, чтобы последующие этапы обрабатывали меньше строк.

bash
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

Реальные ситуации

01

Всплеск трафика: кто нагружает сайт?

Проблема

Сайт тормозил двадцать минут, а панель хостинга показывает лишь всплеск 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, чтобы следующее расследование заняло две минуты.

02

Скрипт очистки удалил не те файлы

Проблема

Ночной скрипт запускал из 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.

03

Цикл ломается на именах файлов с пробелами

Проблема

Скрипт, конвертирующий загруженные изображения, работает в тестах, но в продакшене падает с «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), которые меняют то, как инструменты сортируют, сравнивают и отображают текст.

Есть идея продукта?Давайте создадим его.

Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.