¿Cómo aprender scripting básico en shell?

Aprender Certificado Fundación Linux en España contenido Técnicos desde Cero en CiberED

Un script de shell es un archivo de texto que contiene una serie de comandos que el intérprete ejecuta secuencialmente.

10.1. ¿Por qué scripting en shell?

Si quieres ser un administrador de sistemas, el programar pequeños scripts significa:

  • Automatizar tareas repetitivas (copias de seguridad, rotación de logs, creación masiva de usuarios).
  • Combinar herramientas ya conocidas (grep, sed, awk, find) para resolver problemas complejos.
  • Reducir errores humanos al estandarizar procedimientos.
  • Cumplir con los objetivos del examen LFCS, donde se pueden pedir scripts sencillos que realicen determinadas operaciones.

En este capítulo aprenderás a escribir, depurar y ejecutar scripts en Bash, el intérprete por defecto en la mayoría de distribuciones.

10.2. El primer script: estructura y ejecución

10.2.1. El shebang

Todo script comienza con una línea especial que indica qué intérprete usar. Esta línea se conoce como shebang y tiene el formato:

#!/bin/bash

Puede ser también #!/usr/bin/env bash para mayor portabilidad. Sin ella, el script se ejecutará con el shell actual del usuario.

10.2.2. Crear y ejecutar un script

1. Crea un archivo con extensión .sh (aunque no es obligatoria):

nano mi_script.sh

2. Escribe:

#!/bin/bash
echo "Hola, mundo!"
echo "Hoy es $(date)"

3. Dale permisos de ejecución:

chmod +x mi_script.sh

4. Ejecuta:

./mi_script.sh

También se puede ejecutar con bash mi_script.sh sin necesidad de permisos de ejecución, pero entonces no es necesario el shebang.

10.3. Variables y parámetros

10.3.1. Variables de usuario

Se asignan sin espacios alrededor del =:

nombre="Juan"
edad=25

Para usar su valor, se antepone $:

echo "Me llamo $nombre y tengo $edad años."

Si el nombre de la variable puede confundirse con texto adyacente, se usan llaves:

echo "Archivo: ${nombre}.txt"

10.3.2. Variables de entorno

Son heredadas por los procesos hijos. Algunas importantes:

  • $HOME, $USER, $PATH, $PWD, $UID
  • Pueden usarse directamente: echo "Tu home es $HOME"

Para crear una variable de entorno, se exporta:

export MI_VAR="valor"

Normalmente se definen en ~/.bashrc o /etc/profile.

10.3.3. Sustitución de comandos

Permite capturar la salida de un comando y asignarla a una variable o usarla en otra orden.

fecha=$(date +%F)
echo "La fecha es $fecha"

También se puede usar la sintaxis con acentos graves (obsoleta pero aún funcional):

fecha=`date +%F`

10.3.4. Parámetros posicionales

Son los argumentos que se pasan al script:

  • $0 : nombre del script.
  • $1, $2, … : primer argumento, segundo, etc.
  • $# : número de argumentos.
  • $@ : todos los argumentos como lista separada.
  • $* : todos los argumentos como una sola cadena (atención al manejo de espacios).
  • $? : código de salida del último comando ejecutado.
  • $$ : PID del script actual.

Ejemplo:

#!/bin/bash
echo "Script: $0"
echo "Primer argumento: $1"
echo "Número de argumentos: $#"
echo "Todos los argumentos: $@"

Si se ejecuta ./script.sh hola mundo, se obtiene:

Script: ./script.sh
Primer argumento: hola
Número de argumentos: 2
Todos los argumentos: hola mundo

10.3.5. Lectura interactiva: read

Pide datos al usuario durante la ejecución:

echo -n "Introduce tu nombre: "
read nombre
echo "Hola, $nombre"

Se pueden usar opciones como -p (prompt) y -s (silencioso, para contraseñas):

read -p "Usuario: " usuario
read -sp "Contraseña: " password
echo

10.4. Operaciones aritméticas

Bash soporta aritmética entera. La forma más limpia es con $((...)):

a=10
b=3
suma=$((a + b))
resto=$((a % b))
echo "Suma: $suma, Resto: $resto"

Incrementos y decrementos:

contador=0
((contador++))
echo $contador

10.5. Estructuras de control

10.5.1. Comprobaciones con test y [ ]

El comando test (o sus equivalentes [ ] y [[ ]]) evalúa expresiones y devuelve un código de salida (0 verdadero, 1 falso).

Comparaciones numéricas:

  • -eq (equal), -ne (not equal), -gt (greater than), -lt, -ge, -le.

Comparaciones de cadenas:

  • = : iguales.
  • != : distintas.
  • -z cadena : longitud cero (vacía).
  • -n cadena : longitud no cero.

Comprobaciones de archivos:

  • -e archivo : existe.
  • -f archivo : es un archivo regular.
  • -d archivo : es un directorio.
  • -r, -w, -x : legible, escribible, ejecutable.
  • -s archivo : tamaño > 0.

