Есть такое правило в сисадминстве — если ты делаешь что-то руками второй раз, стоит задуматься. Если третий раз — точно пора писать скрипт.
Bash-скрипты пугают новичков. Синтаксис странный, кавычки важны, пробелы важны, ошибки невнятные. Но когда привыкаешь — это один из самых мощных инструментов в арсенале. Скрипт написал один раз, запускаешь сколько угодно, передаёшь коллеге, вешаешь на cron — и забыл.
В этой статье — реальные скрипты которые я использую. Не абстрактные примеры из учебника, а то что реально лежит на моих серверах и работает.
Основы которые нужно знать
Прежде чем скрипты — несколько вещей без которых никуда.
Шебанг — первая строка любого скрипта:
bash
#!/bin/bash
Говорит системе чем выполнять файл. Всегда первая строка, всегда.
Сделать скрипт исполняемым:
bash
chmod +x myscript.sh
./myscript.sh
Запускать с отладкой:
bash
bash -x myscript.sh
Флаг -x выводит каждую команду перед выполнением. Незаменимо когда скрипт делает не то что ожидаешь.
Важные настройки в начале скрипта:
bash
#!/bin/bash
set -e # остановить скрипт при любой ошибке
set -u # ошибка при использовании неопределённой переменной
set -o pipefail # ошибка если упала любая команда в пайпе
Эти три строки я добавляю в каждый серьёзный скрипт. Без них скрипт может молча продолжать работу после ошибки — и натворить дел.
Переменные, условия, циклы — быстрый курс
Переменные:
bash
NAME="Ivan"
echo "Hello, $NAME"
# Результат команды в переменную
CURRENT_DATE=$(date +%Y-%m-%d)
echo "Today: $CURRENT_DATE"
# Арифметика
COUNT=5
TOTAL=$((COUNT * 2))
echo $TOTAL # 10
Важно: никаких пробелов вокруг = при присваивании. NAME = "Ivan" — ошибка. NAME="Ivan" — правильно.
Условия:
bash
if [ -f "/etc/nginx/nginx.conf" ]; then
echo "Nginx config exists"
else
echo "Config not found"
fi
Частые проверки:
- -f file — файл существует
- -d dir — директория существует
- -z "$VAR" — переменная пустая
- -n "$VAR" — переменная не пустая
- $A -eq $B — числа равны
- "$A" = "$B" — строки равны
Циклы:
bash
# По списку
for server in web1 web2 web3; do
echo "Checking $server..."
ssh $server "uptime"
done
# По файлам
for file in /var/log/*.log; do
echo "Processing: $file"
done
# While
COUNT=0
while [ $COUNT -lt 5 ]; do
echo "Count: $COUNT"
COUNT=$((COUNT + 1))
done
Аргументы скрипта:
bash
#!/bin/bash
# Вызов: ./script.sh myserver 8080
SERVER=$1 # первый аргумент
PORT=$2 # второй аргумент
echo "Connecting to $SERVER:$PORT"
$1, $2 и т.д. — позиционные аргументы. $0 — имя самого скрипта. $# — количество аргументов. $@ — все аргументы.
Скрипт 1 — бэкап с ротацией
Самый используемый скрипт на любом сервере. Делает бэкап директории, архивирует, удаляет старые бэкапы.
bash
#!/bin/bash
set -e
# Настройки
SOURCE_DIR="/var/www/mysite"
BACKUP_DIR="/var/backups/mysite"
DAYS_TO_KEEP=30
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup_$DATE.tar.gz"
# Создать директорию если нет
mkdir -p "$BACKUP_DIR"
echo "Starting backup: $SOURCE_DIR"
echo "Destination: $BACKUP_FILE"
# Создать архив
tar -czf "$BACKUP_FILE" -C "$(dirname $SOURCE_DIR)" "$(basename $SOURCE_DIR)"
# Проверить что архив создан
if [ -f "$BACKUP_FILE" ]; then
SIZE=$(du -sh "$BACKUP_FILE" | cut -f1)
echo "Backup created: $BACKUP_FILE ($SIZE)"
else
echo "ERROR: Backup failed!"
exit 1
fi
# Удалить бэкапы старше N дней
echo "Removing backups older than $DAYS_TO_KEEP days..."
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$DAYS_TO_KEEP -delete
echo "Done! Current backups:"
ls -lh "$BACKUP_DIR"
Вешаем на cron — запускать каждую ночь в 3:00:
bash
crontab -e
# Добавляем:
0 3 * * * /usr/local/bin/backup-mysite.sh >> /var/log/backup-mysite.log 2>&1
Скрипт 2 — мониторинг диска с оповещением
Проверяет заполненность дисков. Если больше порога — шлёт письмо.
bash
#!/bin/bash
THRESHOLD=80
EMAIL="admin@example.com"
HOSTNAME=$(hostname)
# Проверяем все разделы
df -h | grep -vE '^Filesystem|tmpfs|cdrom' | while read line; do
USAGE=$(echo "$line" | awk '{print $5}' | sed 's/%//')
PARTITION=$(echo "$line" | awk '{print $6}')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
MESSAGE="ALERT: $HOSTNAME — disk $PARTITION is ${USAGE}% full"
echo "$MESSAGE"
echo "$MESSAGE" | mail -s "Disk Alert: $HOSTNAME" "$EMAIL"
fi
done
Запускаем каждый час через cron:
bash
0 * * * * /usr/local/bin/check-disk.sh
Скрипт 3 — деплой приложения
Реальный скрипт деплоя. Тянет код из git, устанавливает зависимости, перезапускает сервис.
bash
#!/bin/bash
set -e
APP_DIR="/var/www/myapp"
BRANCH="${1:-main}"
LOG_FILE="/var/log/deploy.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "=== Starting deploy (branch: $BRANCH) ==="
cd "$APP_DIR"
# Сохранить текущий коммит для отката
PREV_COMMIT=$(git rev-parse HEAD)
log "Previous commit: $PREV_COMMIT"
# Получить новый код
log "Pulling from git..."
git fetch origin
git checkout "$BRANCH"
git pull origin "$BRANCH"
NEW_COMMIT=$(git rev-parse HEAD)
log "New commit: $NEW_COMMIT"
# Установить зависимости
log "Installing dependencies..."
npm ci --only=production
# Сборка
log "Building..."
npm run build
# Перезапустить сервис
log "Restarting service..."
systemctl restart myapp
# Проверить что сервис запустился
sleep 3
if systemctl is-active --quiet myapp; then
log "Deploy successful!"
else
log "ERROR: Service failed to start. Rolling back..."
git checkout "$PREV_COMMIT"
npm ci --only=production
npm run build
systemctl restart myapp
log "Rolled back to $PREV_COMMIT"
exit 1
fi
log "=== Deploy complete ==="
Запускать можно вручную или через CI/CD:
bash
./deploy.sh main # деплой main ветки
./deploy.sh feature/x # деплой конкретной ветки
Скрипт 4 — проверка доступности сайтов
Пингует список сайтов, если не отвечает — пишет в лог и отправляет алерт.
bash
#!/bin/bash
SITES=(
"https://example.com"
"https://myapp.ru"
"https://api.myservice.ru/health"
)
ALERT_EMAIL="admin@example.com"
LOG="/var/log/site-check.log"
check_site() {
local URL=$1
local HTTP_CODE
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 10 \
--max-time 30 \
"$URL")
if [ "$HTTP_CODE" -eq 200 ]; then
echo "OK [$HTTP_CODE]: $URL"
return 0
else
echo "FAIL [$HTTP_CODE]: $URL"
return 1
fi
}
echo "=== Site check $(date) ===" >> "$LOG"
for SITE in "${SITES[@]}"; do
if ! check_site "$SITE" >> "$LOG" 2>&1; then
MESSAGE="ALERT: $SITE returned non-200 response"
echo "$MESSAGE" | mail -s "Site Down Alert" "$ALERT_EMAIL"
fi
done
Скрипт 5 — быстрая настройка нового сервера
Когда поднимаю новый VPS — первым делом запускаю этот скрипт. Обновляет систему, ставит нужные пакеты, настраивает базовую безопасность.
bash
#!/bin/bash
set -e
echo "=== Initial server setup ==="
# Обновить систему
apt update && apt upgrade -y
# Установить базовые пакеты
apt install -y \
curl wget git htop tmux \
ufw fail2ban \
nginx certbot python3-certbot-nginx \
docker.io docker-compose
# Настроить файрвол
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https
ufw --force enable
# Включить fail2ban
systemctl enable fail2ban
systemctl start fail2ban
# Добавить текущего пользователя в группу docker
usermod -aG docker $USER
echo "=== Setup complete! ==="
echo "Reboot recommended: sudo reboot"
Полезные однострочники
Иногда не нужен целый скрипт — достаточно однострочника.
Запустить команду на нескольких серверах:
bash
for s in web1 web2 web3; do ssh $s "systemctl status nginx"; done
Найти и заменить строку во всех файлах:
bash
find /etc/nginx -name "*.conf" -exec sed -i 's/old.domain.ru/new.domain.ru/g' {} \;
Архивировать и сразу передать на другой сервер:
bash
tar -czf - /var/www/mysite | ssh user@backup-server "cat > /backups/mysite.tar.gz"
Выполнить скрипт на удалённом сервере без копирования:
bash
ssh user@server 'bash -s' < local-script.sh
Мониторить появление строки в логе:
bash
tail -f /var/log/nginx/access.log | grep --line-buffered "500"
Где хранить скрипты
Системные скрипты кладу в /usr/local/bin/ — они доступны из любого места без указания пути. Права:
bash
chmod +x /usr/local/bin/myscript.sh
Все скрипты держу в git-репозитории. Называю его server-scripts или dotfiles. Когда поднимаю новый сервер — клонирую репозиторий и все мои скрипты уже на месте.
Итог
Bash-скрипты — это не высшая математика. Это инструмент который сэкономит тебе часы рутинной работы если начать его использовать.
Начни с малого: следующий раз когда поймаешь себя на повторяющемся действии — открой редактор и напиши скрипт. Даже если он получится кривой и с костылями — он будет работать. А со временем придёт чистота и элегантность.
Автоматизируй или страдай — выбор очевиден.