#!/bin/bash плюс set -euo pipefail — стандартная первая строка любого моего скрипта. Но сама по себе она не делает скрипт надёжным, и вот на чём это ломается.
-e не срабатывает в условиях
set -e
if grep -q pattern file.txt; then
echo "нашли"
fi
Здесь grep может вернуть 1, и скрипт не упадёт — потому что команда стоит в условии. Это правильно по спецификации, но означает, что «у меня же стоит set -e» ничего не гарантирует для всего, что находится внутри if, while, && и ||.
Конвейер падает только по последней команде
set -e
gzip -c huge.log | ssh backup "cat > huge.log.gz"
Если gzip умер на середине, а ssh успешно записал обрезанный файл, код возврата конвейера — 0. Именно от этого спасает pipefail, но и он смотрит на код, а не на смысл: оба процесса завершились «успешно», просто данных меньше ожидаемого.
Подстановка команд в присваивании
set -e
COUNT=$(grep -c error app.log)
grep -c возвращает 1, когда совпадений нет. При set -e скрипт упадёт на пустом логе — при том, что пустой лог это нормальное состояние. Пишу так:
COUNT=$(grep -c error app.log || true)
-u и массивы
Обращение к пустому массиву как "${arr[@]}" в старых версиях bash падало при -u. В bash 4.4+ это исправлено, но на серверах с bash 4.2 (CentOS 7, да) — по-прежнему грабли. Безопасно: "${arr[@]:-}".
Что я добавляю сверху
set -Eeuo pipefail
trap 'echo "failed at line $LINENO: $BASH_COMMAND" >&2' ERR
-E заставляет ERR-ловушку наследоваться функциями, LINENO в сообщении сразу указывает, где именно. Без этого отладка скрипта в три часа ночи превращается в чтение логов через bash -x и догадки.
Ещё одна привычка: любую команду, которая меняет данные, оборачиваю в функцию с проверкой предусловия. Дешевле, чем восстанавливать базу из бэкапа, снятого вчера.