Operadores lógicos:

  • !, -a (AND), -o (OR) dentro de [ ].
  • Con [[ ]] se pueden usar &&, || y () para agrupar.

Ejemplos:

if [ "$a" -gt "$b" ]; then
   echo "a es mayor que b"
fi

if [ -f /etc/passwd ] && [ -r /etc/passwd ]; then
   echo "passwd existe y es legible"
fi

Ventaja de [[ ]]: es más potente (evita problemas de splitting, permite expresiones regulares con =~). En exámenes LFCS, [ ] es suficiente y clásico.

10.5.2. if / then / elif / else / fi

Estructura:

if condicion1; then
   comandos
elif condicion2; then
   comandos
else
   comandos
fi

Ejemplo: determinar si un número es positivo, negativo o cero.

#!/bin/bash
read -p "Introduce un número: " num
if [ $num -gt 0 ]; then
   echo "Positivo"
elif [ $num -lt 0 ]; then
   echo "Negativo"
else
   echo "Cero"
fi

10.5.3. case

Ideal para múltiples opciones discretas.

case $variable in
   patrón1)
      comandos;;
   patrón2)
      comandos;;
   *)
      comandos por defecto;;
esac

Ejemplo: menú simple.

read -p "Elige (start|stop|restart): " opcion
case $opcion in
   start)
      echo "Iniciando...";;
   stop)
      echo "Deteniendo...";;
   restart)
      echo "Reiniciando...";;
   *)
      echo "Opción no válida";;
esac

10.6. Bucles

10.6.1. for

Recorre una lista:

for variable in lista; do
   comandos
done

La lista puede ser literal (1 2 3), una expansión ({1..5}, *.txt), o la salida de un comando.

Ejemplos:

# Listar archivos .txt
for archivo in *.txt; do
   echo "Procesando $archivo"
done

# Contar del 1 al 5
for i in {1..5}; do
   echo "Número $i"
done

También se puede usar el formato C-like: for ((i=1; i<=5; i++)); do ... done

10.6.2. while

Ejecuta mientras la condición sea verdadera.

while [ condición ]; do
   comandos
done

Ejemplo: leer un archivo línea por línea.

while IFS= read -r linea; do
   echo "Línea: $linea"
done < archivo.txt

Cuidado con bucles infinitos. Asegurarse de que la condición se vuelva falsa.

10.6.3. until

Lo mismo que while pero ejecuta mientras la condición sea falsa.

until [ condición ]; do
   comandos
done

10.6.4. Control de bucles: break y continue

  • break sale del bucle.
  • continue salta a la siguiente iteración.

10.7. Funciones

Agrupan código reutilizable. Se definen antes de usarse.

nombre_funcion() {
   comandos
   return valor   # opcional, 0-255
}

O bien:

function nombre_funcion {
   comandos
}

Dentro de la función, los argumentos se reciben como $1, $2, etc. (son locales a la función).

Ejemplo:

#!/bin/bash
saludar() {
   echo "Hola, $1"
}
saludar "Mundo"
saludar "Juan"

Variables locales: dentro de una función, usar local para no contaminar el entorno global.

mi_funcion() {
   local var="solo dentro"
   echo $var
}

10.8. Manejo de archivos y streams en scripts

Se puede redirigir la salida de todo un bloque:

{
   echo "Inicio"
   comando1
   comando2
} > salida.log 2>&1

O usar descriptores de archivo personalizados para escritura/lectura avanzada, aunque no es imprescindible para el nivel LFCS.

Lectura de archivos de configuración: a menudo se leen variables desde un archivo con source o .:

. /etc/mi_script.conf
source /etc/mi_script.conf

Esto ejecuta el archivo como si su contenido estuviera en el script actual.

10.9. Salida y control de errores

10.9.1. Códigos de salida

Todo comando devuelve un código de salida (0 éxito, 1-255 error). Se captura con $?.

En scripts, debemos salir con un código significativo:

exit 0   # éxito
exit 1   # error genérico

Es buena práctica comprobar el éxito de los comandos críticos:

if ! comando; then
   echo "Error al ejecutar comando" >&2
   exit 1
fi

O de forma más compacta: comando || { echo "Error"; exit 1; }

10.9.2. set para depuración y robustez

Al principio del script se pueden activar opciones de bash:

  • set -e : sale del script si un comando falla (excepto en condiciones o hasta que se desactive). Útil pero con matices.
  • set -u : trata las variables no definidas como error.
  • set -o pipefail : si un comando en una tubería falla, la tubería devuelve ese código de error (por defecto solo el último).
  • set -x : imprime cada comando tras expandirlo (depuración).
  • set -v : imprime cada línea antes de ejecutarse (más detalle).

Combinación típica:

set -euo pipefail

Pero set -e puede ser problemático. Alternativamente, manejar errores manualmente.

10.9.3. trap para limpieza

Permite ejecutar código cuando el script recibe una señal (incluso salida normal). Útil para eliminar archivos temporales.

