Олег Марков
Создание Bash-скрипта — полный практический разбор
Введение
Bash-скрипт — это обычный текстовый файл с командами shell, который позволяет автоматизировать повторяющиеся задачи: деплой, бэкапы, миграции, проверку окружения, сбор логов и многое другое.
На практике проблема обычно не в том, чтобы «написать пару команд», а в том, чтобы сделать скрипт надёжным и поддерживаемым: с проверками, понятными сообщениями, корректными кодами возврата и предсказуемым поведением.
В этой статье я покажу, как правильно создавать Bash-скрипты с нуля, какие соглашения использовать и какие ошибки чаще всего допускают в реальных проектах.
Что такое Bash-скрипт
Bash-скрипт — файл (чаще всего с расширением .sh), который интерпретатор Bash выполняет построчно.
Простой пример:
echo "Привет, Bash"
date
Если сохранить эти строки в файл и выполнить его через Bash, команды отработают в указанном порядке.
Минимальная структура корректного скрипта
Рекомендованный шаблон старта:
#!/usr/bin/env bash
set -euo pipefail
main() {
echo "Скрипт запущен"
}
main "$@"
Разберём по частям:
#!/usr/bin/env bash— shebang, указывает, чем запускать файл.set -euo pipefail— «строгий режим» для безопасного поведения.main()— точка входа для читаемой структуры.main "$@"— передача всех аргументов скрипта вmain.
Пошаговое создание первого скрипта
Шаг 1. Создать файл
touch backup.sh
Шаг 2. Добавить структуру
cat > backup.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
SOURCE_DIR="${1:-/var/log}"
TARGET_DIR="${2:-./backups}"
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
ARCHIVE_NAME="backup_${TIMESTAMP}.tar.gz"
main() {
mkdir -p "$TARGET_DIR"
tar -czf "$TARGET_DIR/$ARCHIVE_NAME" "$SOURCE_DIR"
echo "Готово: $TARGET_DIR/$ARCHIVE_NAME"
}
main "$@"
EOF
Шаг 3. Выдать права на исполнение
chmod +x backup.sh
Шаг 4. Запустить
./backup.sh /var/log ./artifacts
Почему важен строгий режим
Конструкция:
set -euo pipefail
означает:
-e— завершать скрипт при ошибке команды (ненулевой exit code);-u— падать при использовании необъявленной переменной;-o pipefail— возвращать ошибку, если любой этап в pipeline завершился неуспешно.
Без этих опций скрипты часто «молча» продолжают работу после ошибок, что приводит к повреждённым артефактам или частично выполненным операциям.
Базовые правила структуры
1) Разделяй константы и логику
Сначала переменные/конфиг, затем функции, затем main.
2) Всегда экранируй переменные в кавычках
# плохо
cp $SOURCE_DIR/file.txt $TARGET_DIR/
# хорошо
cp "$SOURCE_DIR/file.txt" "$TARGET_DIR/"
Это защищает от пробелов и спецсимволов в путях.
3) Используй функции
Даже для среднего скрипта лучше разнести шаги по функциям:
check_dependencies() { ... }
prepare_dirs() { ... }
run_backup() { ... }
Так проще тестировать и дорабатывать.
4) Пиши явные сообщения об ошибках
if [[ ! -d "$SOURCE_DIR" ]]; then
echo "Ошибка: директория не найдена: $SOURCE_DIR" >&2
exit 1
fi
Пример production-friendly скрипта
#!/usr/bin/env bash
set -euo pipefail
readonly SCRIPT_NAME="$(basename "$0")"
readonly DEFAULT_SOURCE="/var/log"
readonly DEFAULT_TARGET="./backups"
log() {
echo "[$SCRIPT_NAME] $*"
}
fail() {
echo "[$SCRIPT_NAME][ERROR] $*" >&2
exit 1
}
check_dependencies() {
command -v tar >/dev/null 2>&1 || fail "Команда tar не найдена"
command -v date >/dev/null 2>&1 || fail "Команда date не найдена"
}
parse_args() {
SOURCE_DIR="${1:-$DEFAULT_SOURCE}"
TARGET_DIR="${2:-$DEFAULT_TARGET}"
}
validate_input() {
[[ -d "$SOURCE_DIR" ]] || fail "Директория не существует: $SOURCE_DIR"
}
run_backup() {
local ts archive
ts="$(date +%Y%m%d_%H%M%S)"
archive="backup_${ts}.tar.gz"
mkdir -p "$TARGET_DIR"
tar -czf "$TARGET_DIR/$archive" "$SOURCE_DIR"
log "Бэкап создан: $TARGET_DIR/$archive"
}
main() {
check_dependencies
parse_args "$@"
validate_input
run_backup
}
main "$@"
Частые ошибки при создании скриптов
Ошибка 1. Неправильный shebang
#!/bin/sh
Если ты используешь Bash-специфичные конструкции ([[ ]], массивы, source, mapfile), запускай именно Bash:
#!/usr/bin/env bash
Ошибка 2. Отсутствие прав на запуск
chmod +x script.sh
Без этого ./script.sh не выполнится.
Ошибка 3. Необработанные аргументы
Если скрипту нужны обязательные параметры, проверяй их явно:
[[ $# -ge 1 ]] || { echo "Использование: $0 <path>" >&2; exit 1; }
Ошибка 4. Игнорирование stderr
Сообщения об ошибках выводи в stderr (>&2), а не в обычный stdout — это упрощает логирование и пайпы.
Проверка и отладка нового скрипта
Быстрая проверка синтаксиса
bash -n script.sh
Трассировка выполнения
bash -x script.sh arg1 arg2
Линтинг
Рекомендуется использовать ShellCheck:
shellcheck script.sh
Он находит большинство типичных проблем до запуска в проде.
Рекомендации по стилю
- Используй осмысленные имена переменных:
SOURCE_DIR,TARGET_DIR,ARCHIVE_NAME. - Для неизменяемых значений применяй
readonly. - Держи одну ответственность на функцию.
- Добавляй краткие комментарии только там, где неочевидна причина решения.
- Делай скрипт идемпотентным, если это возможно (повторный запуск не ломает состояние).
Итоги
Создание Bash-скрипта — это не просто файл с командами, а мини-программа с контрактом:
- понятный вход (аргументы),
- проверка предусловий,
- предсказуемая обработка ошибок,
- читаемая структура,
- корректный код возврата.
Если с самого начала использовать shebang, строгий режим, функции и валидацию входных данных, скрипты становятся намного стабильнее и дешевле в поддержке.