Entrada y Salida Estándar

Domina los flujos de datos fundamentales en Linux: stdin, stdout y stderr

Módulo 2 ⏱️ 35-40 min 🔄 I/O 📊 Streams 📚 Principiante

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: teclado

STDOUT (1)

Standard Output

Salida estándar - donde el programa envía resultados

Por defecto: pantalla

STDERR (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

Comando read Copiar
#!/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

Procesar archivos línea por línea Copiar
#!/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.

Diferentes formas de mostrar salida Copiar
#!/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.

Enviando mensajes a STDERR Copiar
#!/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:

Manejando códigos de salida Copiar
#!/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 - Éxito
  • 1 - Error general
  • 2 - Mal uso de comando
  • 126 - Comando no ejecutable
  • 127 - Comando no encontrado
  • 128+n - Fatal error signal "n"

Redirecciones Básicas

Las redirecciones permiten cambiar hacia dónde van los streams:

Operadores de redirección Copiar
#!/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:

Here Documents (<<) Copiar
#!/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
Here Strings (<<<) Copiar
#!/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:

configurar_proyecto.sh Copiar
#!/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:

monitor_logs.sh Copiar
#!/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