trap 'echo "Saliendo..."; rm -f /tmp/tempfile' EXIT

Señales comunes: EXIT (salida normal), INT (Ctrl+C), TERM, HUP.

10.10. Depuración de scripts

Además de set -x, se puede depurar por partes:

  • Insertar echo mostrando valores de variables.
  • Comentar bloques para aislar problemas.
  • Ejecutar el script con bash -x script.sh (sin modificar el archivo).

Ejemplo:

bash -x mi_script.sh

Esto muestra cada línea precedida de + y con las variables expandidas.

Problemas comunes:

  • Espacios en asignaciones (var = valor en lugar de var=valor).
  • No escapar variables en comillas ("$var") cuando contienen espacios.
  • Uso de [ ] con operadores no soportados (ej. [ $a -gt $b ] si variables vacías dan error). Solución: entrecomillar [ "$a" -gt "$b" ] o usar [[ ]].
  • Permisos de ejecución.
  • Falsos amigos con rutas (rutas relativas vs absolutas).

10.11. Ejemplos prácticos de administración

10.11.1. Copia de seguridad de un directorio

#!/bin/bash
set -uo pipefail

origen="${1:-/home}"
destino="/backup/$(date +%F)"
mkdir -p "$destino"
tar -czf "$destino/home_backup.tar.gz" "$origen" && \
echo "Copia creada en $destino" || \
echo "Error en la copia" >&2

10.11.2. Creación masiva de usuarios desde un CSV

Supongamos usuarios.csv con formato nombre,apellido.

#!/bin/bash
while IFS=',' read -r nombre apellido; do
   usuario="${nombre,,}.${apellido,,}"  # minúsculas, separado por punto
   if id "$usuario" &>/dev/null; then
      echo "Usuario $usuario ya existe"
   else
      useradd -m -c "$nombre $apellido" "$usuario"
      echo "Creado $usuario"
   fi
done < usuarios.csv

10.11.3. Monitorización simple de espacio en disco

#!/bin/bash
umbral=80
particiones=$(df -h | awk 'NR>1 && $5+0 > '"$umbral"' {print $1, $5}')
if [ -n "$particiones" ]; then
   echo "¡Atención! Particiones por encima del $umbral%:"
   echo "$particiones"
else
   echo "Todo bajo control"
fi

10.11.4. Rotación de logs casera

#!/bin/bash
log="/var/log/mi_app.log"
if [ -f "$log" ] && [ $(stat --format=%s "$log") -gt 1048576 ]; then
   mv "$log" "$log.$(date +%Y%m%d)"
   touch "$log"
   echo "Log rotado"
fi

10.12. Ejercicios prácticos

  1. Calculadora simple: Crea un script que reciba dos números y una operación (suma, resta, multiplicación, división) y muestre el resultado. Debe validar el número de argumentos y que no se divida por cero.

  2. Validación de entrada: Script que pregunte una IP y compruebe si es una IPv4 válida (4 octetos entre 0 y 255 separados por puntos). Pista: usar grep con expresión regular.

  3. Monitor de servicios: Escribe un script que lea un archivo de configuración con nombres de servicios (uno por línea). Para cada servicio, compruebe si está activo (con systemctl is-active) y si no, intente iniciarlo y registre el evento en un log.

  4. Backup rotatorio: Crea un script que haga backup de /etc en /backups/etc-YYYYMMDD.tar.gz y conserve solo los últimos 7 backups, borrando los más antiguos.

  5. Menú interactivo: Usa case y un bucle infinito para ofrecer un menú con opciones: 1) Mostrar fecha, 2) Listar usuarios conectados, 3) Espacio en disco, 4) Salir. Usa read para la elección.

  6. Depuración: Toma un script de los ejercicios anteriores, introduce intencionadamente un error (por ejemplo, variable sin definir o redirección incorrecta). Utiliza set -x y bash -x para localizarlo y corregirlo.

10.13. Resumen y consejos LFCS

  • La estructura básica de un script (shebang, variables, condicionales, bucles, funciones) debe ser memoria muscular.
  • Practica la diferencia entre [ ] y [[ ]], y cuándo entrecomillar variables.
  • Controla los códigos de salida y usa trap para scripts robustos.
  • En el examen, te pueden pedir que escribas un pequeño script que automatice una tarea; la simplicidad y la claridad puntúan más que la complejidad innecesaria.
  • Siempre prueba los scripts con diferentes entradas, incluidos casos límite (archivos inexistentes, espacios en nombres).
  • Los conocimientos de este capítulo se integran con todo lo anterior: grep, sed, awk, permisos y variables de entorno. Un buen script es la combinación de todas esas herramientas.

Has completado los fundamentos de scripting en shell. En el próximo capítulo nos adentraremos en el almacenamiento avanzado con LVM, donde aplicarás muchos de estos conceptos para automatizar la gestión de volúmenes lógicos.

Vistas: 0

Descubre más desde CIBERED

Suscríbete y recibe las últimas entradas en tu correo electrónico.

Scroll al inicio