Cuando tienes un archivo con columnas y necesitas algo más que filtrar líneas, grep y cut se quedan cortos. awk es un lenguaje de programación completo orientado a texto estructurado: tiene variables, aritmética, arrays, condicionales y bucles. No es un comando con opciones, es un intérprete que ejecuta un programa pequeño contra cada línea de la entrada.
El modelo de ejecución es sencillo pero poderoso: awk lee la entrada línea a línea y para cada una evalúa tu programa, que tiene la forma patrón { acción }. Si el patrón coincide, ejecuta la acción. Si omites el patrón, la acción se ejecuta en todas las líneas. Eso es el núcleo del lenguaje.
Cada línea que awk procesa se divide automáticamente en campos. Por defecto el separador es cualquier cantidad de espacios o tabulaciones (los agrupa, así que dos espacios cuentan igual que uno). El campo 1 es $1, el 2 es $2, y $0 es la línea completa sin tocar. Dos variables de contexto siempre disponibles: NF (number of fields) es cuántos campos tiene la línea actual, y NR (number of record) es el número de línea que llevas procesando, empezando en 1.
Para separadores distintos usas -F. Un -F: le dice a awk que use : como delimitador, lo que convierte /etc/passwd en algo fácil de diseccionar. Para archivos CSV con comas usarías -F,, y así.
Los bloques especiales BEGIN { } y END { } no están ligados a ninguna línea: BEGIN corre antes de leer la primera línea de entrada —útil para inicializar variables o imprimir cabeceras— y END corre después de la última —donde calculas y muestras resúmenes, totales, medias.
Usas awk cuando necesitas lógica que va más allá del filtrado: sumar una columna, contar ocurrencias, reformatear campos, cruzar valores entre líneas. Si te encuentras encadenando cut, tr, sort y uniq para algo que sigue sin funcionar del todo, probablemente la solución correcta es un programa awk de cinco líneas. Lo que se rompe cuando lo usas mal es sutil: olvidar que $0 incluye el separador original, asumir que los campos están delimitados por un solo espacio cuando hay tabulaciones mezcladas, o intentar comparar números guardados como strings sin forzar la conversión aritmética.
Ejemplo: analizar un log de acceso web
Supón que tienes un log en formato combinado de nginx/Apache y quieres tres cosas a la vez: el total de bytes transferidos, cuántas peticiones devolvió cada código HTTP, y detectar si alguna línea tiene un campo de bytes inválido (- en lugar de número).
# Archivo de ejemplo: /var/log/nginx/access.log (formato Combined Log)
# Cada línea tiene este aspecto:
# 192.168.1.10 - - [12/Jun/2025:10:23:01 +0000] "GET /api/data HTTP/1.1" 200 4321 "-" "curl/7.88"
# Los campos que nos interesan: $9 = código HTTP, $10 = bytes enviados
awk '
BEGIN {
total_bytes = 0
print "Analizando log..."
}
# Ignorar líneas vacías o comentarios
/^#/ { next }
NF < 10 { next }
{
codigo = $9
bytes = $10
# $10 puede ser "-" cuando no hubo cuerpo de respuesta (304, etc.)
if (bytes ~ /^[0-9]+$/) {
total_bytes += bytes
} else {
# Registrar líneas con valor no numérico para revisión
print "AVISO: bytes no numérico en línea " NR ": " $0 > "/dev/stderr"
}
# Acumular peticiones por código de estado
conteo[codigo]++
}
END {
print "\n=== Resumen ==="
print "Total bytes transferidos:", total_bytes
print "\nPeticiones por código HTTP:"
for (codigo in conteo) {
printf " %s -> %d peticiones\n", codigo, conteo[codigo]
}
}
' /var/log/nginx/access.log
Lo que está pasando línea a línea
BEGIN inicializa total_bytes a cero explícitamente. En awk las variables no inicializadas valen 0 o "" según el contexto, pero ser explícito evita confusión cuando el programa crece.
La línea /^#/ { next } usa una expresión regular como patrón: si la línea empieza por #, salta a la siguiente. NF < 10 { next } descarta líneas con menos de diez campos —protege el bloque principal de errores de parsing si el log tiene entradas truncadas o malformadas.
Dentro del bloque sin patrón { ... }, el operador ~ hace coincidencia de regex. bytes ~ /^[0-9]+$/ comprueba que el campo sea un entero puro antes de sumarlo; si no lo es, redirige el aviso a stderr con > "/dev/stderr". Esa redirección dentro de awk funciona exactamente igual que en el shell.
conteo[codigo]++ es un array asociativo: la clave es el string del código HTTP ("200", "404", "304"…) y el valor se incrementa en uno cada vez que aparece. No hace falta declarar el array ni sus claves, awk los crea al vuelo. En END, el bucle for (codigo in conteo) itera sobre todas las claves que se hayan acumulado durante el procesamiento.
printf dentro del END usa el mismo formato que C: %s para strings, %d para enteros. Aquí garantiza que las columnas del resumen queden alineadas independientemente de la longitud del código.
Un detalle que vale la pena notar: el programa completo va entre comillas simples en el shell para que bash no interprete $9, $10 ni $0 como variables del shell. Es el error más frecuente cuando alguien empieza a escribir programas awk en una línea y se pregunta por qué $1 está vacío.
N° 52