7. Bash и shell scripting
Пайпы и перенаправления, коды выхода, xargs, trap, подоболочки, переменные окружения и Bash strict mode — для автоматизации и troubleshooting.
Pipes и redirections
Pipes (конвейеры)
Конвейер — вывод одной команды передаётся на вход другой через символ |. Стандартный вывод (stdout) слева становится stdin справа; stderr по умолчанию не идёт в конвейер.
cat file | grep "error"
ps aux | grep nginx
journalctl -u nginx -n 100 | less
Перенаправления (redirections)
| Запись | Назначение |
|---|---|
> file |
stdout в файл (перезапись). |
>> file |
stdout в файл (дополнение). |
2> file |
stderr в файл. |
&> file или > file 2>&1 |
stdout и stderr в один файл. |
< file |
stdin из файла. |
<< EOF |
heredoc — stdin из следующего текста до EOF. |
Пример: command > /var/log/out.log 2>&1 — весь вывод в один файл. Для скриптов важно не терять ошибки: либо перенаправлять stderr, либо обрабатывать по коду выхода.
Exit codes
Код выхода (exit status) — число 0–255, которое процесс возвращает при завершении. 0 — успех, ненулевой — ошибка (значение зависит от программы). В shell переменная $? содержит код выхода последней команды.
true
echo $? # 0
false
echo $? # 1
grep "x" /nonexistent
echo $? # 2 (обычно)
Условия и циклы используют код выхода: if command; then ...; fi — ветка then выполняется, если код 0. Проверять $? после критичных команд и выходить из скрипта при ошибке (set -e или явный if ! command; then exit 1; fi).
xargs
xargs строит аргументы для команды из stdin (обычно из конвейера). Разбивает ввод по пробелам/переводам строк и подставляет аргументы в команду. Удобно, когда команда не читает stdin, а принимает аргументы.
# Удалить все файлы из списка
find /tmp -name "*.tmp" -print0 | xargs -0 rm -f
# Запустить команду для каждой строки (одна строка — один аргумент)
cat hosts.txt | xargs -I {} ping -c 1 {}
-0 (null-terminated) с find -print0 — безопасно для имён с пробелами. -I {} — подстановка каждой строки как одного аргумента. Без проверок xargs может сломаться на пустом вводе или спецсимволах; при необходимости проверять вывод find или использовать цикл.
trap
trap — перехват сигналов и событий в скрипте. Позволяет выполнить очистку (удалить временные файлы, остановить дочерние процессы) при выходе или при SIGTERM.
cleanup() {
rm -f /tmp/myscript.$$
exit 1
}
trap cleanup EXIT INT TERM
# основной код скрипта
При EXIT (нормальный выход), INT (Ctrl+C), TERM скрипт вызовет cleanup. Важно не забывать trap в долгих скриптах и при создании временных файлов.
Subshells (подоболочки)
Команды в скобках ( ... ) выполняются в подоболочке: отдельный процесс, своё окружение (смена каталога, переменные не влияют на родителя). Скобки { ... } — группа команд в текущем shell (без нового процесса).
( cd /tmp && pwd ) # pwd выведет /tmp, но текущий каталог в основном shell не изменится
cd /tmp && pwd # текущий каталог изменится
Подоболочка полезна, когда нужно временно сменить окружение или не влиять на текущий shell. В скриптах циклы и пайпы по умолчанию могут создавать подоболочки (например, часть конвейера); переменные, присвоенные в пайпе, не видны после пайпа в классическом bash.
Переменные окружения (env variables)
Переменные окружения — пары имя=значение, доступные процессам. В bash: export VAR=value — переменная видна дочерним процессам; VAR=value без export — только текущему shell. Просмотр: env, printenv. В скриптах не полагаться на «унаследованное» окружение без явной установки или документации; для чувствительных данных (пароли) предпочтительнее передача через файлы с правами или секреты оркестратора, а не через env в командной строке (видна в ps).
Bash strict mode
Strict mode уменьшает типичные ошибки: скрипт падает при использовании неинициализированной переменной и при ошибке любой команды в списке.
set -euo pipefail
# -e: выход при ненулевом коде команды
# -u: ошибка при обращении к не заданной переменной
# -o pipefail: код конвейера — по последней команде, при этом любой ненулевой код в конвейере даёт ненулевой итог
Иногда для отдельной команды, которая может вернуть ненулевой код (например, grep при отсутствии совпадений), временно отключают проверку: set +e; grep ...; set -e или grep ... || true. Использование strict mode в скриптах — хорошая практика для production и CI.
Практика
Скрипт проверки состояния сервиса
Пример: проверить, что сервис активен, и при необходимости перезапустить с логированием.
#!/usr/bin/env bash
set -euo pipefail
SERVICE="${1:-nginx}"
LOG="/var/log/service-check.log"
if ! systemctl is-active --quiet "$SERVICE"; then
echo "$(date -Iseconds) $SERVICE was inactive, restarting" >> "$LOG"
systemctl restart "$SERVICE"
exit 0
fi
echo "$(date -Iseconds) $SERVICE is active" >> "$LOG"
Использование: передать имя unit как первый аргумент или по умолчанию nginx. Запуск по cron для периодической проверки.
Корректная обработка ошибок
- Проверять код выхода после важных команд:
if ! command; then echo "Failed"; exit 1; fi. - Использовать set -e (или set -euo pipefail), чтобы скрипт не продолжал выполнение после ошибки.
- trap для очистки при прерывании и при выходе.
- Логировать ошибки в stderr или в файл:
echo "Error: ..." >&2. - Возвращать осмысленный код в конце:
exit 0при успехе,exit 1(или другой ненулевой) при сбое, чтобы вызывающий скрипт или CI мог реагировать.
!!! tip "Практика"
В любом скрипте для автоматизации: 1) shebang и при необходимости set -euo pipefail; 2) проверка аргументов и наличия нужных команд; 3) обработка ошибок и выход с ненулевым кодом при сбое; 4) при временных файлах — trap на очистку. Тогда скрипт предсказуемо ведёт себя в CI и при ручном запуске.
Паттерны и антипаттерны
| Паттерн | Описание |
|---|---|
| set -euo pipefail в начале | Меньше «тихих» сбоев и обращений к пустым переменным. |
| trap при временных файлах | Гарантированная очистка при прерывании. |
| Проверять $? или использовать if ! command | Явная реакция на ошибку вместо продолжения с неверными данными. |
| Антипаттерн | Почему плохо | Что делать |
|---|---|---|
| Игнорировать код выхода | Команда могла упасть, скрипт продолжает с неверным состоянием. | set -e или явные проверки. |
| Пароли в переменных в командной строке | Видны в ps. | Файлы с правами, секреты из vault/CI. |
| Без кавычек вокруг переменных | Пробелы и спецсимволы ломают вызов. | Всегда "$VAR" для подстановки. |
Дополнительные материалы
- Bash Manual
- Bash strict mode
- ShellCheck — статический анализ shell-скриптов
- set(1), trap(1)