← Весь справочник

Арифметика в 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
letlet 'n = n * 2'как ((…)); выражение с пробелами бери в кавычки
declare -i nn='3 + 4' → 7переменная всегда вычисляется как выражение
exprexpr 3 \* 4внешняя программа; * экранируй, операнды и операторы — через пробел
awkawk 'BEGIN { print 7 / 2 }' → 3.5дробные числа и функции sqrt, int, exp, log, sin
python3 -cpython3 -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