Procesamiento de Texto en Bash
Cómo construir pipelines de procesamiento de texto con grep, sed, awk, cut, sort, uniq y tr para análisis de logs y transformación de datos.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Descripción General
Las herramientas de procesamiento de texto Unix están diseñadas para componerse en pipelines: cada herramienta hace una cosa bien, y el shell las conecta con pipes. Una sola línea de Bash puede reemplazar cientos de líneas de Python o JavaScript para análisis de logs, extracción de datos y generación de reportes. Esta receta cubre las herramientas esenciales y cómo combinarlas de forma segura.
Cuándo Usar
- Extraer y filtrar líneas de log por patrón, tiempo o código de estado
- Transformar datos CSV o tabulares (ordenar, deduplicación, agregación)
- Buscar en bases de código por patrones a través de miles de archivos
- Generar reportes rápidos desde salida de texto estructurado
- Pre-procesar datos antes de alimentarlos a una base de datos o API
Cuándo NO Usar
- Parsear formatos anidados o irregulares (JSON, XML, HTML) — usa
jq,xqo un parser apropiado - Tareas que requieren estado complejo entre líneas — awk puede hacerlo, pero Python es más mantenible
- Transformaciones multi-paso donde el manejo de errores importa — los lenguajes de scripting tienen mejor debugging
- Casos edge de Unicode — las herramientas clásicas son byte-oriented y pueden dañar caracteres multibyte
Implementación Paso a Paso
grep — Coincidencia de Patrones
# Buscar recursivamente, mostrar números de línea, ignorar archivos binarios
grep -rn "ERROR" logs/
# Coincidencia invertida, contar ocurrencias
grep -vc "^#" config.ini
# Múltiples patrones con regex extendida
grep -E "(ERROR|FATAL|CRITICAL)" app.log
# Líneas de contexto: 2 antes, 3 después
grep -B 2 -A 3 "Exception" app.log
# Solo nombres de archivo que contienen coincidencia (útil para operaciones batch)
grep -rl "TODO" src/
# Regex compatible con Perl (PCRE) para lookaheads
grep -P "(?<=user_id=)\d+" access.log
sed — Edición de Streams
# Reemplazar primera ocurrencia por línea
sed 's/foo/bar/' file.txt
# Reemplazar todas las ocurrencias globalmente
sed 's/foo/bar/g' file.txt
# Reemplazar in-place con backup
sed -i.bak 's/old_domain/new_domain/g' config.conf
# Eliminar líneas que coinciden con patrón
sed '/^#/d' config.ini # Eliminar comentarios
sed '/^$/d' file.txt # Eliminar líneas vacías
# Extraer líneas específicas
sed -n '10,20p' file.txt # Imprimir líneas 10-20
sed -n '50,$p' file.txt # Imprimir desde línea 50 al final
# Reemplazo multi-línea (append después de coincidencia)
sed '/pattern/a\\New line after match' file.txt
awk — Procesamiento de Campos y Agregación
# Imprimir columnas específicas (delimitadas por espacio/tabulador)
awk '{print $1, $3}' access.log
# Sumar una columna
awk '{sum += $2} END {print sum}' sales.txt
# Promedio con conteo
awk '{sum += $2; count++} END {if (count) print sum/count}' data.txt
# Filtrar filas por condición
awk '$3 > 100 {print $1, $3}' orders.csv
# Procesar CSV con delimitador personalizado
awk -F',' '{print $2, $5}' customers.csv
# Agrupar y contar (como SQL GROUP BY)
awk '{count[$1]++} END {for (k in count) print k, count[k]}' status.log
# Formatear salida con encabezados
awk 'BEGIN {print "IP", "Requests"} {count[$1]++} END {for (ip in count) print ip, count[ip]}' access.log
cut, sort, uniq — Extracción de Columnas y Deduplicación
# Extraer columnas por posición o delimitador
cut -d',' -f1,3,5 data.csv
cut -c1-10 file.txt # Primeros 10 caracteres
# Ordenar numéricamente, inverso, por columna específica
sort -t',' -k3 -n sales.csv # Ordenar por 3ra columna numéricamente
sort -u file.txt # Ordenar y eliminar duplicados
# Contar ocurrencias únicas
sort file.txt | uniq -c | sort -rn # Más frecuentes primero
# Mostrar solo líneas duplicadas o únicas
sort file.txt | uniq -d # Solo duplicados
sort file.txt | uniq -u # Solo líneas únicas
tr — Traducción de Caracteres
# Convertir a mayúsculas
cat file.txt | tr 'a-z' 'A-Z'
# Comprimir caracteres repetidos
tr -s ' ' < file.txt # Colapsar múltiples espacios a uno
# Eliminar caracteres
tr -d '\r' < file.txt # Eliminar retornos de carro
# Reemplazar finales de línea
tr '\n' ',' < lines.txt > comma-separated.txt
Pipelines Complejos
# Top 10 tipos de error más frecuentes en un log
awk '$0 ~ /ERROR|FATAL/ {print $5}' app.log | \
sort | uniq -c | sort -rn | head -10
# Extraer IPs únicas de clientes con conteo de requests, ordenado
awk '{print $1}' access.log | sort | uniq -c | sort -rn | \
awk '{print $2 "," $1}' > ip_counts.csv
# Encontrar queries lentas (>1s) y agrupar por tabla
awk '$NF > 1 {print}' slow_query.log | \
grep -oP 'FROM \K\w+' | sort | uniq -c | sort -rn
# Convertir timestamps de log a formato ISO y filtrar rango de fechas
sed -n '/2024-06-01/,/2024-06-07/p' app.log | \
awk '{gsub(/\//, "-", $1); print $1 "T" $2}'
# Generar reporte: distribución de códigos de estado
awk '{print $9}' access.log | sort | uniq -c | \
awk '{printf "%s: %d requests (%.1f%%)\n", $2, $1, $1*100/total}' \
total=$(wc -l < access.log)
Mejores Prácticas
- Siempre entrecomilla patrones de regex con caracteres especiales.
grep "$pattern"previene que el shell expanda*o?antes de que grep los vea. - Usa
awkpara datos columnares en lugar decutcuando los campos varían en ancho.cutfalla con espaciado variable;awkdivide en cualquier whitespace por defecto. - Prefiere
jqpara JSON,xqpara XML,csvkitpara CSV. Las herramientas clásicas tratan estos formatos como texto plano y romperán en campos entrecomillados o estructuras anidadas. - Encadena herramientas de izquierda a derecha en orden de filtrado. Pon
greptemprano para reducir volumen de datos antes de operaciones costosas deawkosort. - Usa
LC_ALL=Cpara ordenamiento consistente y rendimiento. Fuerza ordenamiento byte-wise y evita comportamiento dependiente de locale.
Errores Comunes
- Parsear JSON/HTML con grep/sed/awk. Estos no son formatos estructurados — usa
jq,python -m json.toolo un DOM parser. - Olvidar que
sedyawkoperan línea por línea por defecto. Los patrones multi-línea requieren flags especiales (sed -z, manipulación de RS enawk) que no son obvios. - Asumir que
sortes estable por defecto. La estabilidad desortvaría por implementación; usasort -ssi la necesitas. - Usar
catinnecesariamente.cat file | grep patternes un uso inútil decat. Usagrep pattern file. - No manejar entrada vacía. Muchos pipelines fallan silenciosamente en archivos vacíos — agrega
| catal final o verifica el tamaño del archivo primero.