Bash уміє виконувати арифметичні дії, але лише з цілими числами. Якщо потрібні обчислення з плаваючою комою, нам доведеться звернутися до якоїсь зовнішньої програми (наприклад, awk або bc).
Арифметичні оператори перелічено (у порядку пріоритету) в посібнику: Арифметика оболонки.
Крім того, Bash виконує арифметичні дії лише в певних місцях (які називають арифметичними «контекстами»).
Арифметичне розгортання має такий самий вигляд, як підстановка команд, але з подвійними дужками.
$(( expression ))
Цей синтаксис розгортається в результат обчислення виразу. Його часто використовують, щоб зберегти результат у змінній:
x=$(( 3 + 4 ))
Скільки пробілів ставити всередині дужок - справа смаку. Це питання стилю та читабельності.
x=$(( 3 + 4 ))
y=$((5+6))
Автор, прихильний до читача, любить додавати пробіли, щоб арифметичний вираз було краще видно в дужках.
Арифметичну умовну конструкцію виконують у подвійних дужках без провідного $:
(( expression ))
Вона не розгортається в результат. Натомість вона формує статус виходу.
Усередині (( Bash використовує те саме розуміння правди та неправди, що й мова C: нуль означає неправду, а ненульове значення - правду.
Це протилежність до того, як Bash оцінює статуси виходу!
Цей синтаксис зазвичай використовують у двох місцях:
як частину COMMAND в умовній конструкції if або в циклі while.
if (( x == y - 1 )); then ...
while (( y > 0 )); do ...
як окрему команду, щоб змінити значення змінної.
(( x = x + 1 ))
echo "new value for x is $x"
Будьмо обережні, поєднуючи арифметику в такий спосіб із set -e.
Якщо вираз обчислюється в нуль, статус виходу буде ненульовим, і Bash завершить роботу.
x=0
(( x++ ))
echo "you will see this"
set -e
x=0
(( x++ ))
echo "shell exits on the previous line; you won't see this"
Докладніше про оператор ++ розповідає стаття Postincrement vs Preincrement
Є ще кілька місць, де діє арифметичний контекст:
for (( i = 0; i < 10; i++ )); do
echo "$i"
done
Частина з індексом у масиві з числовими індексами.
echo "The next element is ${array[x + 1]}"
# ................................^^^^^
Докладніше про масиви читайте в документі концепції Масиви.
Частини зі зсувом і довжиною в розширенні ${var:offset:length}.
(( expression )) має перевагу над let "expresson".
На відміну від арифметичної умовної конструкції, вираз let підлягає розгортанню імен файлів і розбиттю на слова, тож його потрібно брати в лапки.Присвоєння змінній, коли змінна має «атрибут цілого числа».
# variable x does not have the integer attribute
x="3 + 4"
declare -p x # => declare -- x="3 + 4"
# variable y has the integer attribute
declare -i y
y="4 + 5"
declare -p y # => declare -i y="9"
На думку автора, declare -i має обмежене застосування.
Його основна перевага в тому, що він дозволяє «голу» арифметику (без синтаксису арифметичного виразу).
Це може заплутати тих, хто читає код, адже арифметичні вирази обчислюються в несподіваних місцях.
Ось одна особливість арифметичних контекстів, яка покращує читабельність.
height=10
width=20
area=$(( height * width ))
Звернімо увагу: у змінних в арифметичному виразі немає знаків долара $.
Bash це дозволяє.
Завдяки цьому вирази читаються набагато легше.
У посібнику сказано:
Усередині виразу на змінні оболонки можна також посилатися за іменем, не використовуючи синтаксис підстановки параметрів.
Це працює навіть для елементів масиву:
point1=(10 15) # these are (x, y) coordinates
point2=(20 7)
distance_squared=$(( (point1[0] - point2[0])**2 + (point1[1] - point2[1])**2 ))
Але інші вирази з параметрами потребують свого спеціального синтаксису. Наприклад, подвоєння довжини рядка тексту (англ. string):
s="hello world"
len=$(( ${#s} * 2 ))
Те саме стосується й позиційних параметрів. Якщо перші два позиційні параметри задають ширину і висоту:
area=$(( $1 * $2 ))