En la Parte I descubriste la shell y sus fundamentos; en la Parte II aprendiste a programar con condicionales, bucles, funciones y procesamiento de texto. Esta tercera y última parte te lleva al nivel profesional: expresiones regulares nativas, manipulación de cadenas sin comandos externos, here documents, subshells, descriptores de archivo, menús interactivos y un proyecto final completo. Al terminar, tendrás las herramientas para escribir scripts de administración de calidad de producción.
🔎 Expresiones regulares en Bash
Las expresiones regulares (regex) son patrones que describen conjuntos de cadenas. En la Parte II usamos grep y sed con expresiones básicas; ahora aprenderemos a usar regex directamente en Bash con el operador =~ dentro de [[ ]], lo que permite validaciones potentes sin necesidad de comandos externos.
No entrecomilles la expresión regular en =~. Si escribes [[ $var =~ "^[0-9]+$" ]] con comillas, Bash lo tratará como cadena literal, no como regex. La forma correcta es [[ $var =~ ^[0-9]+$ ]] sin comillas a la derecha del operador.
Anatomía de una expresión regular para validar emails simplificados
🧵 Manipulación avanzada de cadenas
Bash incluye un sistema completo de expansión de parámetros que permite manipular cadenas sin invocar comandos externos como sed o awk. Esto hace que los scripts sean más rápidos y portables, especialmente en bucles que procesan miles de iteraciones.
manipulacion_cadenas.sh
#!/bin/bash
archivo="/home/ana/documentos/informe_2026.tar.gz"# ─── Longitud ───echo"Longitud: ${#archivo}"# 42# ─── Subcadena ───echo"Desde pos 10: ${archivo:10}"# documentos/informe_2026.tar.gzecho"5 chars desde 10: ${archivo:10:5}"# docum# ─── Eliminar prefijo (desde la izquierda) ───echo"${archivo#*/}"# home/ana/documentos/informe_2026.tar.gz (# = mínimo)echo"${archivo##*/}"# informe_2026.tar.gz (## = máximo, como basename)# ─── Eliminar sufijo (desde la derecha) ───echo"${archivo%.*}"# /home/ana/documentos/informe_2026.tar (% = mínimo)echo"${archivo%%.*}"# /home/ana/documentos/informe_2026 (%% = máximo)# ─── Extraer directorio y nombre (sin basename/dirname) ───echo"Directorio: ${archivo%/*}"# /home/ana/documentosecho"Nombre: ${archivo##*/}"# informe_2026.tar.gzecho"Extensión: ${archivo##*.}"# gz# ─── Buscar y reemplazar ───
frase="Linux es genial, Linux es libre"echo"${frase/Linux/GNU}"# GNU es genial, Linux es libre (primera)echo"${frase//Linux/GNU}"# GNU es genial, GNU es libre (todas)# ─── Mayúsculas / minúsculas (Bash 4+) ───
nombre="hola mundo"echo"${nombre^}"# Hola mundo (primera letra)echo"${nombre^^}"# HOLA MUNDO (todas)
MAY="ADIÓS"echo"${MAY,,}"# adiós (todas a minúscula)# ─── Valor por defecto ───echo"${EDITOR:-nano}"# Usa 'nano' si $EDITOR no está definidaecho"${EDITOR:=nano}"# Igual, pero ADEMÁS asigna el valor
Sintaxis
Operación
Regla mnemotécnica
${#var}
Longitud de la cadena
# = «contar»
${var:pos:len}
Subcadena
Como substring()
${var#patrón}
Eliminar prefijo más corto
# = izquierda, mínimo
${var##patrón}
Eliminar prefijo más largo
## = izquierda, máximo
${var%patrón}
Eliminar sufijo más corto
% = derecha, mínimo
${var%%patrón}
Eliminar sufijo más largo
%% = derecha, máximo
${var/bus/remp}
Reemplazar primera coincidencia
Una barra = primera
${var//bus/remp}
Reemplazar todas
Dos barras = todas
${var^} / ${var^^}
Primera / todas a mayúscula
^ apunta arriba
${var,} / ${var,,}
Primera / todas a minúscula
, apunta abajo
💡 ¿Cuándo usar expansión de parámetros vs sed/awk?
Usa expansión de parámetros (${var#...}) cuando trabajes con una sola variable y necesites velocidad (ideal dentro de bucles). Usa sed o awk cuando proceses archivos completos o flujos de datos. La regla es simple: si puedes evitar un comando externo, hazlo.
📸 Programación avanzada: dominar la manipulación de cadenas acelera drásticamente los scripts — Pexels (Licencia libre)
📄 Here documents y here strings
Un here document (heredoc) permite incluir bloques de texto multilínea dentro de un script. Es la forma más elegante de generar configuraciones, mensajes de ayuda o contenido dinámico sin archivos auxiliares:
heredoc.sh
#!/bin/bash
nombre="Ana"
fecha=$(date '+%d/%m/%Y')
# ─── Here document básico (con expansión de variables) ───
cat <<EOF
Hola, $nombre.
Fecha del informe: $fecha
Directorio actual: $PWD
Número de archivos: $(ls | wc -l)
EOF# ─── Heredoc SIN expansión (comillas en el delimitador) ───
cat <<'EOF'
Esto se imprime literalmente:
$nombre no se expande aquí.
$(date) tampoco se ejecuta.
EOF# ─── Heredoc con indentación (operador <<-) ───if true; then
cat <<-EOF
Este texto puede tener tabuladores
al inicio y <<- los elimina.
Útil dentro de bloques indentados.
EOFfi# ─── Generar un archivo de configuración ───
cat <<EOF > /tmp/mi_config.conf
# Configuración generada automáticamente
# Fecha: $fecha
server_name=mi_servidor
port=8080
log_level=info
EOF# ─── Here string (una sola línea) ───read -r nombre apellido <<<"Ana López"echo"Nombre: $nombre, Apellido: $apellido"# Here string con variable
linea="campo1:campo2:campo3"
IFS=: read -r a b c <<<"$linea"echo"$a | $b | $c"
✅ Caso de uso real
Los heredocs son perfectos para generar mensajes de --help en scripts, crear archivos de configuración dinámicos, enviar emails con sendmail, generar SQL para ejecutar con mysql, o crear fragmentos HTML en scripts CGI.
🔄 Subshells y entornos de ejecución
Una subshell es un proceso hijo que hereda una copia del entorno del shell padre (variables, directorio actual, etc.), pero los cambios que se hagan dentro de la subshell no afectan al padre. Entender este concepto es crucial para evitar errores sutiles:
subshells.sh
#!/bin/bash# ─── Subshell explícita con paréntesis ───
var="padre"
(
var="hijo"
cd /tmp
echo"Dentro: var=$var, dir=$PWD"
)
echo"Fuera: var=$var, dir=$PWD"# Fuera: var=padre (no cambió), dir=/home/... (no cambió)# ─── ¿Qué crea subshells? ───# 1. Paréntesis: ( comandos )# 2. Tuberías: cmd1 | cmd2 (cada lado es subshell)# 3. Sustitución de comandos: $(cmd)# 4. Segundo plano: cmd &# ─── Trampa clásica: tubería y variables ───
contador=0echo -e "a\nb\nc" | whileread -r linea; do
((contador++))
doneecho"Contador: $contador"# ¡Da 0! El while se ejecuta en subshell por la tubería# ─── Solución: sustitución de procesos ───
contador=0whileread -r linea; do
((contador++))
done < <(echo -e "a\nb\nc")
echo"Contador: $contador"# ¡Ahora sí da 3!
Los cambios en variables y directorio dentro de una subshell no afectan al shell padre
⚡ Sustitución de procesos
La sustitución de procesos es una función exclusiva de Bash (y Zsh) que permite usar la salida de un comando como si fuera un archivo. La sintaxis es <(comando) para lectura y >(comando) para escritura. El sistema crea un descriptor de archivo temporal (/dev/fd/N) que el comando receptor puede leer como un archivo normal.
sustitucion_procesos.sh
#!/bin/bash# ─── Comparar dos salidas de comando (como si fueran archivos) ───
diff <(ls /etc/*.conf | sort) <(ls /usr/share/*.conf | sort 2>/dev/null)
# ─── Comparar archivos ordenados sin crear temporales ───
diff <(sort archivo1.txt) <(sort archivo2.txt)
# ─── Procesar un comando con while sin subshell ───# (Resuelve el problema de la tubería que vimos antes)
total=0while IFS=: read -r usuario _ uid _; doif [[ $uid -ge 1000 ]]; thenecho"Usuario real: $usuario (UID $uid)"
((total++))
fidone < <(cat /etc/passwd)
echo"Total usuarios reales: $total"# ─── Escribir en múltiples destinos simultáneamente ───echo"Mensaje de log" | tee >(cat >> info.log) >(grep -i error >> errores.log) > /dev/null
💡 ¿Cuándo usar sustitución de procesos?
Úsala cuando: necesites comparar la salida de dos comandos (diff), quieras evitar subshells en bucles while read, o cuando un programa requiera un archivo pero tú quieras pasarle datos generados dinámicamente. Es mucho más limpio que crear archivos temporales.
📂 Descriptores de archivo avanzados
En la Parte I aprendiste que todo proceso tiene tres descriptores estándar: stdin (0), stdout (1) y stderr (2). Bash permite crear descriptores adicionales (del 3 al 9) para manejar múltiples flujos de datos simultáneamente:
descriptores.sh
#!/bin/bash# ─── Abrir descriptor 3 para escritura ───exec3> /tmp/log_detalle.txt
echo"Esto va a stdout (pantalla)"echo"Esto va al descriptor 3 (archivo)" >&3echo"Más datos al log" >&3# Cerrar descriptor 3exec3>&-
# ─── Abrir descriptor 4 para lectura ───exec4< /etc/hostname
read -r mi_host <&4exec4<&- # Cerrarecho"Hostname: $mi_host"# ─── Intercambiar stdout y stderr (truco clásico) ───# Enviar stdout al archivo y stderr a la pantalla:
comando 3>&11>salida.txt 2>&33>&-
# ─── Log dual: pantalla + archivo simultáneamente ───exec > >(tee -a /tmp/script.log) 2>&1echo"Este texto aparece en pantalla Y en el log"
⚠️ Cuidado con exec
exec sin un comando redirige los descriptores del shell actual de forma permanente. Si ejecutas exec > archivo.txt, toda la salida posterior del script irá al archivo. Para restaurar, guarda una copia: exec 3>&1 (copia stdout en 3) antes de redirigir, y luego exec 1>&3 para restaurar.
📋 Select y menús interactivos
El comando select es una joya poco conocida de Bash que genera menús numerados automáticamente. Combinado con case, permite crear interfaces interactivas profesionales con muy poco código:
menu_select.sh
#!/bin/bash# ─── Menú simple con select ───
PS3="Elige una distribución: "# Personaliza el promptselectdistroinUbuntu Fedora Debian Arch "Salir"; docase"$distro"in
Ubuntu) echo"Basada en Debian, usa APT" ;;
Fedora) echo"Patrocinada por Red Hat, usa DNF";;
Debian) echo"La madre de Ubuntu, estable" ;;
Arch) echo"Rolling release, para expertos" ;;
Salir) echo"¡Hasta luego!"; break ;;
*) echo"Opción no válida" ;;
esacdone# Salida del script:# 1) Ubuntu# 2) Fedora# 3) Debian# 4) Arch# 5) Salir# Elige una distribución:
gestor_servicios.sh — Menú profesional
#!/bin/bash# Obtener lista de servicios activos dinámicamente
mapfile -t servicios < <(systemctl list-units --type=service --state=running --no-legend | awk '{print $1}' | head -10)
PS3=$'\n'"Selecciona servicio (q=salir): "select srv in"${servicios[@]}"; do
[[ "$REPLY" == "q" ]] && break
[[ -z "$srv" ]] && { echo"Opción inválida"; continue; }
echo"── Estado de $srv ──"
systemctl status "$srv" --no-pager | head -5
echodone
✅ Buenas prácticas de scripting
Después de tres partes de aprendizaje, estas son las prácticas que separan un script amateur de uno profesional. Aplícalas en todos tus proyectos:
Las 10 reglas de oro del scripting profesional en Bash
📸 Automatización profesional: un script bien escrito ahorra horas de trabajo manual — Pexels (Licencia libre)
🚀 Proyecto final: monitor de sistema
Vamos a aplicar todo lo aprendido en las tres partes de esta serie en un proyecto real: un script de monitorización del sistema que genera un informe completo. Este script usa funciones, arrays, condicionales, procesamiento de texto, heredocs, traps y buenas prácticas:
sysmonitor.sh — Proyecto final completo
#!/usr/bin/env bash# ─────────────────────────────────────# Monitor de sistema — Proyecto final Shell de Linux# Genera un informe HTML con el estado del sistema# ─────────────────────────────────────set -euo pipefail
readonly INFORME="/tmp/informe_$(hostname)_$(date +%Y%m%d_%H%M).html"readonly UMBRAL_CPU=80readonly UMBRAL_DISCO=90# ─── Funciones de datos ───
obtener_cpu() {
local cpu_idle
cpu_idle=$(top -bn1 | awk '/^%?Cpu/{print $8}' | head -1)
echo"$((100 - ${cpu_idle%.*}))"
}
obtener_memoria() {
free -m | awk '/^Mem:/{printf "%d/%d MB (%.0f%%)", $3, $2, $3/$2*100}'
}
obtener_disco() {
df -h / | awk 'NR==2 {print $3 "/" $2 " (" $5 ")"}'
}
obtener_top_procesos() {
ps aux --sort=-%cpu | awk 'NR>1 && NR<=6 {printf "<tr><td>%s</td><td>%s%%</td><td>%s%%</td></tr>\n", $11, $3, $4}'
}
verificar_alertas() {
local alertas=""local cpu=$(obtener_cpu)
local disco_pct
disco_pct=$(df / | awk 'NR==2 {gsub(/%/,""); print $5}')
[[ $cpu -gt $UMBRAL_CPU ]] && alertas+="<li>⚠️ CPU al ${cpu}%</li>"
[[ $disco_pct -gt $UMBRAL_DISCO ]] && alertas+="<li>⚠️ Disco al ${disco_pct}%</li>"
[[ -n "$alertas" ]] && echo"<ul>${alertas}</ul>" || echo"✅ Todo normal"
}
# ─── Generar informe HTML con heredoc ───
generar_informe() {
cat <<HTML > "$INFORME"
<!DOCTYPE html>
<html lang="es">
<head><meta charset="UTF-8">
<title>Informe — $(hostname)</title>
<style>body{font-family:sans-serif;max-width:700px;margin:2rem auto}
table{border-collapse:collapse;width:100%}
td,th{border:1px solid #ddd;padding:6px 10px;text-align:left}
th{background:#2E7D32;color:#fff}</style>
</head><body>
<h1>📊 Informe de $(hostname)</h1>
<p>Generado: $(date '+%d/%m/%Y %H:%M')</p>
<h2>Resumen</h2>
<ul>
<li><b>CPU:</b> $(obtener_cpu)%</li>
<li><b>Memoria:</b> $(obtener_memoria)</li>
<li><b>Disco /:</b> $(obtener_disco)</li>
<li><b>Uptime:</b> $(uptime -p)</li>
<li><b>Kernel:</b> $(uname -r)</li>
</ul>
<h2>Alertas</h2>
$(verificar_alertas)
<h2>Top 5 procesos (CPU)</h2>
<table><tr><th>Proceso</th><th>CPU</th><th>MEM</th></tr>
$(obtener_top_procesos)
</table>
</body></html>
HTML
}
# ─── Main ───echo"Generando informe del sistema..."
generar_informe
echo"✅ Informe guardado en: $INFORME"echo" Ábrelo con: xdg-open $INFORME"
✅ Ampliaciones sugeridas
Este proyecto se puede extender fácilmente: añadir envío por email con sendmail, programar ejecución periódica con cron (que aprenderás en la lección de automatización con cron), agregar monitorización de servicios con systemctl, o enviar alertas a Slack o Telegram mediante su API.
📝 Ejercicios prácticos
📋 Ejercicio 1: Validador de contraseñas con regex
Crea un script que pida una contraseña y valide que tenga al menos 8 caracteres, una mayúscula, una minúscula y un dígito. Usa =~ con BASH_REMATCH.
📋 Ejercicio 3: Generador de configuración con heredoc
Crea un script que pregunte nombre de host, puerto e IP, y genere un archivo de configuración Nginx usando un heredoc. El archivo debe guardarse en /tmp/nginx_<hostname>.conf.
Usando sustitución de procesos, escribe un script que compare los archivos de dos directorios y muestre: archivos solo en el primero, solo en el segundo, y en ambos.
💡 Ver solución
comparar_dirs.sh
#!/bin/bashset -euo pipefail
[[ $# -eq 2 ]] || { echo"Uso: $0 dir1 dir2"; exit1; }
echo"=== Solo en $1 ==="
comm -23 <(ls "$1" | sort) <(ls "$2" | sort)
echo"=== Solo en $2 ==="
comm -13 <(ls "$1" | sort) <(ls "$2" | sort)
echo"=== En ambos ==="
comm -12 <(ls "$1" | sort) <(ls "$2" | sort)
📋 Ejercicio 5: Menú con select y arrays dinámicos
Crea un script que liste los usuarios reales del sistema (UID ≥ 1000) con select. Al elegir uno, muestra su directorio home, shell, último login y uso de disco en su directorio.
❓ Preguntas frecuentes sobre La Shell de Linux — Parte III: Técnicas avanzadas y proyectos reales
Las dudas más comunes respondidas de forma clara y directa.
Bash soporta expresiones regulares extendidas (ERE) con el operador =~ dentro de [[ ]]. Esto incluye cuantificadores (+, *, ?), alternancia (|), grupos de captura con paréntesis y clases de caracteres como [[:digit:]].
Un here document (heredoc) permite incluir bloques de texto multilínea dentro de un script, delimitados por una marca (como EOF). Es ideal para generar configuraciones, mensajes de ayuda o contenido HTML desde un script sin necesidad de archivos externos.
Una subshell es un proceso hijo que hereda una copia del entorno del padre. Los cambios en variables o directorio dentro de la subshell no afectan al shell padre. Se crean con paréntesis (), tuberías, sustitución de comandos $() y scripts ejecutados con ./
Bash ofrece operadores de expansión de parámetros: ${var#patrón} y ${var##patrón} para eliminar prefijos, ${var%patrón} y ${var%%patrón} para sufijos, ${var/buscar/reemplazar} para sustitución, ${var:inicio:longitud} para subcadenas, y ${#var} para la longitud.
La sustitución de procesos, con sintaxis <(comando) o >(comando), permite usar la salida de un comando como si fuera un archivo. Es útil cuando un programa necesita un archivo de entrada pero tú quieres pasarle datos dinámicos generados por otro comando.
Las principales son: usar siempre set -euo pipefail, entrecomillar todas las variables, declarar variables locales en funciones con local, usar nombres descriptivos, añadir comentarios en la lógica compleja, validar argumentos de entrada, y pasar el script por ShellCheck.
★★★★★
Valora este artículo
¿Útil?
💬 Foro de discusión
¿Tienes dudas sobre La Shell de Linux — Parte III: Técnicas avanzadas y proyectos reales? Comparte tu pregunta con la comunidad.
¿Tienes cuenta?o comenta como invitado ↓
Iniciar sesión
🔑 Recuperar contraseña
Introduce el email con el que te registraste. Te enviaremos un enlace para crear una nueva contraseña.
Crear cuenta
Solo necesitas nombre, email y contraseña. Sin verificación por email.
Todavía no hay mensajes. ¡Sé el primero en participar!
🚀 ¿Quieres dominar Linux profesionalmente?
Cursos bonificados por FUNDAE para empresas — formación 100% subvencionada
Usamos cookies propias para mejorar tu experiencia de navegación y analizar
el uso del sitio. No compartimos datos con terceros ni usamos cookies de
publicidad. Puedes aceptar todas, aceptar solo las necesarias o configurar
tus preferencias.
Política de privacidad
Imprescindibles para el funcionamiento del sitio: preferencias de interfaz,
gestión de sesiones y este mismo aviso de cookies. No recogen datos
identificativos.
Nos permiten entender cómo navegas por el contenido para mejorar la
experiencia de aprendizaje. Utilizan identificadores anónimos (UUID) sin
vinculación a datos personales. Retención máxima: 6 meses.
¿Cómo valorarías tu experiencia aprendiendo en esta sección?