LinuxParty

Inicio desactivadoInicio desactivadoInicio desactivadoInicio desactivadoInicio desactivado
 

Realizar operaciones matemáticas en Bash: cálculos con enteros y decimales

Bash incorpora operadores para sumar, restar, multiplicar y comparar números enteros, mientras que herramientas como bc y awk permiten trabajar con decimales y operaciones más avanzadas

Tarde o temprano, cualquier script de Bash necesita realizar algún cálculo: incrementar un contador, comprobar el espacio disponible, obtener un porcentaje, calcular el tiempo transcurrido o decidir si un valor supera determinado límite.

Bash permite realizar directamente numerosas operaciones matemáticas, pero presenta una limitación importante: su aritmética interna trabaja con números enteros. Si necesitamos utilizar decimales, raíces cuadradas, logaritmos u otras operaciones avanzadas, tendremos que recurrir a programas externos como bc o awk.

En este tutorial veremos las principales alternativas y explicaremos cuál conviene utilizar en cada situación.

La forma recomendada: $(( expresión ))

La expansión aritmética es generalmente la forma más clara y práctica de efectuar cálculos enteros en Bash.

Su sintaxis es:

resultado=$(( expresion ))

Por ejemplo:

resultado=$((5 + 3))
echo "$resultado"

El resultado será:

8

Dentro de una expresión aritmética no es obligatorio anteponer $ al nombre de las variables:

numero1=15
numero2=4

resultado=$((numero1 + numero2))

echo "$resultado"

También sería válido escribir:

resultado=$(($numero1 + $numero2))

Pero la primera forma resulta más limpia y suele ser preferible.

Operadores aritméticos disponibles

Bash admite los operadores matemáticos habituales:

  • + para sumar.
  • - para restar.
  • * para multiplicar.
  • / para dividir.
  • % para obtener el resto de una división.
  • ** para calcular una potencia.
  • ++ para incrementar un valor.
  • -- para reducir un valor.

Podemos probarlos mediante:

a=20
b=6

echo "Suma: $((a + b))"
echo "Resta: $((a - b))"
echo "Multiplicación: $((a * b))"
echo "División: $((a / b))"
echo "Resto: $((a % b))"
echo "Potencia: $((a ** 2))"

La salida será:

Suma: 26
Resta: 14
Multiplicación: 120
División: 3
Resto: 2
Potencia: 400

La división 20 / 6 devuelve 3 y no 3,333, porque Bash descarta la parte decimal.

Diferencia entre $(( )) y (( ))

Aunque ambas construcciones trabajan con expresiones aritméticas, no se utilizan exactamente de la misma manera.

La expresión $(( )) devuelve el resultado para poder mostrarlo o asignarlo:

resultado=$((10 * 5))
echo "$resultado"

La construcción (( )), en cambio, evalúa la operación y suele emplearse para modificar variables o realizar comparaciones:

contador=10

((contador++))
((contador += 5))

echo "$contador"

El resultado será:

16

También podemos realizar una asignación dentro de los dobles paréntesis:

((total = 25 + 15))
echo "$total"

Operadores abreviados de asignación

Bash admite operadores que permiten modificar una variable sin repetir su nombre:

numero=10

((numero += 5))
echo "$numero"

((numero -= 3))
echo "$numero"

((numero *= 2))
echo "$numero"

((numero /= 4))
echo "$numero"

Estas operaciones equivalen a:

numero=$((numero + 5))
numero=$((numero - 3))
numero=$((numero * 2))
numero=$((numero / 4))

La forma abreviada suele resultar más cómoda para contadores y acumuladores.

Incrementar y reducir contadores

Para incrementar un contador podemos utilizar:

contador=0
((contador++))

O bien:

((++contador))

Las dos expresiones aumentan el valor en una unidad, pero existe una diferencia en el resultado de la propia expresión.

El postincremento devuelve primero el valor anterior:

contador=5
echo "$((contador++))"
echo "$contador"

La salida será:

5
6

El preincremento aumenta primero la variable y después devuelve el resultado:

contador=5
echo "$((++contador))"
echo "$contador"

