Когда серверов становится больше десятка, а ночные дежурства превращаются в бесконечное перекладывание логов и перезапуск сервисов, приходит понимание: рутина — главный враг надёжности. Одна опечатка в команде, забытый флаг или пропущенный алерт — и вот ты уже тушишь пожар в три часа ночи. Bash-скрипты — это не просто «хак», а фундамент автоматизации, который превращает монотонную ручную работу в воспроизводимые, контролируемые пайплайны. Это первый шаг к Infrastructure as Code, который легко встроить в Ansible, запустить из CI/CD или использовать как health-check для legacy-систем, где Kubernetes пока не светит.

В этой статье разберём практичные bash-скрипты, которые реально экономят время, снижают риск ошибок и делают инфраструктуру стабильнее. От однострочников до скриптов, готовых к cron и systemd, — всё, что пригодится инженеру, который автоматизирует всё подряд.

Зачем админу вообще писать bash-скрипты

Если вы до сих пор делаете всё руками через SSH, рано или поздно столкнётесь с тем, что:

  • одна опечатка в команде может убить сервис или сервер;
  • одни и те же действия приходится повторять на десятках серверов;
  • в ночное дежурство вы тратите часы на проверку логов и статусов сервисов.

Bash-скрипты решают эти проблемы:

  • Повторяемость: один раз написали — запускаете на любом сервере. В гибридных средах, где локальное железо соседствует с облаком, единый скрипт можно натравить и на bare-metal, и на виртуалку.
  • Контроль: в скрипте вы видите все шаги, их можно проверить, отладить, версионировать. Положили в Git — и получили историю изменений, как для любого кода.
  • Автоматизация: через cron, systemd или внешний оркестратор (Ansible, Jenkins) скрипты запускаются без вашего участия. В эпоху SRE и FinOps каждая минута, потраченная на рутину, — это минута, украденная у проектирования отказоустойчивых архитектур.

Для админа bash — это не «программирование», а инструмент системного инженера, как systemctl, journalctl или ss. Без него никуда, даже если вы уже вовсю используете Terraform и Kubernetes.

Базовые принципы: что делает скрипт «нормальным»

Прежде чем писать сложные скрипты, важно закрепить базовые принципы, которые отличают поделку от production-инструмента:

  • Имена переменных понятные: BACKUP_DIR вместо BD. Через полгода вы сами скажете себе спасибо.
  • Комментарии: объясняйте, почему вы делаете то или иное действие, а не только что делаете. Особенно если логика нетривиальная.
  • Проверка на ошибки: используйте set -euo pipefail и проверяйте коды возврата. Без этого скрипт может молча продолжить работу после сбоя, и вы узнаете об этом только по косвенным признакам.
  • Минимум магии: не усложняйте конструкции, если можно сделать проще и понятнее. Идемпотентность и читаемость важнее красоты.

Это не «красота», а практическая надёжность. Когда скриптов становится больше десятка, отсутствие set -euo pipefail превращает отладку в ад. В production-окружении я всегда добавляю trap для очистки временных файлов — это спасает от мусора в /tmp.

Простой пример: скрипт проверки свободного места

Начнём с базовой задачи, которая есть у каждого: мониторинг дискового пространства.

#!/bin/bash
set -euo pipefail

THRESHOLD=90
df -h | awk 'NR>1 {print $5 " " $6}' | while read output; do
  usage=$(echo $output | awk '{print $1}' | sed 's/%//')
  partition=$(echo $output | awk '{print $2}')
  if [ $usage -ge $THRESHOLD ]; then
    echo "Внимание: раздел $partition заполнен на $usage%"
    # Здесь можно добавить отправку уведомления
    exit 1
  fi
done

Что здесь важно:

  • set -euo pipefail — скрипт остановится при любой ошибке, а не будет тихо продолжать.
  • THRESHOLD — выносим порог в переменную, чтобы не искать магическое число в коде.
  • exit 1 — это сигнал, что что-то пошло не так. Можно использовать в мониторинге (Zabbix, Prometheus, custom check).

Такой скрипт можно положить в /usr/local/bin/check_disk.sh, сделать исполняемым (chmod +x) и запускать из cron:

0 * * * * /usr/local/bin/check_disk.sh

На практике я часто использую подобные скрипты как custom check в Prometheus через textfile collector: кладу результат в файл, который забирает node_exporter. Это даёт единую панель мониторинга без лишних агентов.

