Арифметика в bash
Целочисленные вычисления — $(( )), (( )), let, expr, системы счисления, а дробные числа — через awk
Bash умеет считать только целые числа (64-битные, со знаком). Основной инструмент — арифметическая подстановка $((…)): она вычисляет выражение и подставляет результат. Конструкция ((…)) вычисляет без подстановки и годится для условий и счётчиков. let и внешняя утилита expr — старые способы, их встречают в чужих скриптах. Для дробных чисел нужен внешний калькулятор: в образе песочницы нет bc, зато есть awk и python3.
Синтаксис
x=$(( выражение )) # подставить результат
(( выражение )) # только вычислить; код 0, если результат не 0
let 'выражение' … # то же, что (( )), выражения — отдельными аргументами
expr a + b # внешняя команда, каждый токен — отдельный аргумент
Внутри $((…)) и ((…)) переменные можно писать без $: $((n * 2)). Пробелы вокруг операторов разрешены.
Операторы
| Оператор | Что делает | Пример → результат |
|---|---|---|
+ - * / | сложение, вычитание, умножение, деление нацело | $((7 / 2)) → 3 |
% | остаток от деления | $((7 % 2)) → 1 |
** | степень | $((2 ** 10)) → 1024 |
++ -- | инкремент / декремент (i++ и ++i) | ((i++)) |
= += -= *= /= %= | присваивание с операцией | ((sum += x)) |
< <= > >= == != | сравнение: 1 — истина, 0 — ложь | $((5 > 3)) → 1 |
&& || ! | логические и, или, не | $((1 && 0)) → 0 |
& | ^ ~ << >> | побитовые операции и сдвиги | $((1 << 4)) → 16 |
усл ? a : b | тернарный оператор | $((x > 0 ? x : -x)) |
( ) | группировка | $(( (2 + 3) * 4 )) → 20 |
, | вычислить по очереди, результат — последнее | $((a = 1, b = 2, a + b)) → 3 |
Системы счисления
| Запись | Значение |
|---|---|
0xff / 16#ff | шестнадцатеричное → 255 |
010 / 8#17 | восьмеричное (ведущий ноль!) → 8 / 15 |
2#1010 | двоичное → 10 |
36#z | любая основа от 2 до 64 → 35 |
10#08 | явно десятичное — спасает от ведущих нулей |
Обратно, из десятичного в другую систему, переводит printf: printf '%x\n' 255 → ff, printf '%o\n' 8 → 10.
Способы вычисления
| Способ | Пример | Особенности |
|---|---|---|
$((…)) | n=$((n + 1)) | основной, POSIX; результат — строка-число |
((…)) | ((n++)), if ((n > 10)) | только bash; код 0, если результат не равен 0 |
let | let 'n = n * 2' | как ((…)); выражение с пробелами бери в кавычки |
declare -i n | n='3 + 4' → 7 | переменная всегда вычисляется как выражение |
expr | expr 3 \* 4 | внешняя программа; * экранируй, операнды и операторы — через пробел |
awk | awk 'BEGIN { print 7 / 2 }' → 3.5 | дробные числа и функции sqrt, int, exp, log, sin |
python3 -c | python3 -c 'print(7 / 2)' | дробные, большие числа, сложные формулы |
expr также работает со строками: expr length hello → 5, expr substr hello 2 3 → ell.
Примеры
# счётчик в цикле
count=0
for f in /etc/*.conf; do
((count++))
done
echo "конфигов: $count"
# условие на числах
free_pct=12
if (( free_pct < 20 )); then
echo "мало места"
fi
# деление с округлением вверх: (a + b - 1) / b
pages=$(( (95 + 10 - 1) / 10 )) # 10
# проценты целыми: сначала умножить, потом делить
done=37; total=120
echo "$(( done * 100 / total ))%" # 30%
# байты в мегабайты
size=5242880
echo "$(( size / 1024 / 1024 )) МиБ" # 5 МиБ
# шестнадцатеричное в десятичное и обратно
echo $((16#1F)) # 31
printf '%X\n' 31 # 1F
# дробное деление через awk, 2 знака после точки
awk -v a=10 -v b=3 'BEGIN { printf "%.2f\n", a / b }' # 3.33
# сложить числа из столбца файла — тоже awk
printf '1.5\n2.25\n3\n' | awk '{ s += $1 } END { print s }' # 6.75
# случайное число от 1 до 6
echo $(( RANDOM % 6 + 1 ))
# число с ведущим нулём из даты (08, 09) — явно указать основу 10
month=$(date +%m)
echo $(( 10#$month + 1 ))
Подводные камни
- Деление целочисленное.
$((7 / 2))даёт3, остаток отбрасывается в сторону нуля:$((-7 / 2))→-3. Для дробей используйawk. Умножай до деления:a * 100 / b, а неa / b * 100. - Дробные числа — синтаксическая ошибка.
$((3.5 + 1))падает сinvalid arithmetic operator. - Ведущий ноль — восьмеричная система.
$((010))=8, аx=08; $((x))— ошибкаvalue too great for base. Так ломаютсяdate +%mиdate +%Hв августе, сентябре, в 8 и 9 часов. Пиши$((10#$x)). - Ошибка в арифметике завершает скрипт. Деление на ноль или неверное выражение в
$((…))прерывает неинтерактивный bash целиком, и|| …после неё не выполняется. Проверяй делитель заранее. ((i++))приi=0возвращает код 1. Постфиксный++возвращает старое значение, а 0 — это «ложь». Подset -eскрипт на этом завершится. Пиши((++i)),i=$((i + 1))или((i++)) || true.- Переполнение без предупреждения.
$((2 ** 63))даёт-9223372036854775808. Большие числа считай вpython3. expr 3 * 4без экранирования раскроет*в имена файлов текущего каталога и упадёт сsyntax error. Пишиexpr 3 \* 4, а лучше$((3 * 4)).expr 2+3без пробелов печатает строку2+3, а не5.letбез кавычек:let x=2 * 3раскроет*и ломается. Пишиlet 'x = 2 * 3'или((x = 2 * 3)).- Значение переменной вычисляется как выражение. При
v='2*3'выражение$((v + 1))даст7. Не пускай непроверенный ввод в арифметику — через неё можно выполнить команду.
См. также
variables · conditions-loops · awk · echo-printf · seq · python3