Saltar al contenido principal

🏋️ Ejercicios — Clase 02: Bash Scripting

Practicá todo lo que viste en la Clase 02. Cada ejercicio tiene pistas y solución. Intentá resolverlo solo antes de mirar.

👉 Usá el Playground ROXS en KillerCoda para practicar.


Ejercicio 1 — Calculadora Bash

Nivel: 🟢 Fácil

Creá un script calculadora.sh que:

  1. Pida dos números al usuario con read
  2. Muestre el resultado de: suma, resta, multiplicación y división
  3. Use colores para el resultado (verde si es positivo, rojo si es negativo)

Ejemplo de salida:

🔢 Calculadora Bash
Ingresá el primer número: 15
Ingresá el segundo número: 4
━━━━━━━━━━━━━━━━━━━━
15 + 4 = 19
15 - 4 = 11
15 * 4 = 60
15 / 4 = 3
━━━━━━━━━━━━━━━━━━━━
💡 Pistas
  • Bash solo hace matemática entera: $((A + B))
  • Para colores: \033[0;32m (verde), \033[0;31m (rojo), \033[0m (reset)
  • echo -e interpreta los códigos de color
✅ Solución
#!/bin/bash
VERDE='\033[0;32m'
ROJO='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'

echo -e "${CYAN}🔢 Calculadora Bash${NC}"
read -p "Ingresá el primer número: " A
read -p "Ingresá el segundo número: " B

echo "━━━━━━━━━━━━━━━━━━━━"

SUMA=$((A + B))
RESTA=$((A - B))
MULTI=$((A * B))

echo -e " $A + $B = ${VERDE}$SUMA${NC}"

if [ $RESTA -ge 0 ]; then
echo -e " $A - $B = ${VERDE}$RESTA${NC}"
else
echo -e " $A - $B = ${ROJO}$RESTA${NC}"
fi

echo -e " $A * $B = ${VERDE}$MULTI${NC}"

if [ "$B" -ne 0 ]; then
DIV=$((A / B))
echo -e " $A / $B = ${VERDE}$DIV${NC}"
else
echo -e " $A / $B = ${ROJO}Error: división por cero${NC}"
fi

echo "━━━━━━━━━━━━━━━━━━━━"

Ejercicio 2 — Validador de sitio web

Nivel: 🟢 Fácil

Creá verificar-sitio.sh que reciba un nombre de sitio y verifique:

  1. ¿Existe la carpeta /var/www/SITIO?
  2. ¿Existe index.html dentro?
  3. ¿Nginx está corriendo?
  4. ¿El sitio responde con curl?

Mostrá cada check con ✅ o ❌ y colores.

💡 Pistas
  • [ -d "/ruta" ] verifica si existe un directorio
  • [ -f "/ruta/archivo" ] verifica si existe un archivo
  • systemctl is-active --quiet nginx retorna 0 si está corriendo
  • curl -s -o /dev/null -w "%{http_code}" http://localhost devuelve el código HTTP
✅ Solución
#!/bin/bash
VERDE='\033[0;32m'
ROJO='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'

ok() { echo -e "${VERDE}$1${NC}"; }
fail() { echo -e "${ROJO}$1${NC}"; }

read -p "Nombre del sitio a verificar: " SITIO
RUTA="/var/www/$SITIO"

echo -e "\n${CYAN}🔍 Verificando sitio: $SITIO${NC}\n"

# Check 1: carpeta
if [ -d "$RUTA" ]; then
ok "Carpeta $RUTA existe"
else
fail "Carpeta $RUTA NO existe"
fi

# Check 2: index.html
if [ -f "$RUTA/index.html" ]; then
ok "index.html encontrado"
else
fail "index.html NO encontrado"
fi

# Check 3: Nginx
if systemctl is-active --quiet nginx 2>/dev/null; then
ok "Nginx está corriendo"
else
fail "Nginx NO está corriendo"
fi

# Check 4: respuesta HTTP
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost 2>/dev/null)
if [ "$HTTP_CODE" == "200" ]; then
ok "Sitio responde HTTP 200"
else
fail "Sitio responde HTTP $HTTP_CODE"
fi

echo ""

Ejercicio 3 — Generador de proyectos

Nivel: 🟡 Intermedio

Creá nuevo-proyecto.sh que:

  1. Pida el nombre del proyecto
  2. Pida el autor
  3. Pida elegir un color de tema (1=azul, 2=verde, 3=violeta)
  4. Cree la estructura:
/var/www/PROYECTO/
├── index.html (con el color elegido)
├── css/
│ └── style.css
└── js/
└── app.js
  1. El index.html debe linkear el CSS y JS
  2. El style.css debe tener el color de fondo elegido
  3. El app.js debe hacer un console.log("Proyecto NOMBRE cargado")
💡 Pistas
  • Usá case para elegir el color: case $OPCION in 1) COLOR="#1e3a5f";; esac
  • mkdir -p crea subcarpetas
  • Usá heredocs (cat > archivo << EOF) para cada archivo
✅ Solución
#!/bin/bash
VERDE='\033[0;32m'
CYAN='\033[0;36m'
NC='\033[0m'

echo -e "${CYAN}🚀 Generador de Proyectos Web${NC}\n"

read -p "Nombre del proyecto: " PROYECTO
read -p "Autor: " AUTOR
echo "Elegí un tema de color:"
echo " 1) 🔵 Azul"
echo " 2) 🟢 Verde"
echo " 3) 🟣 Violeta"
read -p "Opción (1/2/3): " OPCION

case $OPCION in
1) COLOR="#1e3a5f"; NOMBRE_COLOR="Azul";;
2) COLOR="#1a5f3a"; NOMBRE_COLOR="Verde";;
3) COLOR="#3a1a5f"; NOMBRE_COLOR="Violeta";;
*) COLOR="#1a1a2e"; NOMBRE_COLOR="Default";;
esac