Как структурировать скрипт админа

Хороший скрипт для админа обычно состоит из нескольких блоков:

  1. Ше-банг и настройки
    #!/bin/bash + set -euo pipefail.
  2. Константы и переменные
    Пути, пороги, временные файлы, логи.
  3. Функции
    Разносим логику по функциям: check_disk(), backup_db(), send_alert().
  4. Основная логика
    Вызовы функций, проверка условий, обработка ошибок.
  5. Логирование
    Пишем в лог-файл, чтобы потом было что смотреть.

Пример структуры:

#!/bin/bash
set -euo pipefail

# Константы
LOG_FILE="/var/log/my_script.log"
THRESHOLD=90

# Функции
check_disk() {
  # логика проверки
}

backup_db() {
  # логика резервного копирования
}

# Основная логика
main() {
  check_disk
  backup_db
}

main "$@"

Такой подход позволяет:

  • легко добавлять новые функции;
  • переиспользовать код;
  • быстро находить ошибки по логам.

Модульная структура с функциями упрощает тестирование: можно отдельно проверить функцию check_disk, не запуская весь скрипт. Это особенно полезно, когда вы встраиваете скрипты в Ansible роли или CI/CD пайплайны.

Автоматизация резервного копирования

Резервное копирование — одна из самых рутинных задач. Вместо ручного копирования файлов или запуска tar вручную напишем скрипт, который:

  • создаёт архив каталога;
  • добавляет дату в имя файла;
  • удаляет старые архивы (например, старше 7 дней).
#!/bin/bash
set -euo pipefail

BACKUP_DIR="/backup"
SOURCE_DIR="/var/www"
RETENTION_DAYS=7
LOG_FILE="/var/log/backup.log"

log() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}

backup() {
  local date_stamp=$(date +%Y%m%d)
  local archive="${BACKUP_DIR}/backup_${date_stamp}.tar.gz"
  
  tar -czf "$archive" "$SOURCE_DIR"
  log "Бэкап создан: $archive"
  
  # Удаление старых архивов
  find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete
  log "Старые архивы удалены (старше $RETENTION_DAYS дней)"
}

backup

Что здесь важно:

  • tar -czf — сжатый архив без лишних флагов.
  • find ... -mtime +$RETENTION_DAYS -delete — автоматическая очистка старых бэкапов.
  • Логирование — чтобы вы видели, когда бэкап прошёл и когда удалились старые файлы.

Такой скрипт можно запускать из cron каждый день:

0 2 * * * /usr/local/bin/backup.sh

В гибридных средах, где локальное железо соседствует с облаком, этот скрипт можно дополнить загрузкой бэкапа в S3-совместимое хранилище через rclone. Это даст дополнительную географическую отказоустойчивость без лишних телодвижений.

Скрипты для мониторинга сервисов

Часто админу нужно проверять, что сервис жив. Например, веб-сервер, база данных или очередь сообщений.

Простой пример проверки HTTP-сервиса:

#!/bin/bash
set -euo pipefail

URL="http://localhost:8080/health"
TIMEOUT=5

if curl -f -s -m "$TIMEOUT" "$URL" > /dev/null; then
  echo "OK: сервис доступен"
  exit 0
else
  echo "CRITICAL: сервис недоступен"
  exit 1
fi

Нюансы:

  • -f — curl вернёт ошибку при HTTP-коде 4xx/5xx.
  • -s — тихий режим, без лишнего вывода.
  • -m — таймаут, чтобы скрипт не зависал.

Такой скрипт можно использовать:

  • как custom check в Zabbix;
  • как health-check в systemd unit;
  • как часть более сложного мониторинга.

Для Kubernetes есть liveness probes, но для legacy-систем без оркестрации bash-скрипт с curl — простой и надёжный способ. В systemd можно использовать ExecStartPre для проверки зависимостей перед запуском сервиса.

Проверка логов и уведомления

Часто админу нужно проверять логи на наличие ошибок и отправлять уведомления. Например, проверка логов nginx на 5xx ошибки.

#!/bin/bash
set -euo pipefail

LOG_FILE="/var/log/nginx/access.log"
ERROR_THRESHOLD=10
TIME_WINDOW=$(date -d '5 minutes ago' '+%d/%b/%Y:%H:%M:%S')

