Procesado de texto en Linux — grep, sed, awk y pipes

Filosofía Unix: todo es texto

Unix fue diseñado con una idea radical: representar casi todo como texto plano (logs, configuración, procesos, dispositivos). De ahí surge el principio:

“Haz una cosa y hazla bien. Conecta programas con pipes.”

Un pipe | toma la salida estándar (stdout) de un programa y la pasa como entrada estándar (stdin) al siguiente. No se escribe nada a disco; la cadena vive en memoria.

programa_A | programa_B | programa_C

Esto permite componer herramientas simples para hacer tareas complejas sin escribir un script. Es el corazón del trabajo en CLI.


Ver y leer ficheros

cat — volcar contenido

cat fichero.txt              # imprime todo el fichero
cat -n fichero.txt           # con números de línea
cat a.txt b.txt > unido.txt  # concatenar dos ficheros

Gotcha: cat fichero | grep patron es un “Useless Use of Cat”. Usa directamente grep patron fichero.

less — paginador interactivo (recomendado para ficheros grandes)

less /var/log/syslog
less +F /var/log/syslog   # modo "follow" igual que tail -f, Ctrl+C para parar
TeclaAcción
j / kBajar / subir una línea
d / uBajar / subir media página
g / GInicio / final del fichero
/patronBuscar hacia adelante
n / NSiguiente / anterior resultado
qSalir

head y tail — primeras/últimas líneas

head -n 20 fichero.txt      # primeras 20 líneas (default: 10)
tail -n 50 fichero.txt      # últimas 50 líneas
tail -f /var/log/syslog     # seguir en vivo (follow); Ctrl+C para parar
tail -n +5 fichero.txt      # todo excepto las primeras 4 líneas

tail -f es imprescindible para monitorear logs de aplicaciones, servicios systemd, etc.

wc — contar

wc -l fichero.txt    # número de líneas
wc -w fichero.txt    # número de palabras
wc -c fichero.txt    # bytes
wc fichero.txt       # líneas  palabras  bytes  nombre

Buscar contenido con grep

Sintaxis base

grep "patron" fichero.txt
grep "patron" fichero1.txt fichero2.txt
grep "patron" *.log

Opciones esenciales

