detomas.net Curso de Bash desde cero
Descargar PDF

Curso de Bash desde cero / Capítulo 10

Lógica

Operadores aritméticos

Sirven para hacer cuentas. Se usan dentro de $(( )).

Aviso

La división de Bash es entera: $((7 / 2)) da 3, no 3.5. Si necesitas decimales, hay que recurrir a bc o a awk.

Operadores de comparación

Estos no son aritméticos: no calculan, sino que responden verdadero o falso. Son los que se usan dentro de un if.

Comparar números

Comparar cadenas de texto

Combinar condiciones

Comprobar archivos

Los dos tipos de corchetes

Habrás visto las comprobaciones escritas de dos formas. No son lo mismo.

[ ] es en realidad un comando, no parte del lenguaje. Por eso hay que dejar espacios a los lados: [ -f "$archivo" ] funciona, [-f "$archivo"] da error de «orden no encontrada». Y por eso también es tan sensible a las comillas.

[[ ]] sí es parte de Bash, y es más seguro y más cómodo:

archivo=""

[ -n $archivo ]      # error silencioso: da verdadero (ver cap. de comillas)
[[ -n $archivo ]]    # correcto: da falso, aunque olvides las comillas

Además [[ ]] permite cosas que [ ] no:

# Comparar alfabéticamente
[[ "$a" < "$b" ]]

# Comprobar con un patrón
[[ "$fichero" == *.txt ]]

# Comprobar con una expresión regular
[[ "$edad" =~ ^[0-9]+$ ]] && echo "Es un número"

# Y y O dentro de los mismos corchetes
[[ $num -ge 18 && -n "$nombre" ]]

Consejo

Usa [[ ]] siempre que escribas para Bash. Es más difícil equivocarse. [ ] solo hace falta si necesitas que el script funcione también con sh en sistemas muy antiguos.

Aun así, sigue poniendo comillas: [[ ]] te salva de las variables vacías, pero acostumbrarse a las comillas te salva en todos los demás sitios.

En el resto del capítulo verás [ ] porque es lo que aparece en la mayoría de scripts que te vas a encontrar por ahí, y hay que saber leerlo.

Condicionales

Permiten ejecutar comandos solo si se cumplen ciertas condiciones.

if

if [ condición ]; then
    comando_si_verdadero
fi

if con else

if [ condición ]; then
    comando_si_verdadero
else
    comando_por_defecto
fi

if con elif y else

if [ condición ]; then
    comando_si_verdadero
elif [ condición ]; then
    comando_si_verdadero
fi

Aviso

Puedes encadenar tantos elif como quieras, pero en cuanto se cumple la primera condición, las demás ya no se evalúan. El orden importa: pon primero las condiciones más concretas y deja las generales para el final.

if [ condición ]; then
    comando_si_verdadero
elif [ condición ]; then
    comando_si_verdadero
elif [ condición ]; then
    comando_si_verdadero
else
    comando_por_defecto
fi

case

Cuando hay que comparar la misma variable contra muchos valores, encadenar elif queda largo y farragoso. Para eso está case, que es lo que se usa normalmente para los menús.

case variable in
    valor_1) comando_si_es_valor_1;;
    valor_2) comando_si_es_valor_2;;
    valor_3) comando_si_es_valor_3;;
    *)       comando_por_defecto;;
esac

Un ejemplo real:

#!/bin/bash
read -r -p "Elige una opción (a/b/c): " opcion

case "$opcion" in
    a) echo "Elegiste A";;
    b) echo "Elegiste B";;
    c) echo "Elegiste C";;
    *) echo "Opción no válida";;
esac

Todo junto

#!/bin/bash

num=25
name="Nica"

# Comparar números
if [ $num -ge 10 ]; then
    echo "Número mayor o igual que 10"
elif [ $num -eq 0 ]; then
    echo "Número igual a 0"
else
    echo "Condición por defecto"
fi

# Comprobar que una cadena no está vacía
if [ -n "$name" ]; then
    echo "El nombre existe"
fi

# Dos condiciones a la vez
if [ $num -ge 18 ] && [ -n "$name" ]; then
    echo "Mayor de edad"
fi

# Comprobar que un archivo existe
if [ -e "./script.sh" ]; then
    echo "El archivo existe"
fi

Bucles

Un bucle repite un trozo de código varias veces. Bash tiene tres, y la diferencia está en cuándo paran.

for

Recorre una lista de valores que ya conoces de antemano: números, nombres de archivo, líneas… Se ejecuta una vez por cada elemento.

for i in 1 2 3 4 5
do
    echo "Número: $i"
done
for name in *.sh
do
    echo "Archivo: $name"
done

while

Se repite mientras la condición sea verdadera. Se usa cuando no sabes de antemano cuántas vueltas hará.

count=1
while [ $count -le 5 ]
do
    echo "Contador: $count"
    ((count++))
done

until

Es el while al revés: se repite hasta que la condición se cumpla.

count=1
until [ $count -gt 10 ]
do
    echo "Contador: $count"
    ((count++))
done

Control de bucles

for i in 1 2 3 4 5
do
    if [ $i -eq 3 ]; then
        continue
    elif [ $i -eq 4 ]; then
        break
    fi
    echo "Número: $i"
done

Script de ejemplo con bucles


#!/bin/bash

# for
for i in 1 2 3 4 5
do
   echo "Número: $i"
done
for name in *.sh
do
   echo "Archivo: $name"
done

# while
count=1
while [ $count -le 5 ]
do
   echo "Contador: $count"
   ((count++))
done

# until
count=1
until [ $count -gt 10 ]
do
   echo "Contador: $count"
   ((count++))
done

# Control de bucles
for i in 1 2 3 4 5
do
   if [ $i -eq 3 ]; then
       continue
   elif [ $i -eq 4 ]; then
       break
   fi
       echo "Número: $i"
done

Funciones

Una función es un trozo de script al que le pones nombre para poder reutilizarlo. Sirve para no repetir el mismo código diez veces.

Función simple

saludar() {
    echo "Hola desde la función"
}

saludar

La definición tiene que ir antes de la llamada en el script. Bash lee de arriba abajo: si llamas a una función que aún no ha leído, dirá que no existe.

Función con parámetros

saludar_a() {
    echo "Hola $1"
}

saludar_a Nica

Dentro de la función, $1 es el primer argumento que le pasas a la función, $2 el segundo, y así.

Aviso

Ojo con esto, que confunde mucho: dentro de una función, $1 ya no es el primer argumento del script, sino el de la función. En cambio $0 sigue siendo el nombre del script.

Si dentro de una función necesitas un argumento del script, pásaselo tú: mi_funcion "$1".

Lo que devuelve una función

Aquí hay una trampa que conviene entender bien desde el principio.

comprobar_edad() {
    if [ "$1" -ge 18 ]; then
        return 0        # 0 = todo bien
    else
        return 1        # distinto de 0 = algo pasa
    fi
}

comprobar_edad 20
echo $?                 # 0

Precaución

return no devuelve un resultado, devuelve un código de salida: un número entre 0 y 255 que dice si la función fue bien o mal. Y al revés de lo que uno esperaría, 0 significa éxito y cualquier otro número significa error.

Por eso return 1 no está «devolviendo un uno»: está diciendo «he fallado».

Si lo que quieres es que la función te devuelva un dato (una suma, un texto, una ruta), no se usa return: se usa echo y se recoge con $(...).

sumar() {
    echo $(( $1 + $2 ))
}

resultado=$(sumar 5 3)
echo "La suma es $resultado"     # La suma es 8

Resumiendo:

Quieres… Usa Se recoge con
Decir si fue bien o mal return 0 / return 1 $?
Devolver un dato echo "$valor" $(mi_funcion)

Variables globales y locales (Ámbito)

#!/bin/bash
nombre="Nica"                  # global: visible en todo el script
saludar() {
    local mensaje="hola"       # local: solo existe aquí dentro
    echo "Dentro:  $mensaje, $nombre"
}
saludar
echo "Fuera:   $mensaje, $nombre"

Al ejecutarlo se ve la diferencia:

Dentro:  hola, Nica
Fuera:   , Nica

Precaución

Declara siempre con local las variables que solo uses dentro de una función. Si te olvidas del local, la variable pasa a ser global sin que te des cuenta y puedes machacar otra que se llame igual.

Script de ejemplo con funciones


#!/bin/bash

# Función
my_function() {
   echo "Hola desde la función"
}
my_function

# Función con parámetros
my_function_with_params() {
   echo "Hola $1"
}
my_function_with_params Nica

# Función con retorno
my_function_with_return() {
   return 1
}
my_function_with_return
echo $?

# Variables globales y locales
name=Nica # global
my_function_2() {
   local msj=", mundo" # local
   echo "Hola $msj $name"
}
echo "Hola desde fuera $msj"

my_function_2

Manejo básico de errores

Códigos de salida

0 Éxito
!=0 Error

#!/bin/bash
cp file.txt ../Course
if [ $? -ne 0 ]; then
    echo "Error al copiar el archivo"
fi
cp file.txt ../Course intenta realizar la copia.

Atajos

Bash permite encadenar comandos según si el anterior fue bien o mal, sin escribir un if entero:

cp file.txt ../Course || echo "Otra vez se ha producido el error"
cp script.sh ../Course && echo "No se ha producido un error"

Script de ejemplo con errores


#!/bin/bash
cp file.txt ../prueba-wsl
if [ $? -ne 0 ]; then
   echo "Error al copiar el archivo"
fi
cp file.txt ../prueba-wsl || echo "Otra vez se ha producido el error, fallo al copiar el archivo"
cp bucles.sh ../prueba-wsl && echo "No se ha producido un error, fichero copiado correctamente"

Ejercicios a realizar en la parte de lógica

  1. Crea un script que pida un número y muestre si es positivo, negativo o cero usando if, elif y else.

  2. Pide al usuario dos números y muestra cuál es mayor o si son iguales.

  3. Crea un script que muestre un menú con tres opciones y ejecute la opción correspondiente según la elección del usuario.

  4. Muestra todos los números del 1 al 10 usando un bucle for.

  5. Crea un script que pida números al usuario hasta que introduzca el número 0. Al final, muestra cuántos números ha introducido en total.

  6. Haz un script que muestre los números del 1 al 10, saltando el 5 y deteniéndose en el 8.

  7. Crea una función saludar que reciba un nombre como argumento y muestre: Hola , bienvenido al script.

  8. Crea una función que reciba dos números, calcule su suma y la devuelva usando return. Muestra el resultado en el script principal.

  9. Intenta copiar un archivo que no exista y muestra un mensaje de error si el comando falla, usando $? o ||.

  10. Crea un script con un comentario inicial con autor, fecha, descripción y un bucle for que liste todos los archivos .sh en el directorio actual.

Soluciones a los ejercicios

Pueden variar según tu sistema y los directorios que tengas.

  1. Crea un script que pida un número y muestre si es positivo, negativo o cero usando if, elif y else.
#!/bin/bash

# Nombre: positivo_negativo.sh

# Descripción: Verifica si un número es positivo, negativo o cero

# Pedir un número al usuario
read -r -p "Introduce un número: " numero

# Verificar si es positivo, negativo o cero
if [ $numero -gt 0 ]; then
    echo "El número $numero es positivo"
elif [ $numero -lt 0 ]; then
    echo "El número $numero es negativo"
else
    echo "El número es cero"
fi
  1. Pide al usuario dos números y muestra cuál es mayor o si son iguales.
#!/bin/bash

# Nombre: comparar_numeros.sh

# Descripción: Compara dos números y muestra cuál es mayor

# Pedir dos números al usuario
read -r -p "Introduce el primer número: " num1
read -r -p "Introduce el segundo número: " num2

# Comparar los números
if [ $num1 -gt $num2 ]; then
    echo "El número $num1 es mayor que $num2"
elif [ $num1 -lt $num2 ]; then
    echo "El número $num2 es mayor que $num1"
else
    echo "Los números son iguales: $num1 = $num2"
fi
  1. Crea un script que muestre un menú con tres opciones y ejecute la opción correspondiente según la elección del usuario.
#!/bin/bash

# Nombre: menu.sh

# Descripción: Muestra un menú con tres opciones

# Mostrar el menú
echo "=== MENÚ PRINCIPAL ==="
echo "1. Mostrar fecha y hora"
echo "2. Listar archivos del directorio"
echo "3. Mostrar información del usuario"
echo "======================="
read -r -p "Elige una opción (1-3): " opcion

# Ejecutar la opción elegida
case $opcion in
    1)
    echo ""
    echo "Fecha y hora actual:"
    date ;;
2)
echo ""
echo "Archivos en el directorio actual:"
ls -lh ;;
3)
echo ""
echo "Información del usuario:"
echo "Usuario: $USER"
echo "Directorio home: $HOME"
echo "Shell actual: $SHELL" ;; *)
echo "Opción inválida. Por favor, elige 1, 2 o 3." ;;
esac
  1. Muestra todos los números del 1 al 10 usando un bucle for.
#!/bin/bash

# Nombre: bucle_for_1_10.sh

# Descripción: Muestra los números del 1 al 10 con bucle for
echo "Números del 1 al 10:"

# Bucle for con secuencia
for i in {1..10}
do
    echo $i
done

# Alternativa 1: usando seq
echo ""
echo "=== Alternativa con seq ==="
for i in $(seq 1 10)
do
    echo $i
done

# Alternativa 2: estilo C
echo ""
echo "=== Alternativa estilo C ==="
for ((i=1; i<=10; i++))
do
    echo $i
done

Crear y ejecutar el script:

nano bucle_for_1_10.sh
chmod +x bucle_for_1_10.sh
./bucle_for_1_10.sh
  1. Crea un script que pida números al usuario hasta que introduzca el número 0. Al final, muestra cuántos números ha introducido en total.
#!/bin/bash

# Nombre: contar_numeros.sh

# Descripción: Pide números hasta que el usuario introduzca 0

# Inicializar contador
contador=0
echo "Introduce números (0 para terminar):"

# Bucle while infinito
while true
do
    read -r -p "Número: " numero

    # Verificar si es 0 para salir
    if [ $numero -eq 0 ]; then
        break
    fi

    # Incrementar contador
    ((contador++))
done

# Mostrar el resultado
    echo ""
    echo "Has introducido $contador números en total."
    Versión alternativa:

    #!/bin/bash
    contador=0
    numero=1 # Inicializar con valor diferente de 0
    echo "Introduce números (0 para terminar):"
    while [ $numero -ne 0 ]
    do
        read -r -p "Número: " numero
        if [ $numero -ne 0 ]; then
            ((contador++))
        fi
    done
    echo "Has introducido $contador números en total."
  1. Haz un script que muestre los números del 1 al 10, saltando el 5 y deteniéndose en el 8.
#!/bin/bash

# Nombre: continue_break.sh

# Descripción: Muestra números del 1 al 10, salta el 5 y se detiene
en el 8
echo "Números del 1 al 10 (saltando el 5 y deteniéndose en el 8):"
for i in {1..10}
do

    # Saltar el número 5
    if [ $i -eq 5 ]; then
        continue
    fi

    # Mostrar el número
    echo $i

    # Detenerse después del 8
    if [ $i -eq 8 ]; then
        break
    fi
done
echo "Bucle terminado."

Versión con más explicación:

#!/bin/bash

# continue_break_explicado.sh
echo "=== Bucle con continue y break ==="
echo "Saltaremos el 5 (continue) y nos detendremos en el 8 (break)"
echo ""
for i in {1..10}
do

    # continue: salta a la siguiente iteración
    if [ $i -eq 5 ]; then
        echo "[Saltando el $i]"
        continue
    fi
    echo "Número: $i"

    # break: sale del bucle completamente
    if [ $i -eq 8 ]; then
        echo "[Deteniéndose en el $i]"
        break
    fi
done
echo ""
echo "Fin del bucle."
  1. Crea una función saludar que reciba un nombre como argumento y muestre: Hola , bienvenido al script.
#!/bin/bash

# Nombre: funcion_saludar.sh

# Descripción: Función que saluda con el nombre proporcionado

    # Definir la función
    saludar() {
        nombre=$1
        echo "Hola $nombre, bienvenido al script."
    }

saludar “Nica”

saludar “Marta”

saludar “Leo”

# Pedir nombre al usuario y saludar
echo ""
read -r -p "Introduce tu nombre: " mi_nombre
saludar "$mi_nombre"

Versión con validación:

#!/bin/bash

# funcion_saludar_validacion.sh

# Función con validación
saludar() {
    if [ -z "$1" ]; then
        echo "Error: No se proporcionó un nombre"
        return 1
    fi
    nombre=$1
    echo "Hola $nombre, bienvenido al script."
}

# Probar la función
saludar "Nica"
saludar "" # Sin argumento
saludar "Leo"
  1. Crea una función que reciba dos números, calcule su suma y la devuelva usando return. Muestra el resultado en el script principal.
#!/bin/bash

# Nombre: funcion_suma.sh

# Descripción: Función que suma dos números y devuelve el resultado

    # Función para sumar
    sumar() {
        local num1=$1
        local num2=$2
        local suma=$((num1 + num2))

        # return solo puede devolver valores entre 0-255

        # Por eso usamos echo para devolver el resultado
            echo $suma
        }

        # Llamar a la función y capturar el resultado
            resultado=$(sumar 5 3)
            echo "La suma de 5 + 3 = $resultado"
            resultado=$(sumar 10 20)
            echo "La suma de 10 + 20 = $resultado"

            # Pedir números al usuario
            read -r -p "Introduce el primer número: " a
            read -r -p "Introduce el segundo número: " b
            resultado=$(sumar $a $b)
            echo "La suma de $a + $b = $resultado"
            Versión alternativa usando variable global:

            #!/bin/bash

            # funcion_suma_return.sh

            # Nota: return en bash solo devuelve códigos de estado (0-255)

            # Para devolver valores, usamos variables globales o echo

            # Versión con variable global
            sumar_global() {
                local num1=$1
                local num2=$2
                RESULTADO=$((num1 + num2))
            }
            echo "=== Usando variable global ==="
            sumar_global 15 25
            echo "Resultado: $RESULTADO"

            # Versión con echo (recomendada)
            sumar_echo() {
                local num1=$1
                local num2=$2
                echo $((num1 + num2))
            }
            echo ""
            echo "=== Usando echo (capturando con \$()) ==="
            resultado=$(sumar_echo 7 8)
            echo "Resultado: $resultado"
  1. Intenta copiar un archivo que no exista y muestra un mensaje de error si el comando falla, usando $? o ||.
#!/bin/bash

# Nombre: manejo_errores.sh

# Descripción: Manejo de errores al copiar archivos
echo "=== Método 1: Usando \$? ==="

# Intentar copiar un archivo que no existe
cp archivo_que_no_existe.txt destino.txt

# Verificar el código de salida
if [ $? -ne 0 ]; then
    echo "Error: No se pudo copiar el archivo"
else
    echo "Archivo copiado exitosamente"
fi
echo ""
echo "=== Método 2: Usando || (OR) ==="

# Si el comando falla (retorna no-cero), ejecuta lo que está después
de || cp otro_archivo_inexistente.txt destino2.txt || echo "Error: El archivo no existe"
echo ""
echo "=== Método 3: Usando && (AND) ==="

# Si el comando tiene éxito (retorna 0), ejecuta lo que está después
de && cp archivo_real.txt copia.txt && echo "Archivo copiado con éxito" || echo "Error al copiar"
echo ""
echo "=== Método 4: Manejo completo ==="
archivo_origen="archivo_inexistente.txt"
archivo_destino="destino.txt"
if [ ! -f "$archivo_origen" ]; then
    echo "Error: El archivo '$archivo_origen' no existe"
    exit 1
fi
cp "$archivo_origen" "$archivo_destino"
if [ $? -eq 0 ]; then
    echo "Archivo copiado exitosamente"
else
    echo "Error al copiar el archivo"
fi
Versión simplificada:

#!/bin/bash

# manejo_errores_simple.sh
echo "Intentando copiar un archivo que no existe..."

# Usando ||
cp archivo_inexistente.txt destino.txt || {
    echo "Error: No se pudo copiar el archivo"
    echo "El archivo probablemente no existe"
}
echo ""
echo "Script finalizado."
  1. Crea un script con un comentario inicial con autor, fecha, descripción y un bucle for que liste todos los archivos .sh en el directorio actual.
#!/bin/bash

# Nombre: listar_scripts.sh

# Descripción: Este script lista todos los archivos con extensión .sh
# que se encuentran en el directorio actual
echo "=== Listado de archivos .sh en el directorio actual ==="
echo "Directorio: $(pwd)"
echo ""

# Contador de archivos
contador=0

# Bucle for para listar archivos .sh
for archivo in *.sh
do

    # Verificar si existen archivos .sh
    if [ -e "$archivo" ]; then
        ((contador++))
        echo "$contador. $archivo"

        # Información adicional del archivo
        tamanio=$(ls -lh "$archivo" | awk '{print $5}')
        echo " Tamaño: $tamanio"

        # Verificar si tiene permisos de ejecución
        if [ -x "$archivo" ]; then
            echo " Permisos: Ejecutable "
        else
            echo " Permisos: No ejecutable"
        fi
        echo ""
    else
        echo "No se encontraron archivos .sh en este directorio"
        break
    fi
done

# Resumen
if [ $contador -gt 0 ]; then
    echo "Total de archivos .sh encontrados: $contador"
else
    echo "Total de archivos .sh encontrados: 0"
fi
echo ""
echo "Fecha de ejecución: $(date '+%d/%m/%Y %H:%M:%S')"

Versión simplificada:

#!/bin/bash

# Script: listar_scripts_simple.sh

# Descripción: Lista archivos .sh en el directorio actual
echo "Archivos .sh en el directorio actual:"
echo ""
for archivo in *.sh
do
    if [ -f "$archivo" ]; then
        echo "- $archivo"
    fi
done