Los Tres Streams
En Unix/Linux, cada proceso tiene tres flujos de datos estándar. Estos streams son fundamentales para entender cómo los programas se comunican entre sí y con el usuario.
STDIN (0)
Standard Input
Entrada estándar - donde el programa lee datos
Por defecto: tecladoSTDOUT (1)
Standard Output
Salida estándar - donde el programa envía resultados
Por defecto: pantallaSTDERR (2)
Standard Error
Salida de errores - donde van los mensajes de error
Por defecto: pantalla¿Por qué separar STDOUT y STDERR?
Separar la salida normal de los errores permite manejar cada flujo independientemente. Por ejemplo, puedes guardar los resultados en un archivo mientras los errores se muestran en pantalla.
Entrada Estándar (STDIN)
STDIN es cómo tu script puede recibir datos del usuario o de otros programas.
Leyendo entrada del usuario
#!/bin/bash
# Leer entrada básica
echo "¿Cómo te llamas?"
read nombre
echo "Hola $nombre"
# Leer con prompt integrado
read -p "¿Cuál es tu edad? " edad
echo "Tienes $edad años"
# Leer sin mostrar la entrada (para contraseñas)
read -s -p "Ingresa tu contraseña: " password
echo
echo "Contraseña ingresada (no se muestra por seguridad)"
# Leer múltiples variables
read -p "Ingresa nombre y apellido: " nombre apellido
echo "Nombre: $nombre, Apellido: $apellido"
# Timeout para entrada
if read -t 5 -p "Tienes 5 segundos para responder: " respuesta; then
echo "Respondiste: $respuesta"
else
echo "Tiempo agotado"
fi
$ ./leer_entrada.sh
¿Cómo te llamas?
Juan
Hola Juan
¿Cuál es tu edad? 25
Tienes 25 años
Ingresa tu contraseña:
Contraseña ingresada (no se muestra por seguridad)
Ingresa nombre y apellido: María González
Nombre: María, Apellido: González
Tienes 5 segundos para responder: Hola
Respondiste: Hola
Leyendo desde archivos
#!/bin/bash
archivo="lista_usuarios.txt"
# Crear archivo de ejemplo
cat > "$archivo" << EOF
Juan:25:Desarrollador
María:30:Diseñadora
Pedro:22:Estudiante
Ana:28:Gerente
EOF
echo "Procesando archivo: $archivo"
echo "================================"
# Leer archivo línea por línea
while IFS=':' read -r nombre edad profesion; do
echo "Nombre: $nombre"
echo "Edad: $edad años"
echo "Profesión: $profesion"
echo "---"
done < "$archivo"
# Alternativa con cat
echo -e "\nAlternativa con cat:"
cat "$archivo" | while IFS=':' read -r nombre edad profesion; do
echo "$nombre es $profesion de $edad años"
done
Salida Estándar (STDOUT)
STDOUT es donde tu script envía sus resultados normales.
#!/bin/bash
# echo básico
echo "Mensaje simple"
# echo sin salto de línea
echo -n "Sin salto de línea: "
echo "continuación"
# echo con secuencias de escape
echo -e "Línea 1\nLínea 2\tcon tab"
# printf (más control sobre formato)
nombre="Juan"
edad=25
printf "Nombre: %-10s Edad: %d años\n" "$nombre" $edad
# Variables en formato
precio=19.99
printf "El precio es: $%.2f\n" $precio
# Colores en la salida
echo -e "\e[31mTexto en rojo\e[0m"
echo -e "\e[32mTexto en verde\e[0m"
echo -e "\e[34mTexto en azul\e[0m"
echo -e "\e[1mTexto en negrita\e[0m"
# Here document para múltiples líneas
cat << EOF
Este es un bloque
de múltiples líneas
que va a STDOUT
EOF
Códigos de color ANSI
\e[31m
- Rojo\e[32m
- Verde\e[33m
- Amarillo\e[34m
- Azul\e[0m
- Resetear color\e[1m
- Negrita
Salida de Error (STDERR)
STDERR se usa para mensajes de error, advertencias y información de debugging.
#!/bin/bash
# Funciones auxiliares para mensajes
error() {
echo -e "\e[31m[ERROR]\e[0m $1" >&2
}
warning() {
echo -e "\e[33m[WARNING]\e[0m $1" >&2
}
info() {
echo -e "\e[34m[INFO]\e[0m $1" >&2
}
debug() {
if [[ "${DEBUG:-}" == "true" ]]; then
echo -e "\e[35m[DEBUG]\e[0m $1" >&2
fi
}
# Usar las funciones
info "Iniciando el proceso..."
warning "Este es un mensaje de advertencia"
error "Error simulado"
debug "Información de debug (solo si DEBUG=true)"
# Validación con manejo de errores
archivo="$1"
if [[ -z "$archivo" ]]; then
error "No se proporcionó nombre de archivo"
echo "Uso: $0 " >&2
exit 1
fi
if [[ ! -f "$archivo" ]]; then
error "El archivo '$archivo' no existe"
exit 1
fi
info "Archivo '$archivo' encontrado"
echo "Contenido del archivo:"
cat "$archivo"
$ DEBUG=true ./manejo_errores.sh archivo.txt
[INFO] Iniciando el proceso...
[WARNING] Este es un mensaje de advertencia
[ERROR] Error simulado
[DEBUG] Información de debug (solo si DEBUG=true)
[ERROR] El archivo 'archivo.txt' no existe
Códigos de Salida
Los códigos de salida comunican si un programa terminó exitosamente o con error:
#!/bin/bash
# Función para validar archivo
validar_archivo() {
local archivo="$1"
if [[ -z "$archivo" ]]; then
echo "Error: No se proporcionó archivo" >&2
return 1 # Código de error
fi
if [[ ! -f "$archivo" ]]; then
echo "Error: '$archivo' no existe" >&2
return 2 # Código específico para archivo no encontrado
fi
if [[ ! -r "$archivo" ]]; then
echo "Error: '$archivo' no es legible" >&2
return 3 # Código para permisos
fi
echo "Archivo '$archivo' es válido"
return 0 # Éxito
}
# Usar la función
if validar_archivo "$1"; then
echo "✅ Validación exitosa"
wc -l "$1"
else
codigo_error=$?
case $codigo_error in
1) echo "❌ Falta parámetro" >&2 ;;
2) echo "❌ Archivo no encontrado" >&2 ;;
3) echo "❌ Sin permisos de lectura" >&2 ;;
*) echo "❌ Error desconocido: $codigo_error" >&2 ;;
esac
exit $codigo_error
fi
# Verificar comando anterior
ls /directorio/inexistente 2>/dev/null
if [[ $? -ne 0 ]]; then
echo "El comando ls falló"
fi
# Alternativa más elegante
if ! ls /directorio/inexistente >/dev/null 2>&1; then
echo "Directorio no encontrado (método elegante)"
fi
Códigos de salida comunes
0
- Éxito1
- Error general2
- Mal uso de comando126
- Comando no ejecutable127
- Comando no encontrado128+n
- Fatal error signal "n"
Redirecciones Básicas
Las redirecciones permiten cambiar hacia dónde van los streams:
#!/bin/bash
echo "=== REDIRECCIONES BÁSICAS ==="
# Redirigir STDOUT a archivo (sobrescribe)
echo "Este texto va al archivo" > salida.txt
# Redirigir STDOUT a archivo (añade)
echo "Esta línea se añade" >> salida.txt
# Redirigir STDERR a archivo
ls /directorio/inexistente 2> errores.txt
# Redirigir STDOUT y STDERR al mismo archivo
comando_inexistente > todo.txt 2>&1
# Redirigir STDERR a STDOUT
ls /directorio/inexistente 2>&1
# Descartar salida (/dev/null es el "agujero negro")
comando_ruidoso > /dev/null 2>&1
# Leer desde archivo
while read linea; do
echo "Procesando: $linea"
done < salida.txt
echo "Archivos creados:"
ls -la *.txt
Cuidado con el orden
En comando > archivo 2>&1
, el orden importa. Primero se redirige STDOUT, luego STDERR sigue a STDOUT. 2>&1 > archivo
no funciona igual.
Here Documents y Here Strings
Técnicas avanzadas para manejar bloques de texto y entrada múltiple:
#!/bin/bash
# Here document básico
cat << EOF
Este es un bloque de texto
que puede contener múltiples líneas
y variables como: $USER
EOF
# Here document sin expansión de variables (usando 'EOF')
cat << 'EOF'
Este texto es literal
$USER no se expande aquí
EOF
# Here document indentado (<<-)
if true; then
cat <<- INDENTADO
Este texto puede estar indentado
en el código fuente
pero se mostrará sin la indentación inicial
INDENTADO
fi
# Crear archivo con here document
cat << ARCHIVO > configuracion.conf
# Archivo de configuración
servidor=localhost
puerto=8080
usuario=$USER
fecha=$(date)
ARCHIVO
# Here document con comando
mysql -u root -p << CONSULTA
USE mi_base_datos;
SELECT * FROM usuarios WHERE activo = 1;
SHOW TABLES;
CONSULTA
echo "Archivo de configuración creado:"
cat configuracion.conf
#!/bin/bash
# Here string básico
grep "bash" <<< "Esta línea contiene bash script"
# Procesar variable con here string
datos="juan:25:desarrollador"
IFS=':' read nombre edad trabajo <<< "$datos"
echo "Nombre: $nombre, Edad: $edad, Trabajo: $trabajo"
# Usar con while loop
while read linea; do
echo "Procesando: $linea"
done <<< "Línea 1
Línea 2
Línea 3"
# Combinado con comandos
resultado=$(wc -w <<< "estas son cinco palabras exactamente")
echo "Número de palabras: $resultado"
Ejercicios Prácticos
Ejercicio 1: Script interactivo de configuración
Crea un script que configure un proyecto básico:
#!/bin/bash
# Funciones auxiliares
info() { echo -e "\e[34m[INFO]\e[0m $1"; }
error() { echo -e "\e[31m[ERROR]\e[0m $1" >&2; }
success() { echo -e "\e[32m[OK]\e[0m $1"; }
echo "========================================="
echo " CONFIGURADOR DE PROYECTO"
echo "========================================="
# Recopilar información
read -p "Nombre del proyecto: " proyecto
read -p "Descripción: " descripcion
read -p "Autor: " autor
read -p "Versión inicial [1.0.0]: " version
version=${version:-"1.0.0"}
# Validar entrada
if [[ -z "$proyecto" ]]; then
error "El nombre del proyecto es obligatorio"
exit 1
fi
if [[ -d "$proyecto" ]]; then
error "El directorio '$proyecto' ya existe"
exit 1
fi
# Crear estructura
info "Creando estructura del proyecto..."
mkdir -p "$proyecto"/{src,docs,tests}
cd "$proyecto" || exit 1
# Crear README.md
cat << README > README.md
# $proyecto
$descripcion
## Información del Proyecto
- **Autor:** $autor
- **Versión:** $version
- **Fecha de creación:** $(date "+%Y-%m-%d")
## Estructura
\`\`\`
$proyecto/
├── src/ # Código fuente
├── docs/ # Documentación
├── tests/ # Pruebas
└── README.md # Este archivo
\`\`\`
## Instalación
[Instrucciones de instalación aquí]
## Uso
[Ejemplos de uso aquí]
README
# Crear archivo .gitignore básico
cat << GITIGNORE > .gitignore
# Logs
*.log
logs/
# Archivos temporales
*.tmp
*.temp
.cache/
# Configuración local
config.local.*
.env
# Sistema operativo
.DS_Store
Thumbs.db
GITIGNORE
success "Proyecto '$proyecto' creado exitosamente"
info "Estructura creada:"
tree . 2>/dev/null || find . -type d | sed 's/^./├── /'
Ejercicio 2: Monitor de logs con filtrado
Script que procesa archivos de log y separa errores de información normal:
#!/bin/bash
log_file="${1:-/var/log/syslog}"
fecha=$(date +%Y%m%d_%H%M%S)
# Validar archivo
if [[ ! -f "$log_file" ]]; then
echo "Error: El archivo '$log_file' no existe" >&2
exit 1
fi
if [[ ! -r "$log_file" ]]; then
echo "Error: Sin permisos para leer '$log_file'" >&2
exit 1
fi
echo "Analizando: $log_file"
echo "Fecha: $(date)"
echo "================================="
# Separar por tipo de mensaje
grep -i "error\|fail\|critical" "$log_file" > "errores_${fecha}.txt" 2>/dev/null
grep -i "warning\|warn" "$log_file" > "warnings_${fecha}.txt" 2>/dev/null
grep -i "info\|notice" "$log_file" > "info_${fecha}.txt" 2>/dev/null
# Mostrar estadísticas
total_lineas=$(wc -l < "$log_file")
errores=$(wc -l < "errores_${fecha}.txt")
warnings=$(wc -l < "warnings_${fecha}.txt")
info=$(wc -l < "info_${fecha}.txt")
echo "ESTADÍSTICAS:"
echo "Total de líneas: $total_lineas"
echo -e "\e[31mErrores: $errores\e[0m"
echo -e "\e[33mWarnings: $warnings\e[0m"
echo -e "\e[32mInfo: $info\e[0m"
# Si hay errores, mostrarlos
if [[ $errores -gt 0 ]]; then
echo -e "\n\e[31mÚLTIMOS ERRORES:\e[0m"
tail -5 "errores_${fecha}.txt"
fi
echo -e "\nArchivos generados:"
ls -la *_${fecha}.txt