RUTA="/var/www/$PROYECTO"
mkdir -p "$RUTA/css" "$RUTA/js"

# CSS
cat > "$RUTA/css/style.css" << EOF
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', sans-serif;
background: $COLOR;
color: white;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
h1 { font-size: 3rem; margin-bottom: 0.5rem; }
p { font-size: 1.2rem; opacity: 0.7; }
.badge {
display: inline-block;
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.2);
padding: 0.4rem 1.2rem;
border-radius: 50px;
font-size: 0.85rem;
margin-top: 1rem;
}
EOF

# JS
cat > "$RUTA/js/app.js" << EOF
console.log("Proyecto $PROYECTO cargado");
document.addEventListener('DOMContentLoaded', () => {
console.log("Autor: $AUTOR | Tema: $NOMBRE_COLOR");
});
EOF

# HTML
cat > "$RUTA/index.html" << EOF
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>$PROYECTO</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div>
<h1>🚀 $PROYECTO</h1>
<p>Proyecto generado con Bash</p>
<div class="badge">Por $AUTOR — Tema $NOMBRE_COLOR</div>
</div>
<script src="js/app.js"></script>
</body>
</html>
EOF

echo -e "\n${VERDE}✅ Proyecto creado en $RUTA${NC}"
echo -e "${VERDE} Archivos: index.html, css/style.css, js/app.js${NC}"

Ejercicio 4 — Backup automático

Nivel: 🟡 Intermedio

Creá backup.sh que:

  1. Reciba como argumento el nombre de un sitio (ej: ./backup.sh mi-sitio)
  2. Verifique que la carpeta /var/www/SITIO existe
  3. Cree un backup comprimido en /root/backups/ con formato: SITIO-YYYY-MM-DD-HHMMSS.tar.gz
  4. Muestre el tamaño del backup
  5. Si ya hay más de 3 backups del mismo sitio, borre el más viejo
