🏋️ 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:
- Pida dos números al usuario con
read - Muestre el resultado de: suma, resta, multiplicación y división
- 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 -einterpreta 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:
- ¿Existe la carpeta
/var/www/SITIO? - ¿Existe
index.htmldentro? - ¿Nginx está corriendo?
- ¿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 archivosystemctl is-active --quiet nginxretorna 0 si está corriendocurl -s -o /dev/null -w "%{http_code}" http://localhostdevuelve 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:
- Pida el nombre del proyecto
- Pida el autor
- Pida elegir un color de tema (1=azul, 2=verde, 3=violeta)
- Cree la estructura:
/var/www/PROYECTO/
├── index.html (con el color elegido)
├── css/
│ └── style.css
└── js/
└── app.js
- El
index.htmldebe linkear el CSS y JS - El
style.cssdebe tener el color de fondo elegido - El
app.jsdebe hacer unconsole.log("Proyecto NOMBRE cargado")
💡 Pistas
- Usá
casepara elegir el color:case $OPCION in 1) COLOR="#1e3a5f";; esac mkdir -pcrea 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:
- Reciba como argumento el nombre de un sitio (ej:
./backup.sh mi-sitio) - Verifique que la carpeta
/var/www/SITIOexiste - Cree un backup comprimido en
/root/backups/con formato:SITIO-YYYY-MM-DD-HHMMSS.tar.gz - Muestre el tamaño del backup
- Si ya hay más de 3 backups del mismo sitio, borre el más viejo
💡 Pistas
$1es el primer argumento del scriptdate +%Y-%m-%d-%H%M%Sgenera el timestamptar -czf destino.tar.gz -C /var/www SITIOcomprimedu -h archivomuestra el tamañols -tordena por fecha (más nuevo primero)tail -n +4muestra 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 truepara 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 ... donepara el loop infinitocase $OPCION in 1) funcion1;; esacpara el menúread -p "Opción: " OPCIONpara leer la eleccióntail -20 /var/log/nginx/error.logpara los logsls /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) --helpmuestra 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;; esacshiftavanza 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
- Clase 02 — Bash Scripting
- Playground ROXS
- ShellCheck — Validá tus scripts online
- ExplainShell — Entendé cualquier comando
- ▶️ Playlist del Bootcamp