OpciónSignificadoEjemplo
-iIgnorar mayúsculasgrep -i "error" app.log
-nMostrar número de líneagrep -n "TODO" *.py
-rRecursivo en directoriogrep -r "import os" src/
-vInvertir (líneas que NO casan)grep -v "^#" config.ini
-cContar líneas que casangrep -c "ERROR" app.log
-lSolo nombres de ficheros que tienen el patróngrep -rl "SECRET" .
-ERegex extendida (ERE)`grep -E “error
-oSolo la parte que casa (no la línea entera)grep -oE "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log
-A nn líneas de contexto después del matchgrep -A 3 "Exception" app.log
-B nn líneas de contexto antes del matchgrep -B 2 "FATAL" app.log

Regex básicas en grep

.         cualquier carácter
*         cero o más del carácter anterior
^         inicio de línea
$         fin de línea
[abc]     uno de a, b o c
[^abc]    ninguno de a, b, c
\b        límite de palabra (con -E o -w)

Con -E (ERE) se añaden:

+         uno o más
?         cero o uno
{n,m}     entre n y m repeticiones
(a|b)     alternativa: a o b
# Líneas que empiezan con "Error"
grep "^Error" app.log
 
# Líneas vacías
grep "^$" fichero.txt
 
# IPs en un log de acceso
grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log
 
# Líneas que terminan en punto y coma
grep ";$" script.sql

Gotcha: con -r sobre directorios grandes, añade --include="*.log" para no escanear binarios:

grep -r --include="*.py" "def " src/

Buscar ficheros con find

Sintaxis base

find <directorio_inicio> [condiciones] [acciones]
find . -name "*.log"         # busca desde directorio actual
find /etc -name "*.conf"

Condiciones principales

CondiciónEjemploSignificado
-name "*.py"find . -name "*.py"Por nombre (glob, case-sensitive)
-iname "*.PY"find . -iname "*.py"Por nombre (case-insensitive)
-type ffind . -type fSolo ficheros regulares
-type dfind . -type dSolo directorios
-size +10Mfind . -size +10MMás de 10 MB
-size -1kfind . -size -1kMenos de 1 KB
-mtime -7find . -mtime -7Modificado hace menos de 7 días
-mtime +30find . -mtime +30Modificado hace más de 30 días
-maxdepth 2find . -maxdepth 2 -name "*.py"No bajar más de 2 niveles
! -name "*.log"find . ! -name "*.log"Negación

Acciones: -exec y -delete

# Ejecutar un comando por cada resultado
find . -name "*.tmp" -exec rm {} \;
# {} es el placeholder para el nombre de fichero
# \; termina el comando por cada fichero individualmente
 
# Más eficiente: pasar todos los ficheros juntos a rm
find . -name "*.tmp" -exec rm {} +
 
# Borrar directamente (equivalente pero más rápido)
find . -name "*.tmp" -delete
 
# Cambiar permisos a todos los .sh
find . -name "*.sh" -exec chmod +x {} +
 
# Ver los últimos 5 logs modificados
find /var/log -name "*.log" -mtime -1 | head -5

Gotcha: find sin -maxdepth puede ser muy lento en / o en directorios grandes. Siempre busca desde una ruta específica.


Transformar texto con sed

sed (Stream EDitor) lee línea a línea y aplica transformaciones. Su comando más usado es la sustitución.

Sustitución: s/patron/reemplazo/flags

# Reemplazar primera ocurrencia por línea
sed 's/foo/bar/' fichero.txt
 
# Reemplazar TODAS las ocurrencias (flag g = global)
sed 's/foo/bar/g' fichero.txt
 
# Case-insensitive
sed 's/foo/bar/gI' fichero.txt
 
# Solo mostrar líneas que cambiaron (-n + p)
sed -n 's/foo/bar/p' fichero.txt

-i: editar el fichero in-place

# Modifica el fichero directamente (¡sin backup!)
sed -i 's/localhost/127.0.0.1/g' config.ini
 
# Con backup de seguridad (.bak)
sed -i.bak 's/localhost/127.0.0.1/g' config.ini

Gotcha: en Ubuntu/Linux, -i no necesita argumento. En macOS BSD sed, es obligatorio: -i ''.

Borrar líneas

# Borrar líneas que contengan "DEBUG"
sed '/DEBUG/d' app.log
 
# Borrar líneas vacías
sed '/^$/d' fichero.txt
 
# Borrar líneas 3 a 7
sed '3,7d' fichero.txt
 
# Borrar comentarios (líneas que empiezan con #)
sed '/^#/d' config.ini

Imprimir rangos de líneas

sed -n '10,20p' fichero.txt     # imprime líneas 10 a 20
sed -n '/START/,/END/p' log.txt # imprime desde "START" hasta "END"

Procesar campos con awk

awk es un lenguaje completo orientado a registros y campos. Perfecto para datos tabulares (logs con columnas, CSVs sencillos, /proc/meminfo, etc.).

Modelo mental

awk 'patron { accion }' fichero

awk procesa línea a línea. Por cada línea que casa con el patrón, ejecuta la acción. Si no hay patrón, la acción se ejecuta en todas las líneas.

Variables automáticas

VariableSignificado
$0Línea completa
$1, $2, $NFCampo 1, 2, último campo
NFNúmero de campos en la línea actual
NRNúmero de línea (registro) actual
FSSeparador de campos (default: espacio/tab)

Separador con -F

# Campos separados por coma (CSV básico)
awk -F',' '{print $1, $3}' datos.csv
 
# Separador dos puntos (como /etc/passwd)
awk -F':' '{print $1, $3}' /etc/passwd
 
# Imprimir el último campo de cada línea
awk '{print $NF}' fichero.txt

Patrones y condiciones

# Líneas donde el campo 3 es mayor que 100
awk '$3 > 100 {print $0}' datos.txt
 
# Líneas que contienen "ERROR"
awk '/ERROR/ {print NR, $0}' app.log
 
# BEGIN y END: bloques especiales
awk 'BEGIN {suma=0} {suma += $2} END {print "Total:", suma}' ventas.txt

Ejemplos prácticos de awk

# Imprimir columnas 1 y 4 de ps aux
ps aux | awk '{print $1, $4}'
 
# Suma de la columna 5 de un fichero
awk '{suma += $5} END {print suma}' datos.txt
 
# Usuarios del sistema con UID >= 1000 (campo 3 de /etc/passwd)
awk -F':' '$3 >= 1000 {print $1, $3}' /etc/passwd
 
# Promedio de la columna 2
awk '{suma += $2; n++} END {print "Promedio:", suma/n}' datos.txt

Utilidades de columna y orden

cut — extraer columnas por posición o delimitador

cut -d',' -f1,3 datos.csv     # campos 1 y 3, separador coma
cut -d':' -f1 /etc/passwd     # solo nombres de usuario
cut -c1-10 fichero.txt        # caracteres 1 al 10 de cada línea

sort — ordenar líneas

sort fichero.txt              # alfabético
sort -r fichero.txt           # orden inverso
sort -n numeros.txt           # numérico
sort -k2 -n tabla.txt         # por campo 2, numérico
sort -u fichero.txt           # eliminar duplicados al ordenar

uniq — eliminar duplicados consecutivos (necesita sort previo)

sort fichero.txt | uniq          # eliminar duplicados
sort fichero.txt | uniq -c       # contar ocurrencias
sort fichero.txt | uniq -d       # solo líneas duplicadas

tr — traducir/borrar caracteres

echo "Hola Mundo" | tr 'a-z' 'A-Z'   # minúsculas a mayúsculas
echo "a:b:c" | tr ':' ','             # cambiar separador
cat fichero.txt | tr -d '\r'           # eliminar retornos de carro Windows
echo "aabbcc" | tr -s 'a-z'           # comprimir repetidos: "abc"

column — alinear en columnas

cat /etc/passwd | column -t -s':'    # tabla alineada, separador ':'
mount | column -t                    # salida de mount alineada

xargs — construir y ejecutar comandos desde stdin

# Borrar todos los .log que encuentra find
find . -name "*.log" | xargs rm
 
# Con espacios en nombres de fichero, usar null-separated
find . -name "*.log" -print0 | xargs -0 rm
 
# Ejecutar en paralelo (4 procesos)
cat urls.txt | xargs -P 4 wget
 
# Pasar argumento en posición específica con -I
cat ficheros.txt | xargs -I{} cp {} /backup/

Recetas de pipes reales

1. Los 10 procesos que más RAM consumen

ps aux --sort=-%mem | head -11 | awk '{printf "%-20s %s%%\n", $11, $4}'

2. Contar errores por tipo en un log

grep -oE "ERROR|WARN|FATAL" app.log | sort | uniq -c | sort -rn

3. Buscar y reemplazar en varios ficheros (con backup)

find . -name "*.py" -exec sed -i.bak 's/old_function/new_function/g' {} +

4. IPs que más veces aparecen en un log de acceso

awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

5. Ver qué puertos están escuchando (con nombres de proceso)

ss -tlnp | awk 'NR>1 {print $4, $6}' | column -t

6. Listar los ficheros más grandes de un directorio

find . -type f -printf "%s\t%p\n" | sort -rn | head -20 | awk '{printf "%.1f MB\t%s\n", $1/1048576, $2}'

7. Extraer todos los correos de un fichero

grep -oE "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" fichero.txt | sort -u

8. Monitorear en vivo errores mientras aparecen

tail -f /var/log/syslog | grep --line-buffered -i "error\|fail\|warn"

Nota: --line-buffered fuerza flush línea a línea en pipes con tail -f.


Tabla de referencia rápida

HerramientaPara quéComando típico
grepBuscar líneas por patróngrep -rn "patron" dir/
findBuscar ficherosfind . -name "*.py" -mtime -7
sedSustituir/borrar textosed -i 's/old/new/g' file
awkProcesar campos/columnasawk '{print $1, $NF}' file
cutExtraer columna por delimitadorcut -d',' -f2 csv
sortOrdenar líneassort -rn file
uniqContar/eliminar duplicadossort file | uniq -c
trTraducir caracterestr 'a-z' 'A-Z'
wcContar líneas/palabraswc -l file
xargsConstruir comandos desde stdinfind . -name "*.tmp" | xargs rm

Conexiones