O Bash consegue fazer aritmética, mas apenas com números inteiros. Se precisares de fazer cálculos com números de vírgula flutuante, tens de recorrer a um programa externo (como o awk ou o bc).
Os operadores aritméticos estão listados (por ordem de precedência) no manual: Shell Arithmetic.
Além disso, o Bash só realiza aritmética em certos sítios (conhecidos como "contextos" aritméticos).
A expansão aritmética é parecida com a substituição de comandos, mas com parênteses duplos.
$(( expression ))
Esta sintaxe expande-se para o resultado da avaliação da expressão. É habitualmente usada para guardar o resultado numa variável:
x=$(( 3 + 4 ))
A quantidade de espaços em branco dentro dos parênteses é ao teu critério. É uma questão de estilo e de legibilidade.
x=$(( 3 + 4 ))
y=$((5+6))
O teu simpático autor gosta de acrescentar espaços para que a expressão aritmética fique mais visível dentro dos parênteses.
O condicional aritmético é executado dentro de parênteses duplos sem o $ inicial:
(( expression ))
Isto não se expande para o resultado. No entanto, produz um estado de saída.
Dentro de ((, o Bash usa o mesmo sentido de verdadeiro/falso que a linguagem C: zero é falso, diferente de zero é verdadeiro.
Isto é o oposto da forma como o Bash avalia os estados de saída!
Esta sintaxe é normalmente usada em dois sítios:
como a parte COMMAND de uma condicional if ou de um ciclo 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"
Tem cuidado ao usar aritmética desta forma em conjunto com set -e.
Se a expressão for avaliada como zero, o estado de saída será diferente de zero e o Bash termina.
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 ++, consulta este artigo sobre pós-incremento vs. pré-incremento
Há mais alguns sítios que são contextos aritméticos:
for (( i = 0; i < 10; i++ )); do
echo "$i"
done
A parte do índice de um array com índice numérico.
echo "The next element is ${array[x + 1]}"
# ................................^^^^^
Para saberes mais sobre arrays, lê o documento de conceito Arrays.
As partes do deslocamento e do comprimento da expansão ${var:offset:length}.
(( expression )) é preferível a let "expresson".
Ao contrário da construção condicional aritmética, a expressão let está sujeita a expansão de nomes de ficheiros e a divisão de palavras, e exige o uso de aspas.Atribuição de variáveis, quando a variável tem o "atributo 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"
Na opinião do autor, o declare -i tem uma utilidade limitada.
O seu principal benefício é permitir aritmética "simples" (sem a sintaxe de expressão aritmética).
Isto pode causar confusão a quem lê o teu código, porque as expressões aritméticas são avaliadas em sítios inesperados.
Há um aspeto dos contextos aritméticos que melhora a legibilidade.
height=10
width=20
area=$(( height * width ))
Reparaste que os sinais de dólar $ desapareceram das variáveis na expressão aritmética?
O Bash permite-te fazer isso.
Torna as expressões muito mais fáceis de ler.
O manual diz:
Dentro de uma expressão, as variáveis da shell também podem ser referidas pelo nome sem usar a sintaxe de expansão de parâmetros.
Funciona até para elementos de arrays:
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âmetros têm de usar a sua sintaxe especial. Por exemplo, duplicar o comprimento de uma string:
s="hello world"
len=$(( ${#s} * 2 ))
E os parâmetros posicionais também. Se os dois primeiros parâmetros posicionais representarem a largura e a altura:
area=$(( $1 * $2 ))