11. [Reto] Bash Incremental: Aprender Shell a Golpes de Ejemplos

En esta práctica trabajarás con una colección de scripts en Bash orientados a tareas básicas de automatización y administración en Linux. El objetivo es que ejecutes y analices cada uno de los scripts en tu terminal para comprender su funcionamiento y el efecto que producen.

Tareas a realizar

  1. Copia todos los scripts proporcionados en tu máquina Linux (física o virtual).
  2. Asegúrate de que cada script tenga permisos de ejecución usando: chmod +x nombre_script.sh
  3. Ejecuta cada script desde la terminal y observa el resultado.
  4. Modifica al menos 3 scripts para adaptarlos a una variante que tenga sentido. Por ejemplo:
    • Cambiar rutas
    • Cambiar comandos
    • Añadir opciones
    • Añadir colores o logs

La línea del “shebang” (#!)

#!/bin/bash
#!/usr/bin/env bash

La diferencia tiene que ver con flexibilidad vs. certeza.

Cuando pones #!/bin/bash estás diciendo “usa Bash que está en /bin/bash”.
Eso asume que la ruta es exactamente esa. En muchas distros Linux es verdad, en macOS a veces también, pero en otras no. Por ejemplo, en sistemas BSD, en instalaciones personalizadas, o si manejas múltiples versiones de Bash instaladas desde brew, nix, etc., /bin/bash puede no existir o no ser el Bash que quieres.

La alternativa #!/usr/bin/env bash juega una carta distinta:
env busca ejecutables en el PATH del usuario, encuentra dónde está bash y lo lanza.
Es como decir: “búscame un Bash válido en la ruta del sistema y úsalo”.
Eso lo vuelve más portátil y adaptable, sobre todo en entornos raros, macOS moderno, contenedores, virtual environments y setups educativos donde Bash no está donde uno espera.

El resumen práctico:

#!/bin/bash = certero pero rígido. Si no existe esa ruta, falla.
#!/usr/bin/env bash = más portable y respetuoso del PATH, útil cuando no conoces el entorno.

Ejemplo 1: “Hola, soy un script”
Propósito: variables y echo.

#!/usr/bin/env bash

nombre="Antonio"
echo "Hola, $nombre"
echo "Estoy en el directorio: $(pwd)"

Concepto nuevo: variables y comandos incrustados $(...).


Ejemplo 2: “Si me pasas un nombre, lo uso”
Propósito: argumentos $1.

#!/usr/bin/env bash

if [ -z "$1" ]; then
  echo "Uso: $0 <nombre>"
  exit 1
fi

echo "Hola, $1"

Concepto nuevo: if mínimo, chequeo de argumentos y exit.


Ejemplo 3: “Saludo a varios”
Propósito: recorrer argumentos (for).

#!/usr/bin/env bash

if [ "$#" -eq 0 ]; then
  echo "Uso: $0 nombre1 nombre2 ..."
  exit 1
fi

for persona in "$@"; do
  echo "Hola, $persona"
done

Concepto nuevo: for y "$@" (todos los argumentos).


Ejemplo 4: “Saludo según hora”
Propósito: condicionales compuestos.

#!/usr/bin/env bash

hora=$(date +%H)

if [ "$hora" -lt 12 ]; then
  saludo="Buenos días"
elif [ "$hora" -lt 20 ]; then
  saludo="Buenas tardes"
else
  saludo="Buenas noches"
fi

echo "$saludo, $(whoami)"

Concepto nuevo: elif, comparaciones numéricas y uso de comandos para info del sistema.


Pequeño ejemplo 5: “Función que loguea cosas”
Propósito: funciones y reutilización.

#!/usr/bin/env bash

log(){
  echo "[$(date +%H:%M:%S)] $1"
}

log "Iniciando proceso"
sleep 1
log "Proceso terminado"

Concepto nuevo: funciones y timestamps.


Pequeño ejemplo 6: “Descargo URLs de una lista”
Propósito: arrays, loops y llamar programas externos.

Archivo urls.txt de ejemplo:

https://example.org
https://wikipedia.org

Script:

#!/usr/bin/env bash

while read -r url; do
  [ -z "$url" ] && continue
  echo "Descargando $url"
  curl -s -O "$url"
done < urls.txt

Concepto nuevo: while read, redirección y pequeñas automatizaciones.


Ejemplo 7: “Contador de líneas de código por extensión”
Propósito: bucles, condicionales, comandos, pipes.

#!/usr/bin/env bash

if [ -z "$1" ]; then
  echo "Uso: $0 <directorio>"
  exit 1
fi

dir="$1"
for ext in sh py js php; do
  total=$(find "$dir" -type f -name "*.$ext" -print0 | xargs -0 cat 2>/dev/null | wc -l)
  echo "$ext: $total líneas"
done

Concepto nuevo: usar herramientas externas y procesar resultados.


Ejemplo 8: “Menú interactivo mínimo”
Propósito: select y control básico de flujo.

#!/usr/bin/env bash

select opcion in Fecha Usuario Salir; do
  case "$opcion" in
    Fecha) date ;;
    Usuario) whoami ;;
    Salir) echo "Adiós"; break ;;
    *) echo "Opción no válida" ;;
  esac