La salida será:

6
6

Precaución con set -e y ((contador++))

La construcción (( expresión )) devuelve un estado de ejecución 0 cuando la expresión produce un resultado distinto de cero. Si el resultado es cero, devuelve el estado 1.

Esto puede causar una finalización inesperada en scripts que utilicen:

set -e

Por ejemplo:

#!/usr/bin/env bash

set -e

contador=0
((contador++))

echo "Este mensaje podría no mostrarse"

Como contador++ evalúa primero el valor anterior, que era cero, la expresión devuelve el estado 1 y set -e puede detener el script.

Una alternativa más segura es:

((++contador))

También podemos impedir explícitamente que ese estado detenga el script:

((contador++)) || true

Comparaciones numéricas mediante (( ))

Los dobles paréntesis también resultan cómodos para incluir comparaciones dentro de estructuras condicionales:

edad=20

if ((edad >= 18)); then
    echo "Es mayor de edad"
else
    echo "Es menor de edad"
fi

Podemos utilizar los operadores:

  • == igual que.
  • != distinto de.
  • < menor que.
  • <= menor o igual que.
  • > mayor que.
  • >= mayor o igual que.
  • && condición lógica Y.
  • || condición lógica O.
  • ! negación.

Por ejemplo:

temperatura=32
humedad=70

if ((temperatura > 30 && humedad >= 60)); then
    echo "Hace calor y existe una humedad elevada"
fi

Dentro de (( )) podemos utilizar directamente < y >, sin necesidad de escaparlos.

Utilizar operaciones en un bucle

La aritmética de Bash se integra perfectamente en los bucles de estilo C:

for ((i = 1; i <= 5; i++)); do
    echo "Iteración número $i"
done

También podemos sumar una serie de valores:

total=0

for ((i = 1; i <= 10; i++)); do
    ((total += i))
done

echo "La suma es: $total"

El resultado será:

La suma es: 55

Declarar variables enteras con declare -i

Bash permite indicar que una variable debe tratarse como un número entero mediante:

declare -i numero

A partir de ese momento, cualquier asignación será evaluada como una expresión aritmética:

declare -i numero=10

numero=numero+5

echo "$numero"

El resultado será:

15

También podemos combinar diferentes atributos:

declare -ir limite=100

En este caso:

  • -i declara la variable como entera.
  • -r la convierte en una variable de solo lectura.

Si intentamos modificarla:

limite=200

Bash mostrará un error porque la variable es inmutable.

Dentro de una función, declare crea normalmente una variable local. Si necesitamos declarar una variable global desde una función, podemos utilizar declare -g, siempre que la versión de Bash lo permita:

declare -gi contador_global=0

Realizar cálculos con let

let es una orden interna de Bash que evalúa expresiones aritméticas:

numero=10

let numero=numero+5
echo "$numero"

También admite incrementos y operadores abreviados:

let numero++
let numero+=10
let numero*=2

Cuando la expresión contiene espacios o caracteres que la shell podría interpretar, es recomendable entrecomillarla:

let "resultado = 5 * 8"

Aunque let continúa siendo válido, los dobles paréntesis suelen producir un código más legible:

((resultado = 5 * 8))

El veterano comando expr

expr es una herramienta tradicional de Unix que permite evaluar expresiones:

resultado=$(expr 5 + 3)
echo "$resultado"

Los espacios entre los números y el operador son obligatorios:

expr 5 + 3

Si escribimos:

expr 5+3

el programa interpreta el argumento como una cadena y devuelve:

5+3

El operador de multiplicación debe escaparse para evitar que Bash lo interprete como un comodín:

expr 5 \* 3

Aunque expr puede encontrarse en scripts antiguos y mantiene su utilidad para determinadas operaciones con cadenas, en código Bash moderno suele ser preferible utilizar:

echo $((5 * 3))

Cómo trabajar con números decimales

Bash no realiza directamente cálculos en coma flotante. Si ejecutamos:

echo $((5 / 2))

obtendremos:

2

Para conseguir un resultado decimal podemos utilizar bc, una calculadora de precisión arbitraria que funciona desde la línea de comandos.

