Bash-скрипты для автоматизации администрирования
Основы написания bash-скриптов для сервера: переменные и кавычки, условия и циклы, коды возврата и обработка ошибок через set -e и set -u, функции, реальный пример скрипта и запуск по расписанию через cron.
Шебанг, права на выполнение и структура скрипта
Bash-скрипт — это обычный текстовый файл со списком команд, которые оболочка выполняет последовательно сверху вниз, как если бы их вводили в терминале вручную. Первая строка файла — шебанг, начинающийся с #! — указывает системе, каким интерпретатором нужно выполнять содержимое при прямом запуске файла.
#!/bin/bash
#
# backup-check.sh — проверка свежести бэкапов
set -euo pipefail
echo "Скрипт запущен"
Без шебанга система при запуске файла через ./script.sh попытается выполнить его через оболочку по умолчанию (часто sh, а не bash), и специфичные для bash конструкции — массивы, [[ ]], локальные переменные функций — могут вызвать синтаксическую ошибку в более минималистичной оболочке. Явный путь /bin/bash в шебанге устраняет эту неопределённость.
Чтобы запускать скрипт как самостоятельную программу (./backup-check.sh), а не только через явный вызов bash backup-check.sh, файлу нужно выставить бит выполнения.
chmod +x backup-check.sh
./backup-check.sh
Переменные, кавычки и подстановка команд
Переменные в bash задаются без пробелов вокруг знака равенства и без указания типа: имя_переменной=значение. Обращение к значению — через знак доллара, желательно с фигурными скобками, если переменная соседствует с другим текстом без разделителя.
backup_dir="/var/backups/mysql"
today=$(date +%F)
archive="${backup_dir}/dump-${today}.sql.gz"
echo "Файл архива: ${archive}"
Конструкция $(команда) выполняет команду в подоболочке и подставляет её вывод как строку — это современный и рекомендуемый синтаксис вместо устаревших обратных кавычек `команда`, который хуже читается и не поддерживает вложенность без экранирования.
Разница между двойными и одинарными кавычками принципиальна и регулярно становится источником трудноуловимых ошибок. Внутри двойных кавычек bash подставляет значения переменных и результаты команд, но защищает содержимое от разбиения на отдельные слова пробелами и от раскрытия масок файлов вроде звёздочки. Внутри одинарных кавычек всё воспринимается буквально, без единой подстановки.
file="report 2026.txt"
rm $file
rm "$file"
В первом варианте выше bash разобьёт значение переменной по пробелу и передаст rm два отдельных аргумента — "report" и "2026.txt", ни один из которых, скорее всего, не существует как отдельный файл, что приведёт к ошибке "no such file or directory". Второй вариант с двойными кавычками передаст ровно один аргумент с пробелом внутри — правило, о котором стоит помнить для абсолютно каждой переменной, содержащей путь к файлу.
Условия: if, test и двойные квадратные скобки
Условная конструкция if в bash проверяет код возврата команды, стоящей сразу после if, а не булево значение в привычном для языков программирования смысле — код 0 означает успех и трактуется как истина, любой ненулевой код — как ложь.
if [ -f "/etc/nginx/nginx.conf" ]; then
echo "Конфиг nginx найден"
elif [ -d "/etc/nginx" ]; then
echo "Каталог nginx есть, но конфига нет"
else
echo "nginx, похоже, не установлен"
fi
Внутри квадратных скобок [ ] на самом деле вызывается отдельная команда test, а не встроенный синтаксис — отсюда обязательные пробелы вокруг скобок и внутри них. Современный bash поддерживает также двойные квадратные скобки [[ ]], которые ведут себя надёжнее: не требуют кавычек вокруг переменных для защиты от разбиения на слова и поддерживают более удобный синтаксис сравнения строк с масками через оператор ==.
status=$(systemctl is-active nginx)
if [[ "$status" == "active" ]]; then
echo "nginx работает"
elif [[ "$status" =~ ^(failed|inactive)$ ]]; then
echo "nginx не работает: $status"
fi
Частые проверки для файлов: -f (обычный файл существует), -d (каталог существует), -x (есть право на выполнение), -s (файл существует и не пустой) — последняя особенно полезна при проверке, что бэкап действительно что-то записал, а не создал пустой архив нулевого размера.
Циклы: for и while для обработки списков и файлов
Цикл for в bash чаще всего перебирает список слов, результат подстановки команды или файлы, подходящие под маску, а while выполняет тело цикла, пока условие остаётся истинным — типичный случай для чтения файла построчно.
for db in $(mysql -N -e "SHOW DATABASES;" | grep -Ev "^(information_schema|performance_schema|mysql|sys)$"); do
mysqldump "$db" | gzip > "/var/backups/mysql/${db}.sql.gz"
echo "Готово: ${db}"
done
При переборе результата команды в for важно помнить, что разбиение идёт по пробельным символам, а не по строкам, поэтому список имён баз данных с пробелами в названиях сломает такой цикл — для более надёжной построчной обработки произвольного текста лучше подходит while с чтением через read.
while IFS= read -r line; do
echo "Обрабатываю: $line"
done < /etc/hosts
Установка IFS= перед read отключает автоматическое обрезание начальных и конечных пробелов у прочитанной строки, а флаг -r запрещает интерпретацию обратного слеша как символа экранирования — оба нюанса часто упускают, из-за чего построчная обработка файлов с обратными слешами в путях или лишними пробелами даёт неожиданный результат.
Коды возврата и обработка ошибок: set -e, set -u, pipefail
Каждая команда в Linux при завершении возвращает числовой код: 0 означает успех, любое другое значение (от 1 до 255) — тот или иной вид ошибки, специфичный для конкретной программы. По умолчанию bash-скрипт продолжает выполнение после команды, завершившейся с ошибкой, — то есть провал одной команды на середине скрипта молча пропускается, а следующие строки выполняются как ни в чём не бывало, что для серверных скриптов часто опаснее, чем явный сбой.
set -e
set -u
set -o pipefail
set -e останавливает выполнение скрипта немедленно, как только любая команда завершается с ненулевым кодом (с рядом задокументированных исключений — например, команда внутри условия if не прерывает скрипт, так как её код возврата явно проверяется). set -u превращает обращение к необъявленной переменной в фатальную ошибку вместо молчаливой подстановки пустой строки — это ловит опечатки в именах переменных, которые иначе могли бы привести, например, к удалению не того каталога, если переменная с путём оказалась пустой. set -o pipefail делает код возврата всего конвейера (команда1 | команда2) равным коду первой команды в цепочке, завершившейся с ошибкой, а не только последней, как ведёт себя bash по умолчанию.
Часто все три директивы объединяют в одну строку set -euo pipefail в самом начале скрипта сразу после шебанга — это стало неформальным стандартом для серверных bash-скриптов, где непредсказанная ошибка на середине выполнения может привести к куда более серьёзным последствиям, чем немедленная и явная остановка с сообщением об ошибке.
mkdir -p "$backup_dir" || { echo "Не удалось создать каталог: $backup_dir" >&2; exit 1; }
Даже при включённом set -e для критичных операций стоит писать явную обработку через || с понятным сообщением в stderr (перенаправление >&2) и осмысленным кодом завершения exit — это заметно упрощает диагностику, когда скрипт запускается не интерактивно, а по расписанию, и единственный след его работы — строка в логе.
Функции: переиспользование кода внутри скрипта
Функции в bash оформляются либо словом function перед именем, либо просто именем со скобками — оба варианта равнозначны, выбор обычно дело стиля конкретной команды или проекта. Локальные переменные внутри функции стоит объявлять через local, чтобы они не перезаписывали одноимённые переменные во внешней части скрипта.
log() {
local level="$1"
local message="$2"
echo "$(date "+%Y-%m-%d %H:%M:%S") [${level}] ${message}" >> /var/log/backup-check.log
}
log "INFO" "Проверка бэкапов начата"
log "ERROR" "Файл бэкапа за сегодня не найден"
Вынесение повторяющейся логики в функции вроде log() выше не только сокращает объём кода, но и даёт единую точку изменения формата — если позже понадобится, например, добавить отправку критичных ошибок в Telegram или на почту, менять придётся только тело функции, а не десятки мест по всему скрипту, где что-то логировалось напрямую через echo.
Код возврата функции задаётся через return с числом (по умолчанию — код возврата последней выполненной внутри неё команды), а не через return со значением произвольного типа, как в языках программирования общего назначения, — если функции нужно вернуть текстовые данные, а не только код успеха или неудачи, это делают через echo с последующим захватом результата в вызывающем коде через $(имя_функции аргументы).
Реальный пример: скрипт проверки свежести бэкапов
Рассмотрим законченный практический пример: скрипт, который проверяет, что резервная копия базы данных была создана сегодня и её размер не подозрительно мал (что часто указывает на оборванный или пустой дамп), и уведомляет об ошибке в лог, если что-то не так.
#!/bin/bash
set -euo pipefail
BACKUP_DIR="/var/backups/mysql"
MIN_SIZE_KB=100
LOG_FILE="/var/log/backup-check.log"
log() {
echo "$(date "+%Y-%m-%d %H:%M:%S") $1" >> "$LOG_FILE"
}
latest_file=$(find "$BACKUP_DIR" -name "*.sql.gz" -mtime -1 -printf "%T@ %p\n" | sort -rn | head -1 | cut -d" " -f2-)
if [ -z "$latest_file" ]; then
log "ERROR: за последние сутки не найден ни один файл бэкапа в ${BACKUP_DIR}"
exit 1
fi
size_kb=$(du -k "$latest_file" | cut -f1)
if [ "$size_kb" -lt "$MIN_SIZE_KB" ]; then
log "ERROR: файл ${latest_file} подозрительно мал (${size_kb} KB)"
exit 1
fi
log "OK: свежий бэкап ${latest_file}, размер ${size_kb} KB"
exit 0
Скрипт использует find с флагом -mtime -1 для поиска файлов, изменённых за последние сутки, сортирует найденные файлы по времени модификации и берёт самый свежий, затем проверяет его размер через du. Такая проверка обнаруживает сразу два независимых типа сбоя резервного копирования: полное отсутствие свежей копии (например, если сам скрипт бэкапа упал ещё до создания файла) и создание пустого или почти пустого файла (например, если mysqldump завершился с ошибкой доступа, но сама команда архивации технически отработала).
Код возврата 1 при обеих ошибках и 0 при успехе делает этот скрипт удобным для встраивания в систему мониторинга — большинство систем проверки состояния (Zabbix, Nagios, простые проверки через cron с уведомлением) ориентируются именно на код возврата, а не на разбор текста в логе.
Запуск скриптов по расписанию: окружение cron и блокировки
Скрипт, отлично работающий при ручном запуске из интерактивного терминала, нередко ведёт себя иначе при запуске из cron — у cron-заданий заметно более скромное окружение: короткая переменная PATH без пользовательских дополнений, отсутствие большинства переменных окружения, которые обычно задаются в .bashrc или .profile при интерактивном входе.
0 3 * * * /usr/bin/flock -n /tmp/backup-check.lock /opt/scripts/backup-check.sh >> /var/log/backup-check-cron.log 2>&1
Явные абсолютные пути к используемым в скрипте утилитам (или явное задание PATH в начале скрипта) снимают большинство проблем "работает в терминале, но не работает в cron". Перенаправление >> файл 2>&1 в самой записи crontab сохраняет и стандартный вывод, и вывод ошибок в один файл — без этого cron по умолчанию попытается отправить вывод письмом локальному пользователю, что на сервере без настроенной почтовой системы просто теряется без следа.
Утилита flock из примера выше — простой и надёжный способ защититься от повторного запуска того же скрипта, если предыдущий запуск ещё не завершился (например, резервное копирование большой базы данных, занимающее дольше интервала между запусками по расписанию). Флаг -n завершает работу немедленно с ошибкой, если файл блокировки уже занят другим запущенным экземпляром, вместо того чтобы ждать освобождения блокировки — это предотвращает одновременный запуск двух копий одного и того же тяжёлого скрипта резервного копирования.
Типичные ошибки при написании серверных bash-скриптов
Отсутствие кавычек вокруг переменных с путями к файлам — самая частая причина скриптов, которые "работают почти всегда", но иногда ломаются на файлах с пробелами или специальными символами в имени, и найти такую ошибку постфактум по логам заметно сложнее, чем избежать её изначально.
Отсутствие set -euo pipefail в начале скрипта — без него ошибка в середине скрипта резервного копирования или деплоя может остаться незамеченной, и следующие команды выполнятся над несуществующими или неполными данными, создавая иллюзию успешного завершения при реально провалившейся операции.
Жёстко прописанные относительные пути вместо абсолютных — скрипт, который явно или неявно полагается на текущий рабочий каталог (например, вызывает ./другой_скрипт.sh), сломается при запуске из cron или из другого каталога, где текущая директория окажется иной, чем при ручном тестировании.
Игнорирование кодов возврата вызываемых команд и полагание только на текст вывода — вывод программы в другой локали, версии или с другими опциями может отличаться, тогда как код возврата — куда более стабильный и предназначенный именно для этой цели канал сигнализации об успехе или неудаче.
Итоги: от разовой команды к надёжной автоматизации
Bash-скрипт становится инструментом администрирования, на который можно положиться, когда в нём последовательно применены несколько простых, но не всегда очевидных сразу практик: явный шебанг, кавычки вокруг всех переменных с путями, set -euo pipefail в начале, понятные сообщения об ошибках в лог, а не только молчаливое падение, и защита от параллельного запуска через flock там, где это уместно.
Разница между скриптом, написанным "чтобы один раз сработало", и скриптом, готовым к запуску по расписанию месяцами без присмотра, обычно не в сложности логики, а именно в обработке нештатных ситуаций: что произойдёт, если диск заполнен, если сеть недоступна, если предыдущий запуск ещё не завершился. Именно эти края определяют, будет ли администратор узнавать о проблеме из внятного сообщения в логе или из последствий тихого сбоя, обнаруженного через неделю.
Почему стоит выбрать нас
Быстрый запуск
Подберём конфигурацию и развернём проект в день обращения.
Надёжность
NVMe-диски, защита от DDoS и резервные копии по расписанию.
Поддержка 24/7
Дежурные инженеры на связи в любое время суток.
Настройка под ключ
Установим CMS, настроим веб-сервер, SSL и почту за вас.
Перенос без простоя
Аккуратно перенесём сайт и базу данных на новый сервер.
Прозрачные цены
Актуальные тарифы и честный подбор без навязывания.
Вопросы и ответы
Зачем нужна строка #!/bin/bash в начале скрипта?
Эта строка называется шебангом и указывает системе, каким интерпретатором нужно выполнять файл при запуске напрямую (./script.sh) — без неё система попытается выполнить содержимое как команды текущей оболочки, что для скрипта, использующего специфичные для bash конструкции, может привести к синтаксическим ошибкам в других оболочках вроде dash или sh.
Чем set -e лучше проверки кода возврата после каждой команды?
set -e останавливает выполнение скрипта сразу при первой команде, завершившейся с ненулевым кодом возврата, избавляя от необходимости писать явную проверку if [ $? -ne 0 ]; then после каждой строки — это заметно сокращает код и снижает риск случайно пропустить проверку одной из команд, хотя и требует понимания исключений (команды в условиях if, в цепочках через ||, не считаются провалом всего скрипта).
В чём разница между переменной в двойных и одинарных кавычках?
В двойных кавычках bash подставляет значения переменных и результат выполнения команд в обратных кавычках или $(), а в одинарных — воспринимает всё содержимое буквально, без какой-либо подстановки. Двойные кавычки почти всегда нужны вокруг переменных, чтобы защититься от разбиения значения на отдельные слова пробелами и от раскрытия символов подстановки пути вроде звёздочки.
Что означает код возврата 127 у команды в скрипте?
Код 127 означает, что команда не найдена — либо опечатка в имени команды, либо утилита не установлена в системе, либо она не находится в переменной PATH текущей оболочки. Это стоит проверять в первую очередь, если скрипт неожиданно завершается сразу после конкретной строки без внятного сообщения об ошибке.
Нужно ли использовать bash или лучше писать под sh?
Если скрипт использует специфичные для bash возможности — массивы, [[ ]] вместо [ ], подстановку процессов через <(), локальные переменные внутри функций — он должен запускаться именно через bash и иметь соответствующий шебанг, поскольку в более минималистичной sh (например, dash в Debian и Ubuntu) часть этого синтаксиса вызовет ошибку.
Как передать аргументы в bash-скрипт и проверить их количество?
Аргументы командной строки доступны внутри скрипта как $1, $2 и так далее, полный список — как $@, а их количество — как $#. Проверка количества аргументов перед началом основной логики (например, через if [ "$#" -lt 2 ]; then) — стандартная защита от запуска скрипта без обязательных параметров.
Как отлаживать bash-скрипт, если непонятно, что идёт не так?
Флаг -x при запуске (bash -x script.sh или set -x внутри скрипта) включает трассировку — каждая выполняемая строка выводится в терминал с подставленными значениями переменных перед выполнением, что обычно быстро показывает, на каком именно шаге и с какими значениями переменных скрипт пошёл не по плану.
Безопасно ли хранить пароли и API-ключи прямо в теле bash-скрипта?
Нет — скрипт может случайно попасть в систему контроля версий, лог, резервную копию или к другому пользователю сервера. Правильная практика — выносить секреты в отдельный файл с правами доступа 600, читаемый только владельцем, и подключать его через source, либо использовать переменные окружения, задаваемые вне самого скрипта.
Как оплачивать услуги?
Оплата помесячная, для облачных серверов доступна почасовая тарификация. Принимаются карты, банковские переводы и электронные кошельки.
Есть ли круглосуточная поддержка?
Да, дежурные инженеры на связи 24/7.
Выдаёте ли закрывающие документы?
Да, предоставим необходимые документы для оплаты.
Как оставить заявку?
Заполните форму на странице или нажмите «Заказать» — мы свяжемся и поможем с подбором.