💡 Pistas
  • $1 es el primer argumento del script
  • date +%Y-%m-%d-%H%M%S genera el timestamp
  • tar -czf destino.tar.gz -C /var/www SITIO comprime
  • du -h archivo muestra el tamaño
  • ls -t ordena por fecha (más nuevo primero)
  • tail -n +4 muestra desde la línea 4 en adelante (para borrar los viejos)
✅ Solución
#!/bin/bash
VERDE='\033[0;32m'
ROJO='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'

exito() { echo -e "${VERDE}$1${NC}"; }
error() { echo -e "${ROJO}$1${NC}"; }
info() { echo -e "${CYAN}ℹ️ $1${NC}"; }

# Verificar argumento
if [ -z "$1" ]; then
error "Uso: ./backup.sh NOMBRE_SITIO"
exit 1
fi

SITIO="$1"
ORIGEN="/var/www/$SITIO"
BACKUP_DIR="/root/backups"
TIMESTAMP=$(date +%Y-%m-%d-%H%M%S)
ARCHIVO="$BACKUP_DIR/${SITIO}-${TIMESTAMP}.tar.gz"

# Verificar que existe
if [ ! -d "$ORIGEN" ]; then
error "La carpeta $ORIGEN no existe"
exit 1
fi

# Crear directorio de backups
mkdir -p "$BACKUP_DIR"

# Comprimir
info "Creando backup de $SITIO..."
tar -czf "$ARCHIVO" -C /var/www "$SITIO"

if [ $? -eq 0 ]; then
TAMANO=$(du -h "$ARCHIVO" | cut -f1)
exito "Backup creado: $ARCHIVO ($TAMANO)"
else
error "Error al crear backup"
exit 1
fi

# Limpiar backups viejos (mantener solo 3)
CANTIDAD=$(ls -1 "$BACKUP_DIR/${SITIO}-"*.tar.gz 2>/dev/null | wc -l)
if [ "$CANTIDAD" -gt 3 ]; then
info "Limpiando backups viejos (hay $CANTIDAD, mantengo 3)..."
ls -t "$BACKUP_DIR/${SITIO}-"*.tar.gz | tail -n +4 | while read VIEJO; do
rm "$VIEJO"
info "Borrado: $(basename $VIEJO)"
done
fi

exito "¡Backup completado!"

Ejercicio 5 — Menú interactivo de administración

Nivel: 🔴 Avanzado

Creá admin.sh, un menú interactivo que permita:

╔═══════════════════════════════════╗
║ 🛠️ Panel de Admin — ROXS ║
╠═══════════════════════════════════╣
║ 1) Ver estado de Nginx ║
║ 2) Listar sitios activos ║
║ 3) Crear nuevo sitio ║
║ 4) Hacer backup de un sitio ║
║ 5) Ver logs de Nginx ║
║ 6) Reiniciar Nginx ║
║ 0) Salir ║
╚═══════════════════════════════════╝

Requisitos:

  • Usá un loop while true para que el menú se repita
  • Cada opción debe ser una función
  • Usá colores
  • La opción 3 debe pedir nombre y crear el sitio completo (HTML + config Nginx)
  • La opción 5 debe mostrar las últimas 20 líneas del log de error
💡 Pistas
  • while true; do ... done para el loop infinito
  • case $OPCION in 1) funcion1;; esac para el menú
  • read -p "Opción: " OPCION para leer la elección
  • tail -20 /var/log/nginx/error.log para los logs
  • ls /etc/nginx/sites-enabled/ para listar sitios activos
✅ Solución
#!/bin/bash
VERDE='\033[0;32m'
ROJO='\033[0;31m'
AMARILLO='\033[1;33m'
CYAN='\033[0;36m'
NEGRITA='\033[1m'
NC='\033[0m'

exito() { echo -e "${VERDE}$1${NC}"; }
error() { echo -e "${ROJO}$1${NC}"; }
info() { echo -e "${CYAN} ℹ️ $1${NC}"; }

