Перейти к содержанию

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" для подстановки.

Дополнительные материалы