
Bash: Особенности арифметических операций — инкремент ((expression++))
Приветствую!
Когда тестировал скрипт проверки доступности хостов, о котором говорил в прошлый раз, столкнулся с интересной особенностью 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, но впервые столкнулся с ним в контексте асинхронных команд.