done

Concepto nuevo: select + case.


Pequeño ejemplo 9: “Backup comprimido con timestamp”
Propósito: combinación de todo para algo útil.

#!/usr/bin/env bash

origen="$1"
destino="$2"

if [ -z "$origen" ] || [ -z "$destino" ]; then
  echo "Uso: $0 <directorio_origen> <directorio_destino>"
  exit 1
fi

fecha=$(date +%Y%m%d_%H%M%S)
archivo="$destino/backup_$fecha.tar.gz"

echo "Creando backup de $origen en $archivo..."
tar -czf "$archivo" "$origen"
echo "Backup creado."

Ejemplo 10: Crear estructura de carpetas de proyecto

Idea: Montar la estructura típica de un proyecto sin ir haciendo mkdir a mano.

#!/usr/bin/env bash

if [ -z "$1" ]; then
  echo "Uso: $0 <nombre_proyecto>"
  exit 1
fi

PROYECTO="$1"

mkdir -p "$PROYECTO"/{src,bin,logs,tmp,docs}

echo "Proyecto creado en: $PROYECTO"
ls -R "$PROYECTO"

Ejemplo 11: Buscar una palabra en todos los .log de un directorio

Idea: Mini-grep para revisar logs del sistema o de una app.

#!/usr/bin/env bash

DIR="${1:-/var/log}"
CADENA="$2"

if [ -z "$CADENA" ]; then
  echo "Uso: $0 <directorio_logs> <cadena_a_buscar>"
  echo "Ejemplo: $0 /var/log error"
  exit 1
fi

echo "Buscando '$CADENA' en *.log de $DIR..."
grep -Rni --include="*.log" "$CADENA" "$DIR"

Ejemplo 12: Top 5 archivos más grandes de un directorio

Idea: Visualizar qué archivos se están comiendo el disco.

#!/usr/bin/env bash

DIR="${1:-.}"

echo "Top 5 archivos más grandes en: $DIR"
du -ah "$DIR" 2>/dev/null | sort -h | tail -n 5

Ejemplo 13: Aviso sencillo de uso de disco

Idea: Script que comprueba / y avisa si pasa de cierto porcentaje.

#!/usr/bin/env bash

LIMITE=80  # porcentaje

uso=$(df -h / | awk 'NR==2 {gsub("%","",$5); print $5}')

echo "Uso actual de / : $uso%"

if [ "$uso" -gt "$LIMITE" ]; then
  echo "⚠ Atención: uso de disco por encima de ${LIMITE}%"
else
  echo "Todo OK, por debajo de ${LIMITE}%"
fi

