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

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

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

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

  1. Арифметическое вычисление.
  2. Логическую проверку результата (возвращаемого кода, он же return code, rc).

Особенности кода возврата арифметических операций:

А операция вида ((expression++)) сначала возвращает текущее значение переменной, а затем увеличивает его🍿

Так вот, если начальное значение check_count=0, то при первом выполнении команды ((check_count++)) Bash сперва вернёт код 1, что интерпретируется как ошибка😵💫

По умолчанию Bash игнорирует любые ошибки и продолжает выполнение. Поэтому в своих скриптах я часто использую параметр оболочки set -e, чтобы скрипт завершался при любой ошибке — это делает его поведение более предсказуемым.
И вроде как всё очевидно: получил ошибку — завершился, но в скрипте есть асинхронные задачи (&), выполняющиеся в подпроцессах🤔

Пример из скрипта check_hosts.sh:

BASH
for host in "${CHECK_HOSTS[@]}"; do
    monitor_host "$host" &
done
Нажмите, чтобы развернуть и увидеть больше

Этот блок запускает monitor_host параллельно для каждого хоста из заданного списка.
Если подпроцесс попадал в ветку else и доходил до ((check_count++)), где check_count был равен 0, он завершался с ошибкой из-за set -e. При этом основной скрипт и другие подпроцессы продолжали работу💪

Это привело к не очевидному поведению: часть процессов завершалась, часть — продолжала выполняться. Что без детальных проверок можно и не заметить. Чтобы отловить этот момент, пришлось немного вынести поднапрячь мозги🤯

👌Решение:
Я добавил логическое «или» || true, чтобы команда всегда завершалась успешно:

BASH
((check_count++)) || true
Нажмите, чтобы развернуть и увидеть больше

Также возможен (и, вероятно, более правильный) способ — использовать префиксную форму:

BASH
((++check_count))
Нажмите, чтобы развернуть и увидеть больше

В этом случае Bash сразу возвращает новое значение, и если оно больше 0 (а так и будет), код возврата — 0.

Ну или, как мне подсказали, явно прибавлять единичку:

BASH
check_count=$((check_count+1))
Нажмите, чтобы развернуть и увидеть больше

Я знал про этот нюанс в арифметике Bash, но впервые столкнулся с ним в контексте асинхронных команд.

Авторские права

Автор: Иван Чёрный

Ссылка: https://r4ven.me/automation/bash-osobennosti-arifmeticheskih-operaczij-inkrement-expression/

Лицензия: CC BY-NC-SA 4.0

Использование материалов блога разрешается при условии: указания авторства/источника, некоммерческого использования и сохранения лицензии.

Начать поиск

Введите ключевые слова для поиска статей

↑↓
ESC
⌘K Горячая клавиша