Создание bash-скриптов: переменные, условия, циклы и отладка

Почему 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» [email protected]
else
echo «Ошибка при резервном копировании» | mail -s «Backup FAILED» [email protected]
fi
«`

Здесь применены:
— Проверка ошибок через `set -e`
— Переменные с датой
— Условие с `if`
— Логирование и уведомление

Бонус: 5 трюков, которые знают только опытные

— Используйте `declare -A` для ассоциативных массивов (Bash 4+).
— Разделяйте скрипты на функции и храните в отдельных модулях.
— Проверяйте наличие бинарников через `command -v` или `type`.
— Используйте `getopts` для обработки флагов и параметров.
— Замените `echo` на `printf` — он надёжнее при спецсимволах.

Заключение: пишите скрипты, которыми не стыдно делиться

Создание скриптов — это не просто набор команд, а полноценное программирование с учётом UX, читаемости и отказоустойчивости. Потратьте время на структуру, отладку и обработку ошибок — и ваш Bash станет инструментом, которому доверяют. Даже в мире Kubernetes и Ansible он по-прежнему на своём месте.

Прокрутить вверх