count=$(awk -v start="$TIME_WINDOW" '$4 > "["start { if ($9 ~ /^5/) print $9 }' "$LOG_FILE" | wc -l)
count=$((count+0))

if [ "$count" -ge "$ERROR_THRESHOLD" ]; then
  echo "Внимание: $count ошибок 5xx за последние 5 минут"
  # Здесь можно отправить уведомление в Slack/Telegram
fi

Что здесь важно:

  • awk парсит логи, фильтрует по времени и коду ответа.
  • count+0 — чтобы избежать пустого значения.
  • Вместо echo можно использовать mail, curl для отправки в Slack/Telegram.

В современных стеках мы используем Loki и Grafana, но быстрый скрипт для алертов в Telegram или Slack, запущенный по cron, может спасти ночью, когда основная система мониторинга ещё не развёрнута или недоступна.

Автоматизация управления пользователями

Создание и удаление пользователей — ещё одна рутина, которая легко автоматизируется.

Скрипт создания пользователя:

#!/bin/bash
set -euo pipefail

USERNAME=$1
PASSWORD=$2

if [ -z "$USERNAME" ] || [ -z "$PASSWORD" ]; then
  echo "Использование: $0  "
  exit 1
fi

if id "$USERNAME" &>/dev/null; then
  echo "Пользователь $USERNAME уже существует"
  exit 1
fi

useradd -m "$USERNAME"
echo "$USERNAME:$PASSWORD" | chpasswd
echo "Пользователь $USERNAME создан"

Нюансы:

  • id "$USERNAME" &>/dev/null — проверяем, существует ли пользователь.
  • useradd -m — создаём домашний каталог.
  • &>/dev/null — подавляем вывод, чтобы не засорять лог.

Такой скрипт можно использовать:

  • в Ansible (через script или command);
  • в CI/CD при развертывании окружения;
  • как часть onboarding-процесса.

В облачных средах пользователи управляются через IAM, но для локальных серверов скрипт создания пользователей с ssh-ключами — классика. В Ansible есть модуль user, но иногда проще запустить скрипт, особенно если нужно быстро накатить пользователей на десяток серверов без разворачивания полноценного playbook.

Скрипты для управления сервисами

Управление сервисами через systemctl — это уже автоматизация, но можно пойти дальше и добавить проверки.

Пример скрипта перезапуска сервиса при его падении:

#!/bin/bash
set -euo pipefail

SERVICE="nginx"

if ! systemctl is-active --quiet "$SERVICE"; then
  echo "Сервис $SERVICE неактивен, перезапускаем..."
  systemctl restart "$SERVICE"
  if systemctl is-active --quiet "$SERVICE"; then
    echo "Сервис $SERVICE успешно перезапущен"
  else
    echo "Ошибка: не удалось перезапустить $SERVICE"
    exit 1
  fi
fi

Что здесь важно:

  • systemctl is-active --quiet — проверяем статус без лишнего вывода.
  • После перезапуска снова проверяем статус.
  • Можно использовать в cron или systemd timer.

Systemd уже умеет перезапускать сервисы (Restart=on-failure), но скрипт с кастомной логикой может быть полезен, когда нужно проверить не только статус, но и, например, доступность порта или корректность ответа API.

Обработка ошибок и отладка

Ошибки в скриптах — это нормально. Важно, как вы их обрабатываете.

Базовые практики:

  • Всегда используйте set -euo pipefail.
  • Проверяйте коды возврата команд.
  • Используйте trap для отлова сигналов.

Пример:

#!/bin/bash
set -euo pipefail

cleanup() {
  echo "Очистка временных файлов..."
  rm -f /tmp/my_script.*
}
trap cleanup EXIT INT TERM

# Основная логика
echo "Скрипт работает..."
# ...

Что даёт trap:

  • EXIT — выполняется при любом завершении скрипта.
  • INT TERM — при прерывании (Ctrl+C, kill).

trap для очистки временных файлов — это стандарт в production-скриптах. Если скрипт прервётся, вы не оставите за собой мусор в /tmp. В больших пайплайнах это критично, чтобы не забить диск.

Логирование и мониторинг скриптов

Логирование — это не «прихоть», а необходимость. Без логов вы не поймёте, почему скрипт упал в 3 часа ночи.

Простой пример:

#!/bin/bash
set -euo pipefail

LOG_FILE="/var/log/my_script.log"

log() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}

log "Скрипт запущен"
# ... полезная работа
log "Скрипт завершён"

Дополнительные советы:

  • Используйте logger для отправки в системный журнал (/var/log/syslog).
  • Настройте logrotate для логов скриптов.
  • Мониторьте логи через Zabbix, Prometheus или ELK.

Логи можно отправлять в syslog и затем в ELK или Loki. Я предпочитаю использовать logger с тегом, чтобы потом легко фильтровать в journald. Например: logger -t my_script "Бэкап завершён".

Интеграция с cron и systemd

Скрипты становятся по-настоящему полезными, когда они запускаются автоматически.

Cron:

0 * * * * /usr/local/bin/check_disk.sh

Systemd timer (альтернатива cron):

# /etc/systemd/system/check_disk.service
[Unit]
Description=Check disk space

[Service]
Type=oneshot
ExecStart=/usr/local/bin/check_disk.sh

# /etc/systemd/system/check_disk.timer
[Unit]
Description=Run check disk every hour

[Timer]
OnCalendar=hourly
Persistent=true

[Install]
WantedBy=timers.target

Systemd timer даёт более гибкую настройку времени и интеграцию с системой. Он предпочтительнее cron: journald, зависимости от других сервисов и возможность запуска от имени конкретного пользователя с изолированным окружением. В production-окружении я почти всегда выбираю systemd timer, если нет специфических требований.

Типовые ошибки и как их избежать

1. Не проверять коды возврата

# Плохо
cp /source /dest
echo "Готово"  # выполнится, даже если cp упал

# Хорошо
if cp /source /dest; then
  echo "Готово"
else
  echo "Ошибка копирования" >&2
  exit 1
fi

2. Не использовать set -euo pipefail

Это приводит к тому, что скрипт продолжает работать, даже если команда завершилась с ошибкой. В production это может вызвать каскадные сбои.

3. Жёстко прописывать пути

# Плохо
BACKUP_DIR="/home/user/backup"

# Лучше
BACKUP_DIR="${BACKUP_DIR:-/home/user/backup}"

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

Заключение

Bash-скрипты — это не «программирование для гиков», а реальный инструмент для системного администратора, который хочет перестать тушить пожары и начать строить надёжные системы. Они позволяют автоматизировать рутину, уменьшить риск ошибок и сделать инфраструктуру более стабильной. Это фундамент, на котором вырастают Ansible, Terraform и Kubernetes. Без понимания bash вы будете как без рук, даже в самых современных облачных архитектурах.

Начните с простых задач: проверка дискового пространства, резервное копирование, мониторинг сервисов. Постепенно добавляйте новые функции и интеграции. Через некоторое время вы заметите, что проводите меньше времени за SSH и больше — за проектированием архитектуры. А когда скриптов станет много, оберните их в Ansible role или упакуйте в Docker — и вы уже на шаг ближе к полноценной IaC.

FAQ

Q: Нужно ли знать bash глубоко, чтобы писать полезные скрипты?
A: Нет. Достаточно понимать базовые конструкции: переменные, условия, циклы, функции. Остальное можно подсмотреть и адаптировать под свои задачи. Главное — практика и привычка проверять коды возврата.

Q: Можно ли использовать bash-скрипты в Ansible?
A: Да. Ansible поддерживает модули script и command, которые позволяют запускать bash-скрипты на удалённых серверах. Но лучше по возможности переписывать логику на нативные модули Ansible — они идемпотентны и проще в отладке.

Q: Как безопасно хранить пароли в скриптах?
A: Не храните пароли в коде. Используйте переменные окружения, файлы конфигурации с правами 600 или специализированные инструменты: HashiCorp Vault, sealed secrets в Kubernetes, SOPS. В production никогда не коммитьте секреты в репозиторий.

Q: Что делать, если скрипт падает в cron?
A: Всегда настраивайте логирование. Проверяйте логи и вывод cron (/var/log/syslog, journalctl). Используйте MAILTO для получения писем об ошибках. В systemd timer можно смотреть статус через systemctl status и journal.

Q: Нужно ли писать тесты для bash-скриптов?
A: Для критичных скриптов — да. Используйте простые тесты: проверка выходного кода, наличия файлов, содержимого логов. Для более серьёзного тестирования есть bats (Bash Automated Testing System) — это спасёт от глупых ошибок при деплое.