Bash: Особенности арифметических операций — инкремент ((expression++))

Bash: Особенности арифметических операций — инкремент ((expression++))

Приветствую!

Когда тестировал скрипт проверки доступности хостов, о котором говорил в прошлый раз, столкнулся с интересной особенностью Bash при выполнении арифметических операций💪

Подписывайтесь на наш телеграм @r4ven_me📱, чтобы не пропустить новые публикации на сайте😉. А если есть вопросы или желание пообщаться по тематике — заглядывайте в Вороний чат @r4ven_me_chat🧐.

👨‍💻В чём суть:
В моём скрипте используется переменная check_count, содержащая текущее количество неудачных проверок. Увеличение этого счётчика производится командой инкремента: ((check_count++)).
Ключевое здесь слово — команда. Для дальнейшего понимания стоит добавить немного контекста😒

🤯Контекст:
В Bash синтаксис ((expression)) выполняет две функции:

  1. Арифметическое вычисление.
  2. Логическую проверку результата (возвращаемого кода, он же 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, но впервые столкнулся с ним в контексте асинхронных команд.

Не забывайте про нашу телегу📱и чат💬
Всех благ✌️

That should be it. If not, check the logs 🙂

Подписаться
Уведомить о
0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии