Curso de Bash desde cero / Capítulo 10
Lógica
Operadores aritméticos
Sirven para hacer cuentas. Se usan dentro de $(( )).
+suma-resta*multiplicación/división entera%módulo
Aviso
La división de Bash es entera:
$((7 / 2))da3, no3.5. Si necesitas decimales, hay que recurrir abco aawk.
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
-eqigual-nedistinto-gtmayor-ltmenor-gemayor o igual-lemenor o igual
Comparar cadenas de texto
=igual!=distinto-zcadena vacía-ncadena no vacía>mayor en orden alfabético (solo dentro de[[ ]])<menor en orden alfabético (solo dentro de[[ ]])
Combinar condiciones
&&AND (Y): se tienen que cumplir las dos.||OR (O): basta con que se cumpla una.!NOT (NO): invierte la condición.
Comprobar archivos
-eexiste-fes un archivo regular (contiene datos legibles o binarios y no es un directorio)-des un directorio-rtiene permisos de lectura-wtiene permisos de escritura-xes ejecutable-sexiste y no está vacío
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 comillasAdemá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 conshen 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
fiif con else
if [ condición ]; then
comando_si_verdadero
else
comando_por_defecto
fiif con elif y else
if [ condición ]; then
comando_si_verdadero
elif [ condición ]; then
comando_si_verdadero
fiAviso
Puedes encadenar tantos
elifcomo 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
ficase
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- Cada opción termina en doble punto y coma
(
;;). - El
*)del final es el «en cualquier otro caso», como elelsedelif. Conviene ponerlo siempre para que el script no se quede callado ante una entrada que no esperabas.
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";;
esacTodo 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$num -ge 10→ comprueba que la variable num sea mayor o igual que 10.$num -eq 0→ comprueba que la variable num sea igual a 0.-n "$name"→ comprueba que la cadena asociada a name no está vacía. Fíjate en las comillas: sin ellas, la comprobación daría verdadero siempre, incluso con la variable vacía. Está explicado en el capítulo Comillas y expansión.[ $num -ge 18 ] && [ -n "$name" ]→ comprueba que num sea mayor o igual que 18 y que name no está vacío.-e "./script.sh"→ comprueba que el script existe.
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"
donefor i in 1 2 3 4 5recorre el listado de números definidos, almacenando el valor de cada iteración en la variable i.for name in *.shrecorre el listado de archivos que cumplan el criterio de la extensión, almacenando el valor de cada iteración en la variablename.
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++))
donewhile [ $count -le 5 ]se ejecuta el bucle mientras el valor de la variable count sea menor o igual que 5.((count++))incrementa el valor de count en 1 tras cada ejecución del bucle.
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++))
doneuntil [ $count -gt 10 ]→ se ejecuta el bucle hasta el valor de la variable count sea mayor que 10.((count++))incrementa el valor de count en 1 tras cada ejecución del bucle.
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"
donecontinuesalta la iteración y pasa a la siguiente.breaksale del bucle actual.
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"
doneFunciones
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"
}
saludarsaludar() { ... }es la definición: describe qué hace, pero no la ejecuta.saludara secas es la llamada: ahí sí se ejecuta.
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 NicaDentro 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,
$1ya no es el primer argumento del script, sino el de la función. En cambio$0sigue 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 $? # 0Precaución
returnno 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 1no 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 8Resumiendo:
| 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)
Variables globales: visibles en todo el script.
Variables locales: visibles solo dentro de la función (
local).
#!/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: , Nicalocalsirve para definir variables locales.- Dentro de la función se ven las dos variables: la local y la global.
- Fuera,
$mensajesale vacío: la variable local dejó de existir en cuanto terminó la función.$nombre, que es global, sigue ahí.
Precaución
Declara siempre con
locallas variables que solo uses dentro de una función. Si te olvidas dellocal, 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.- $? -ne 0 accede al código de salida y comprueba si es distinto de cero (error).
Atajos
Bash permite encadenar comandos según si el anterior fue bien o mal,
sin escribir un if entero:
comando1 || comando2ejecuta el segundo solo si el primero falla.comando1 && comando2ejecuta el segundo solo si el primero fue bien.
cp file.txt ../Course || echo "Otra vez se ha producido el error"
cp script.sh ../Course && echo "No se ha producido un error"|| se ejecuta el echo si se produce un error en el cp.
&& se ejecuta el echo si cp se realiza con éxito.
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
Crea un script que pida un número y muestre si es positivo, negativo o cero usando if, elif y else.
Pide al usuario dos números y muestra cuál es mayor o si son iguales.
Crea un script que muestre un menú con tres opciones y ejecute la opción correspondiente según la elección del usuario.
Muestra todos los números del 1 al 10 usando un bucle for.
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.
Haz un script que muestre los números del 1 al 10, saltando el 5 y deteniéndose en el 8.
Crea una función saludar que reciba un nombre como argumento y muestre: Hola
, bienvenido al script. Crea una función que reciba dos números, calcule su suma y la devuelva usando return. Muestra el resultado en el script principal.
Intenta copiar un archivo que no exista y muestra un mensaje de error si el comando falla, usando $? o ||.
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.
- 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- 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- 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- 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
doneCrear y ejecutar el script:
nano bucle_for_1_10.sh
chmod +x bucle_for_1_10.sh
./bucle_for_1_10.sh- 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."- 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."- 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."
}Llamar a la función con diferentes nombres
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"- 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"- 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."- 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