Когда тестировал скрипт проверки доступности хостов, о котором говорил в прошлый раз, столкнулся с интересной особенностью Bash при выполнении арифметических операций💪.
🖐️Эй!
Подписывайтесь на наш телеграм @r4ven_me📱, чтобы не пропустить новые публикации на сайте😉. А если есть вопросы или желание пообщаться по тематике — заглядывайте в Вороний чат @r4ven_me_chat🧐.
👨💻В чём суть:
В моём скрипте используется переменная check_count, содержащая текущее количество неудачных проверок. Увеличение этого счётчика производится командой инкремента: ((check_count++)).
Ключевое здесь слово — команда. Для дальнейшего понимания стоит добавить немного контекста😒
🤯Контекст:
В Bash синтаксис ((expression)) выполняет две функции:
- Арифметическое вычисление.
- Логическую проверку результата (возвращаемого кода, он же return code, rc).
Особенности кода возврата арифметических операций:
- если результат выражения больше 0 — возвращается код 0 (успех)
- если результат равен 0 — возвращается код 1 (ошибка)
А операция вида ((expression++)) сначала возвращает текущее значение переменной, а затем увеличивает его🍿
Так вот, если начальное значение check_count=0, то при первом выполнении команды ((check_count++)) Bash сперва вернёт код 1, что интерпретируется как ошибка😵💫
По умолчанию Bash игнорирует любые ошибки и продолжает выполнение. Поэтому в своих скриптах я часто использую параметр оболочки set -e, чтобы скрипт завершался при любой ошибке — это делает его поведение более предсказуемым.
И вроде как всё очевидно: получил ошибку — завершился, но в скрипте есть асинхронные задачи (&), выполняющиеся в подпроцессах🤔
Пример из скрипта check_hosts.sh:
for host in "${CHECK_HOSTS[@]}"; do
monitor_host "$host" &
doneЭтот блок запускает monitor_host параллельно для каждого хоста из заданного списка.
Если подпроцесс попадал в ветку else и доходил до ((check_count++)), где check_count был равен 0, он завершался с ошибкой из-за set -e. При этом основной скрипт и другие подпроцессы продолжали работу💪
Это привело к не очевидному поведению: часть процессов завершалась, часть — продолжала выполняться. Что без детальных проверок можно и не заметить. Чтобы отловить этот момент, пришлось немного вынести поднапрячь мозги🤯
👌Решение:
Я добавил логическое «или» || true, чтобы команда всегда завершалась успешно:
((check_count++)) || trueТакже возможен (и, вероятно, более правильный) способ — использовать префиксную форму:
((++check_count))В этом случае Bash сразу возвращает новое значение, и если оно больше 0 (а так и будет), код возврата — 0.
Ну или, как мне подсказали, явно прибавлять единичку:
check_count=$((check_count+1))Я знал про этот нюанс в арифметике Bash, но впервые столкнулся с ним в контексте асинхронных команд.