Ejemplo 14: Copia rápida de syslog con timestamp

Idea: Guardar copia de seguridad de un log del sistema en el HOME del usuario.

#!/usr/bin/env bash

ORIGEN="/var/log/syslog"
DEST="$HOME"

if [ ! -f "$ORIGEN" ]; then
  echo "No existe $ORIGEN (en algunas distros es /var/log/messages)"
  exit 1
fi

FECHA=$(date +%Y%m%d_%H%M%S)
COPIA="$DEST/syslog_$FECHA.log"

cp "$ORIGEN" "$COPIA"

echo "Copia creada en: $COPIA"

Ejemplo 15: Ping-monitor cutre pero efectivo

Idea: Comprobar conectividad a un host cada pocos segundos.

#!/usr/bin/env bash

HOST="${1:-8.8.8.8}"

echo "Monitoreando ping a $HOST (Ctrl+C para salir)"

while true; do
  if ping -c 1 -W 1 "$HOST" >/dev/null 2>&1; then
    echo "[$(date +%H:%M:%S)] $HOST está accesible"
  else
    echo "[$(date +%H:%M:%S)] ⚠ $HOST NO responde"
  fi
  sleep 3
done

Ejemplo 16: Listar usuarios del sistema con su shell

Idea: Leer /etc/passwd y mostrar usuario + shell. Tocado sistema pero muy didáctico.

#!/usr/bin/env bash

echo "Usuarios del sistema y su shell:"
echo "--------------------------------"

while IFS=: read -r user _ _ _ _ _ shell; do
  echo "$user -> $shell"
done < /etc/passwd

Ejemplo 17: Comprobar si un servicio está activo (systemd)

Idea: Ver el estado de un servicio con systemctl.

#!/usr/bin/env bash

if [ -z "$1" ]; then
  echo "Uso: $0 <nombre_servicio>"
  echo "Ejemplo: $0 apache2"
  exit 1
fi

SERV="$1"

estado=$(systemctl is-active "$SERV" 2>/dev/null)

if [ "$estado" = "active" ]; then
  echo "✅ El servicio $SERV está ACTIVO"
elif [ "$estado" = "inactive" ]; then
  echo "⏸ El servicio $SERV está INACTIVO"
else
  echo "❓ El servicio $SERV no existe o no usa systemd (estado: $estado)"
fi

Ejemplo 18: Renombrar archivos añadiendo un prefijo

Idea: Jugar con nombres de archivos y bucles. Muy útil para fotos, prácticas, etc.

#!/usr/bin/env bash

if [ "$#" -lt 2 ]; then
  echo "Uso: $0 <prefijo> <archivos...>"
  echo "Ejemplo: $0 practica1 *.txt"
  exit 1
fi

PREFIJO="$1"
shift

for fichero in "$@"; do
  if [ -f "$fichero" ]; then
    nuevo="${PREFIJO}_$fichero"
    mv "$fichero" "$nuevo"
    echo "$fichero -> $nuevo"
  else
    echo "Saltando $fichero (no es un archivo)"
  fi
done

Ejemplo 19: Menú para ver info básica del sistema

Idea: Un menú simple que llame a varios comandos de sistema.

#!/usr/bin/env bash

mostrar_sistema() {
  echo "Sistema:"
  uname -a
}

mostrar_cpu() {
  echo "CPU:"
  lscpu | head -n 10
}

mostrar_mem() {
  echo "Memoria:"
  free -h
}

mostrar_disks() {
  echo "Discos:"
  df -h
}

PS3="Elige una opción (1-5): "

select opcion in "Info sistema" "CPU" "Memoria" "Discos" "Salir"; do
  case "$REPLY" in
    1) mostrar_sistema ;;
    2) mostrar_cpu ;;
    3) mostrar_mem ;;
    4) mostrar_disks ;;
    5) echo "Adiós"; break ;;
    *) echo "Opción no válida" ;;
  esac
done