En Debian, Ubuntu y derivados podemos instalarla mediante:

sudo apt install bc

En Fedora:

sudo dnf install bc

En openSUSE:

sudo zypper install bc

En Arch Linux:

sudo pacman -S bc

Operaciones básicas con bc

Podemos enviar una expresión a bc mediante una tubería:

echo '4.1 + 5.2' | bc

El resultado será:

9.3

Para multiplicar:

echo '4.1 * 5.2' | bc

Dependiendo de las reglas de precisión utilizadas, el resultado puede mostrarse con menos decimales de los esperados. Para establecer explícitamente la precisión debemos asignar un valor a la variable scale:

echo 'scale=2; 4.1 * 5.2' | bc

El resultado será:

21.32

Para realizar una división con cuatro decimales:

echo 'scale=4; 10 / 3' | bc

Obtendremos:

3.3333

La variable scale establece el número de decimales utilizado en determinadas operaciones, especialmente en las divisiones.

No debemos confundirla con la función scale(x), que consulta cuántos dígitos decimales tiene un valor:

echo 'scale(12.345)' | bc

El resultado será:

3

Guardar el resultado de bc

Podemos almacenar el resultado mediante una sustitución de comandos:

resultado=$(echo 'scale=2; 25 / 4' | bc)

echo "$resultado"

La salida será:

6.25

También podemos utilizar una cadena de entrada de Bash:

resultado=$(bc <<< 'scale=2; 25 / 4')

Esta sintaxis evita crear un proceso adicional para echo, aunque es específica de Bash y de otras shells compatibles.

Cuando intervienen variables:

precio=19.95
cantidad=4

total=$(bc <<< "scale=2; $precio * $cantidad")

echo "Total: $total"

El resultado será:

Total: 79.80

Si los valores proceden de la entrada del usuario, debemos validarlos antes de incorporarlos a una expresión.

Calcular porcentajes

Un ejemplo frecuente consiste en calcular un porcentaje:

cantidad=375
total=500

porcentaje=$(bc <<< "scale=2; $cantidad * 100 / $total")

echo "Porcentaje: $porcentaje %"

La salida será:

Porcentaje: 75.00 %

También podemos calcular un descuento:

precio=120
descuento=15

importe_descuento=$(bc <<< "scale=2; $precio * $descuento / 100")
precio_final=$(bc <<< "scale=2; $precio - $importe_descuento")

echo "Descuento: $importe_descuento"
echo "Precio final: $precio_final"

Comparar números decimales con bc

Los operadores tradicionales de test y [ ] están pensados principalmente para enteros. Para comparar números decimales podemos utilizar bc.

En sus comparaciones, bc devuelve 1 cuando la condición es verdadera y 0 cuando es falsa:

echo '5.7 > 3.2' | bc

El resultado será:

1

Podemos utilizarlo dentro de un condicional:

temperatura=37.8

if (( $(bc <<< "$temperatura > 37.5") )); then
    echo "La temperatura supera el límite"
else
    echo "La temperatura está dentro del límite"
fi

Es importante comprobar previamente que la variable contiene un número válido.

Funciones matemáticas de bc

Al ejecutar bc con la opción -l, se carga su biblioteca matemática:

bc -l

Esto permite utilizar funciones como:

  • s(x) para calcular el seno en radianes.
  • c(x) para calcular el coseno en radianes.
  • a(x) para calcular el arcotangente.
  • l(x) para obtener el logaritmo natural.
  • e(x) para calcular la exponencial.
  • sqrt(x) para calcular una raíz cuadrada.

Por ejemplo:

echo 'scale=4; sqrt(2)' | bc -l

La salida será aproximadamente:

1.4142

Para calcular una potencia también podemos utilizar:

echo '2^10' | bc

El resultado será:

1024

Utilizar awk para cálculos decimales

awk constituye otra alternativa muy útil, especialmente cuando ya estamos procesando columnas o archivos de texto.

Para realizar una división:

awk 'BEGIN { printf "%.2f\n", 10 / 3 }'

El resultado será:

3.33

Con variables de Bash:

