Почему bash-скрипты остаются незаменимыми даже в эпоху облаков и Docker
Несмотря на развитие контейнеризации, автоматизации и DevOps-инструментов, старый добрый Bash продолжает быть ядром многих задач системного администрирования и CI/CD процессов. Причина проста: его доступность, скорость исполнения и тесная интеграция с Unix-средой. Но, как показывает практика, большинство скриптов страдают от одних и тех же проблем — непереносимости, неустойчивости и слабой читаемости. В этой статье мы разберёмся, как написать надёжный и масштабируемый bash-скрипт, используя переменные, условия, циклы и техники отладки.
Переменные: больше, чем просто контейнеры для данных
Неочевидные подводные камни
Переменные в Bash по умолчанию — строки. Это часто приводит к неожиданным результатам при арифметике или сравнении:
```bash
a="01"
b="1"
[[ $a == $b ]] && echo "равны" || echo "не равны" # Выведет: не равны
```
Причина — строковое сравнение. Чтобы сравнивать числа, используйте двойные круглые скобки:
```bash
(( a == b )) && echo "равны" || echo "не равны"
```
Лайфхаки с переменными
- Используйте `${VAR:-default}` для безопасной подстановки значений по умолчанию.
- Очищайте переменные с `unset VAR`, а не `VAR=""`, если хотите полностью удалить их из окружения.
- Для счётчиков используйте `let`, `(( ))` или встроенную арифметику:
```bash
count=0
((count++))
```
Условия: логика с нюансами
Классика жанра с twist’ом
Операторы `if`, `[[ ]]`, `[ ]` и `(( ))` — похожи, но имеют ключевые отличия. Например, `[[ ]]` более надёжен при работе со строками, особенно содержащими пробелы или спецсимволы:
```bash
file="my file.txt"
[[ -f "$file" ]] # безопасно
[ -f "$file" ] # может дать ошибку
```
Альтернативный подход: case
Для сложных ветвлений `case` может быть чище, чем вложенные `if`:
```bash
case "$1" in
start) systemctl start app ;;
stop) systemctl stop app ;;
*) echo "Usage: $0 {start|stop}" ;;
esac
```
Этот шаблон легко расширяется и читается гораздо лучше, чем каскад `if-elif-else`.
Циклы: когда `for` и `while` работают, а когда нет
Ошибка новичка: парсинг вывода
Цикл по `for i in $(ls)` может сломать скрипт, если в именах файлов есть пробелы. Правильный способ — использовать `find -print0` и `xargs -0`, либо `while read` с уточнёнными IFS:
```bash
while IFS= read -r -d '' file; do
echo "Обработка файла: $file"
done < <(find . -type f -print0)
```
Полезные паттерны
- Бесконечные циклы с контролем выхода:
```bash
while true; do
check_something && break
sleep 1
done
```
- Итерации по массиву:
```bash
arr=("foo" "bar" "baz")
for item in "${arr[@]}"; do
echo "$item"
done
```
Отладка: от `echo` до `trap`
Невидимые враги скрипта
Ошибки часто кроются не в логике, а в окружении: неправильный путь, пустая переменная, неявный `exit`. Решение — включить строгий режим:
```bash
set -euo pipefail
IFS=$'nt'
```
Это заставит скрипт падать при любой ошибке, что облегчает отладку на раннем этапе.
Инструменты профессионала
- `bash -x script.sh` — пошаговая трассировка команд.
- `trap` — перехват сигнала и отладка при выходе:
```bash
trap 'echo "Ошибка в строке $LINENO"; exit 1' ERR
```
- Логирование в STDERR:
```bash
log() { echo "[LOG] $*" >&2; }
```
Реальный пример: резервное копирование с проверкой
Ниже — пример скрипта, который делает резервную копию, проверяет успешность и отправляет уведомление:
```bash
#!/bin/bash
set -euo pipefail
trap 'echo "Ошибка на строке $LINENO"; exit 1' ERR
SRC="/var/www"
DEST="/backup/$(date +%F)"
LOG="/var/log/backup.log"
mkdir -p "$DEST"
rsync -a "$SRC/" "$DEST/" >> "$LOG" 2>&1
if [[ $? -eq 0 ]]; then
echo "Резервное копирование прошла успешно: $DEST" | mail -s "Backup OK" admin@example.com
else
echo "Ошибка при резервном копировании" | mail -s "Backup FAILED" admin@example.com
fi
```
Здесь применены:
- Проверка ошибок через `set -e`
- Переменные с датой
- Условие с `if`
- Логирование и уведомление
Бонус: 5 трюков, которые знают только опытные
- Используйте `declare -A` для ассоциативных массивов (Bash 4+).
- Разделяйте скрипты на функции и храните в отдельных модулях.
- Проверяйте наличие бинарников через `command -v` или `type`.
- Используйте `getopts` для обработки флагов и параметров.
- Замените `echo` на `printf` — он надёжнее при спецсимволах.
Заключение: пишите скрипты, которыми не стыдно делиться
Создание скриптов — это не просто набор команд, а полноценное программирование с учётом UX, читаемости и отказоустойчивости. Потратьте время на структуру, отладку и обработку ошибок — и ваш Bash станет инструментом, которому доверяют. Даже в мире Kubernetes и Ansible он по-прежнему на своём месте.