mostrar_menu() {
clear
echo -e "${NEGRITA}${CYAN}"
echo "╔═══════════════════════════════════╗"
echo "║ 🛠️ Panel de Admin — ROXS ║"
echo "╠═══════════════════════════════════╣"
echo "║ 1) Ver estado de Nginx ║"
echo "║ 2) Listar sitios activos ║"
echo "║ 3) Crear nuevo sitio ║"
echo "║ 4) Hacer backup de un sitio ║"
echo "║ 5) Ver logs de Nginx ║"
echo "║ 6) Reiniciar Nginx ║"
echo "║ 0) Salir ║"
echo "╚═══════════════════════════════════╝"
echo -e "${NC}"
}

estado_nginx() {
if systemctl is-active --quiet nginx; then
exito "Nginx está CORRIENDO"
else
error "Nginx está DETENIDO"
fi
}

listar_sitios() {
info "Sitios activos en /etc/nginx/sites-enabled/:"
ls -la /etc/nginx/sites-enabled/ 2>/dev/null
}

crear_sitio() {
read -p " Nombre del sitio: " NOMBRE
if [ -z "$NOMBRE" ]; then
error "Nombre vacío"; return
fi

mkdir -p "/var/www/$NOMBRE"
cat > "/var/www/$NOMBRE/index.html" << EOF
<!DOCTYPE html>
<html><head><title>$NOMBRE</title>
<style>body{background:#0f0c29;color:white;display:flex;
align-items:center;justify-content:center;min-height:100vh;
font-family:sans-serif;text-align:center}
h1{font-size:3rem}</style></head>
<body><h1>🚀 $NOMBRE</h1></body></html>
EOF

cat > "/etc/nginx/sites-available/$NOMBRE" << EOF
server {
listen 80;
server_name _;
root /var/www/$NOMBRE;
index index.html;
location / { try_files \$uri \$uri/ =404; }
}
EOF

ln -sf "/etc/nginx/sites-available/$NOMBRE" /etc/nginx/sites-enabled/
chown -R www-data:www-data "/var/www/$NOMBRE"
nginx -t 2>/dev/null && systemctl reload nginx
exito "Sitio '$NOMBRE' creado y activado"
}

hacer_backup() {
read -p " Nombre del sitio: " NOMBRE
if [ ! -d "/var/www/$NOMBRE" ]; then
error "Sitio no encontrado"; return
fi
mkdir -p /root/backups
ARCHIVO="/root/backups/${NOMBRE}-$(date +%Y%m%d-%H%M%S).tar.gz"
tar -czf "$ARCHIVO" -C /var/www "$NOMBRE"
TAMANO=$(du -h "$ARCHIVO" | cut -f1)
exito "Backup: $ARCHIVO ($TAMANO)"
}

ver_logs() {
info "Últimas 20 líneas de error.log:"
echo ""
tail -20 /var/log/nginx/error.log 2>/dev/null || echo " (sin logs)"
}

reiniciar_nginx() {
if nginx -t 2>/dev/null; then
systemctl restart nginx
exito "Nginx reiniciado"
else
error "Config inválida, no se reinició"
fi
}

# Loop principal
while true; do
mostrar_menu
read -p " Opción: " OPCION
echo ""

case $OPCION in
1) estado_nginx;;
2) listar_sitios;;
3) crear_sitio;;
4) hacer_backup;;
5) ver_logs;;
6) reiniciar_nginx;;
0) echo -e "${VERDE} ¡Hasta la próxima! 🚀${NC}"; exit 0;;
*) error "Opción inválida";;
esac

echo ""
read -p " Presioná Enter para continuar..."
done

Ejercicio 6 — Script con argumentos y flags

Nivel: 🔴 Avanzado

Creá deploy-pro.sh que funcione con argumentos en vez de preguntas interactivas:

./deploy-pro.sh --nombre mi-sitio --autor "ROXS" --puerto 8080
./deploy-pro.sh -n mi-sitio -a "ROXS" -p 8080
./deploy-pro.sh --help

