Bash-скрипты: автоматизация рутины

Если ты делаешь одно и то же в терминале больше двух раз - напиши скрипт. Деплой, бэкап, проверка здоровья сервиса - всё это автоматизируется за 10 минут.

Первый скрипт

#!/bin/bash
echo "Привет из скрипта!"
echo "Текущая дата: $(date)"
echo "Пользователь: $(whoami)"
chmod +x hello.sh
./hello.sh
`#!/bin/bash` (shebang) - указывает системе, каким интерпретатором запускать скрипт. Без неё система может использовать `sh`, а это не то же самое, что `bash`.

Переменные

#!/bin/bash
APP_NAME="backend"
PORT=8080
LOG_DIR="/var/log/${APP_NAME}"

echo "Запуск ${APP_NAME} на порту ${PORT}"
echo "Логи: ${LOG_DIR}"

Правила:

  • Без пробелов вокруг =: NAME="value" (не NAME = "value")
  • Фигурные скобки ${VAR} - безопаснее, чем $VAR (особенно в строках)
  • $() - подстановка команды: CURRENT_DATE=$(date +%Y-%m-%d)

Аргументы скрипта

#!/bin/bash
# Использование: ./deploy.sh production v1.2.3

ENVIRONMENT=$1    # первый аргумент
VERSION=$2        # второй аргумент

echo "Деплой версии ${VERSION} в ${ENVIRONMENT}"
ПеременнаяЗначение
$0имя скрипта
$1, $2, ...аргументы
$#количество аргументов
$@все аргументы
$?exit code предыдущей команды

Условия

#!/bin/bash

if [ -z "$1" ]; then
    echo "Ошибка: укажи окружение (production/staging)"
    exit 1
fi

if [ "$1" = "production" ]; then
    echo "Деплой в прод - будь осторожен!"
elif [ "$1" = "staging" ]; then
    echo "Деплой в staging"
else
    echo "Неизвестное окружение: $1"
    exit 1
fi

Полезные проверки:

[ -f "file.txt" ]     # файл существует
[ -d "directory" ]     # каталог существует
[ -z "$VAR" ]          # переменная пуста
[ -n "$VAR" ]          # переменная не пуста
[ "$A" = "$B" ]        # строки равны
[ "$A" -gt 10 ]        # число больше 10
`[ -f file.txt ]` - правильно. `[-f file.txt]` - ошибка синтаксиса. Bash тут не прощает.

Циклы

#!/bin/bash
# Проверка здоровья нескольких сервисов

SERVICES=("backend:8080" "frontend:80" "postgres:5432")

for SERVICE in "${SERVICES[@]}"; do
    HOST=$(echo "$SERVICE" | cut -d: -f1)
    PORT=$(echo "$SERVICE" | cut -d: -f2)
    
    if nc -z localhost "$PORT" 2>/dev/null; then
        echo "[OK] ${HOST} - порт ${PORT} открыт"
    else
        echo "[FAIL] ${HOST} - порт ${PORT} не отвечает"
    fi
done

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

#!/bin/bash
set -euo pipefail

# set -e - остановиться при первой ошибке
# set -u - ошибка при использовании неопределённой переменной
# set -o pipefail - ошибка в пайпе не проглатывается
Без этих флагов скрипт продолжит работу после ошибки. Представь: `cd /opt/myapp` не сработал, а следующая строка - `rm -rf *`. Без `set -e` ты удалишь всё в текущем каталоге.

Практический пример: скрипт деплоя

#!/bin/bash
set -euo pipefail

APP_NAME="backend"
DEPLOY_DIR="/opt/${APP_NAME}"
BACKUP_DIR="${DEPLOY_DIR}/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

echo "=== Деплой ${APP_NAME} ==="

# 1. Бэкап текущей версии
echo "Создаю бэкап..."
mkdir -p "${BACKUP_DIR}"
if [ -f "${DEPLOY_DIR}/${APP_NAME}" ]; then
    cp "${DEPLOY_DIR}/${APP_NAME}" "${BACKUP_DIR}/${APP_NAME}_${TIMESTAMP}"
fi

# 2. Остановка сервиса
echo "Останавливаю сервис..."
sudo systemctl stop "${APP_NAME}" || true

# 3. Копирование нового бинарника
echo "Копирую новую версию..."
cp "./build/${APP_NAME}" "${DEPLOY_DIR}/${APP_NAME}"
chmod 755 "${DEPLOY_DIR}/${APP_NAME}"

# 4. Запуск
echo "Запускаю сервис..."
sudo systemctl start "${APP_NAME}"

# 5. Проверка
sleep 2
if curl -sf http://localhost:8080/health > /dev/null; then
    echo "=== Деплой успешен ==="
else
    echo "=== ОШИБКА: сервис не отвечает, откатываю ==="
    cp "${BACKUP_DIR}/${APP_NAME}_${TIMESTAMP}" "${DEPLOY_DIR}/${APP_NAME}"
    sudo systemctl start "${APP_NAME}"
    exit 1
fi

Полезные паттерны

# Логирование
log() { echo "[$(date '+%H:%M:%S')] $*"; }
log "Начинаю деплой"

# Проверка зависимостей
command -v docker >/dev/null 2>&1 || { echo "Docker не установлен"; exit 1; }

# Cleanup при выходе
cleanup() { rm -f /tmp/myapp.lock; }
trap cleanup EXIT

Мини-задание

  • Напиши скрипт check-health.sh, который делает curl на localhost:8080/health и выводит OK/FAIL
  • Добавь set -euo pipefail в начало
  • Добавь аргумент для порта: ./check-health.sh 3000
  • Сделай chmod +x и запусти

Итог

  • set -euo pipefail - первая строка после shebang. Без обсуждений.
  • Переменные в ${}, подстановка команд в $(). Пробелы в [ ] - обязательны.
  • Если делаешь что-то руками больше двух раз - напиши скрипт.

Типичная ошибка

Писать скрипт без set -e и без проверки ошибок. Скрипт «работает» - пока не ломается на середине и не оставляет систему в непонятном состоянии. Всегда думай: «что если эта строка упадёт?»

Мини-практика

Напиши скрипт system-info.sh, который выводит: 1) имя хоста, 2) IP-адрес, 3) uptime, 4) использование диска (df -h /), 5) топ-3 процесса по CPU. Используй переменные и форматированный вывод. Добавь set -euo pipefail. Запусти и убедись, что всё работает.

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.