O Bash consegue fazer aritmética, mas apenas com números inteiros. Se você precisar fazer aritmética de ponto flutuante, vai precisar chamar algum programa externo (como awk ou bc).
Os operadores aritméticos estão listados (em ordem de precedência) no manual: Shell Arithmetic.
Além disso, o Bash só realiza aritmética em certos lugares (conhecidos como "contextos" aritméticos).
A expansão aritmética se parece com a substituição de comando, mas com parênteses duplos.
$(( expression ))
Essa sintaxe expande para o resultado da avaliação da expressão. É comum usá-la para guardar o resultado em uma variável:
x=$(( 3 + 4 ))
A quantidade de espaços em branco dentro dos parênteses é você quem decide. É uma questão de estilo e legibilidade.
x=$(( 3 + 4 ))
y=$((5+6))
O seu amigável autor gosta de adicionar espaços para que a expressão aritmética fique mais visível dentro dos parênteses.
A condicional aritmética é feita dentro de parênteses duplos sem o $ inicial:
(( expression ))
Isso não expande para o resultado. No entanto, produz um status de saída.
Dentro de (( o Bash usa o mesmo sentido de verdadeiro/falso que a linguagem C: zero é falso, diferente de zero é verdadeiro.
Isso é o oposto de como o Bash avalia status de saída!
Essa sintaxe costuma ser usada em dois lugares:
como a parte do COMANDO de uma condicional if ou de um laço while.
if (( x == y - 1 )); then ...
while (( y > 0 )); do ...
como um comando isolado para modificar o valor de uma variável.
(( x = x + 1 ))
echo "new value for x is $x"
Cuidado ao usar aritmética dessa forma combinada com set -e.
Se a expressão for avaliada como zero, o status de saída será diferente de zero e o Bash vai encerrar.
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"
Para uma discussão mais detalhada sobre o operador ++, veja este artigo sobre Postincrement vs Preincrement
Existem alguns outros lugares que são contextos aritméticos:
for (( i = 0; i < 10; i++ )); do
echo "$i"
done
A parte do índice de um array indexado numericamente.
echo "The next element is ${array[x + 1]}"
# ................................^^^^^
Para saber mais sobre arrays, leia o documento do conceito de Arrays.
As partes de deslocamento e comprimento da expansão ${var:offset:length}.
(( expression )) é preferível a let "expresson".
Diferentemente da construção de condicional aritmética, a expressão do let está sujeita à expansão de nomes de arquivo e à divisão de palavras, e exige o uso de aspas.Atribuição de variável, quando a variável tem o "atributo de inteiro".
# 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"
É opinião do autor que declare -i tem uso limitado.
Seu principal benefício é permitir aritmética "solta" (sem a sintaxe de expressão aritmética).
Isso pode causar confusão para quem lê o seu código, já que expressões aritméticas são avaliadas em lugares inesperados.
Veja um aspecto dos contextos aritméticos que melhora a legibilidade.
height=10
width=20
area=$(( height * width ))
Você reparou que os sinais de dólar $ sumiram das variáveis na expressão aritmética?
O Bash permite isso.
Isso deixa as expressões muito mais fáceis de ler.
O manual diz:
Dentro de uma expressão, as variáveis do shell também podem ser referenciadas pelo nome, sem usar a sintaxe de expansão de parâmetro.
Isso funciona até para elementos de array:
point1=(10 15) # these are (x, y) coordinates
point2=(20 7)
distance_squared=$(( (point1[0] - point2[0])**2 + (point1[1] - point2[1])**2 ))
Mas outras expressões de parâmetro precisam usar a sintaxe especial delas. Por exemplo, dobrar o comprimento de uma string:
s="hello world"
len=$(( ${#s} * 2 ))
E os parâmetros posicionais também precisam. Se os dois primeiros parâmetros posicionais representam largura e altura:
area=$(( $1 * $2 ))