Когда серверов становится больше десятка, а ночные дежурства превращаются в бесконечное перекладывание логов и перезапуск сервисов, приходит понимание: рутина — главный враг надёжности. Одна опечатка в команде, забытый флаг или пропущенный алерт — и вот ты уже тушишь пожар в три часа ночи. 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. Это даёт единую панель мониторинга без лишних агентов.
Как структурировать скрипт админа
Хороший скрипт для админа обычно состоит из нескольких блоков:
- Ше-банг и настройки
#!/bin/bash+set -euo pipefail. - Константы и переменные
Пути, пороги, временные файлы, логи. - Функции
Разносим логику по функциям:check_disk(),backup_db(),send_alert(). - Основная логика
Вызовы функций, проверка условий, обработка ошибок. - Логирование
Пишем в лог-файл, чтобы потом было что смотреть.
Пример структуры:
#!/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) — это спасёт от глупых ошибок при деплое.