Requisitos:

  • Soporte flags cortos (-n) y largos (--nombre)
  • --help muestra el uso
  • Si falta --nombre, mostrar error
  • Puerto por defecto: 80
  • Crear el sitio completo con Nginx en el puerto indicado
💡 Pistas
  • Usá while [ $# -gt 0 ] para recorrer argumentos
  • case "$1" in --nombre|-n) NOMBRE="$2"; shift 2;; esac
  • shift avanza al siguiente argumento
  • ${VARIABLE:-valor_default} usa un valor por defecto
✅ Solución
#!/bin/bash
VERDE='\033[0;32m'
ROJO='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'

exito() { echo -e "${VERDE}$1${NC}"; }
error() { echo -e "${ROJO}$1${NC}"; }

mostrar_ayuda() {
echo -e "${CYAN}🚀 Deploy Pro — Uso:${NC}"
echo ""
echo " ./deploy-pro.sh [opciones]"
echo ""
echo " -n, --nombre Nombre del sitio (requerido)"
echo " -a, --autor Nombre del autor (default: ROXS)"
echo " -p, --puerto Puerto (default: 80)"
echo " -h, --help Mostrar esta ayuda"
echo ""
echo " Ejemplo: ./deploy-pro.sh -n mi-web -a Carlos -p 8080"
}

# Defaults
NOMBRE=""
AUTOR="ROXS"
PUERTO=80

# Parsear argumentos
while [ $# -gt 0 ]; do
case "$1" in
-n|--nombre) NOMBRE="$2"; shift 2;;
-a|--autor) AUTOR="$2"; shift 2;;
-p|--puerto) PUERTO="$2"; shift 2;;
-h|--help) mostrar_ayuda; exit 0;;
*) error "Argumento desconocido: $1"; mostrar_ayuda; exit 1;;
esac
done

# Validar
if [ -z "$NOMBRE" ]; then
error "Falta --nombre"
mostrar_ayuda
exit 1
fi

RUTA="/var/www/$NOMBRE"

echo -e "${CYAN}🚀 Desplegando: $NOMBRE (puerto $PUERTO)${NC}\n"

# Instalar Nginx si no está
command -v nginx &>/dev/null || {
apt update -qq && apt install nginx -y -qq
}

# Crear sitio
mkdir -p "$RUTA"
cat > "$RUTA/index.html" << EOF
<!DOCTYPE html>
<html><head><title>$NOMBRE</title>
<style>body{background:linear-gradient(135deg,#0f0c29,#302b63);
color:white;display:flex;align-items:center;justify-content:center;
min-height:100vh;font-family:sans-serif;text-align:center}
h1{font-size:3rem}p{opacity:.7;font-size:1.2rem}
.info{margin-top:1rem;font-size:.85rem;opacity:.4}</style></head>
<body><div><h1>🚀 $NOMBRE</h1><p>Por $AUTOR</p>
<p class="info">Puerto $PUERTO — Deploy automático</p></div></body></html>
EOF

# Config Nginx
cat > "/etc/nginx/sites-available/$NOMBRE" << EOF
server {
listen $PUERTO;
server_name _;
root $RUTA;
index index.html;
location / { try_files \$uri \$uri/ =404; }
}
EOF

ln -sf "/etc/nginx/sites-available/$NOMBRE" /etc/nginx/sites-enabled/
chown -R www-data:www-data "$RUTA"

if nginx -t 2>/dev/null; then
systemctl reload nginx
exito "Sitio desplegado en http://localhost:$PUERTO"
else
error "Error en la configuración de Nginx"
exit 1
fi

🏆 Desafío extra

Combiná los ejercicios 4 y 5: agregá al menú de admin una opción que muestre un "dashboard" con:

  • Cantidad de sitios activos
  • Espacio total usado por /var/www/
  • Último backup realizado
  • Estado de Nginx (corriendo/detenido + uptime)

Todo formateado con colores y bordes tipo box.


🔗 Recursos