precio=19.95
cantidad=4

awk -v p="$precio" -v c="$cantidad" \
    'BEGIN { printf "%.2f\n", p * c }'

awk trabaja con números en coma flotante y permite controlar el formato mediante printf.

Para muchos cálculos administrativos sencillos puede resultar suficiente. Sin embargo, cuando necesitamos precisión decimal arbitraria, bc suele ofrecer más control.

Los cálculos monetarios delicados requieren una estrategia bien definida de redondeo. Otra posibilidad consiste en trabajar siempre con unidades enteras, como céntimos, evitando así determinados problemas de representación en coma flotante.

Cuidado con los ceros iniciales

Bash puede interpretar los números que comienzan por cero como valores octales.

Por ejemplo:

echo $((010))

El resultado será:

8

El número 010 se interpreta como octal y no como decimal.

Esto puede provocar errores al procesar horas, días o meses:

mes=08
echo $((mes + 1))

Bash puede mostrar un error porque el dígito 8 no existe en el sistema octal.

Para forzar la interpretación decimal podemos anteponer 10#:

mes=08
resultado=$((10#$mes + 1))

echo "$resultado"

La salida será:

9

Esta precaución resulta especialmente importante cuando los números proceden de date:

mes=$(date +%m)
mes_decimal=$((10#$mes))

Evitar divisiones por cero

Una división por cero provoca un error:

divisor=0
resultado=$((10 / divisor))

Antes de dividir debemos comprobar el valor:

dividendo=10
divisor=0

if ((divisor == 0)); then
    echo "Error: no se puede dividir por cero" >&2
    exit 1
fi

resultado=$((dividendo / divisor))
echo "$resultado"

La misma precaución debe aplicarse al utilizar bc.

Un ejemplo completo

El siguiente script solicita el precio y la cantidad de un producto, calcula el subtotal, aplica un descuento y muestra el resultado:

#!/usr/bin/env bash

read -r -p "Precio del producto: " precio
read -r -p "Cantidad: " cantidad
read -r -p "Descuento (%): " descuento

patron='^[0-9]+([.][0-9]+)?$'

if [[ ! $precio =~ $patron ||
      ! $cantidad =~ ^[0-9]+$ ||
      ! $descuento =~ $patron ]]; then
    echo "Error: introduce valores numéricos válidos." >&2
    exit 1
fi

subtotal=$(bc <<< "scale=2; $precio * $cantidad")
rebaja=$(bc <<< "scale=2; $subtotal * $descuento / 100")
total=$(bc <<< "scale=2; $subtotal - $rebaja")

printf 'Subtotal: %s €\n' "$subtotal"
printf 'Descuento: %s €\n' "$rebaja"
printf 'Total: %s €\n' "$total"

Este ejemplo valida los valores antes de entregarlos a bc y mantiene separadas las diferentes operaciones para facilitar su lectura.

¿Qué método deberíamos elegir?

Para cálculos con números enteros, la expansión aritmética es normalmente la opción más recomendable:

resultado=$((a + b))

Para modificar variables, crear contadores o realizar comparaciones:

((contador += 1))

Para números decimales y cálculos de precisión configurable:

resultado=$(bc <<< "scale=2; $a / $b")

Cuando ya estamos procesando texto o queremos aplicar rápidamente un formato decimal:

awk 'BEGIN { printf "%.2f\n", 10 / 3 }'

let continúa siendo válido, mientras que expr aparece principalmente en scripts antiguos o destinados a entornos muy específicos.

Bash ofrece suficientes recursos para resolver la mayoría de los cálculos habituales de un script. La clave consiste en recordar que su aritmética interna trabaja con enteros y elegir una herramienta externa cuando necesitemos decimales, mayor precisión o funciones matemáticas avanzadas.


Fuentes

No estás registrado para postear comentarios



Redes:



   

 

Suscribete / Newsletter

Suscribete a nuestras Newsletter y periódicamente recibirás un resumen de las noticias publicadas.

Donar a LinuxParty

 

Dona a LInuxParty

Donar 10

Donar 20

 

 

Tutorial de Linux

Formulario de acceso