← Terminal y Línea de Comandos en Linux Bloque 6 · Filtrar y transformar texto · Tema 30 de 43

Tema 30

Procesar columnas con awk

¿Cuánto vendió cada vendedor? ¿Cuál fue la venta más grande? ¿Qué ventas de Rosario superan los cincuenta pesos? Las tres preguntas tienen algo en común: no hablan de líneas ni de textos, sino de columnas y de números. cut recorta columnas pero no las entiende; grep y sed ven cada línea como una tira de caracteres. Falta una herramienta que mire un archivo como lo mira una planilla de cálculo: filas, columnas, y cuentas entre ellas. Es awk, y con ella se cierra el repertorio de filtros.

1. Un lenguaje para tablas

awk debe su nombre a las iniciales de sus tres autores —Aho, Weinberger y Kernighan—, que lo escribieron en 1977. No es un comando con opciones, como cut o sort: es un pequeño lenguaje de programación, pensado para una sola clase de tarea.

awk lee su entrada línea por línea, divide cada línea en campos, y ejecuta sobre ella un programa formado por reglas de la forma patrón { acción }: si la línea cumple el patrón, se ejecuta la acción.

anatomía de un programa de awk
awk -F, '$6 > 100 { print $2, $6 }' ventas.csv
     │   │         │               │
     │   │         │               └─ el archivo de entrada
     │   │         └─ la acción: qué hacer con las líneas elegidas
     │   └─ el patrón: a qué líneas se aplica
     └─ el separador de campos: una coma

Se lee así: «separando por comas; en las líneas cuyo sexto campo es mayor que 100, mostrar el segundo campo y el sexto». El programa completo va entre comillas simples, por la razón de siempre, y aquí con más motivo: está lleno de signos $ que la shell no debe tocar.

Las dos partes de una regla son optativas, y lo que ocurre al omitir cada una conviene memorizarlo:

Se escribeFaltaQué hace
patrón { acción }NadaEjecuta la acción en las líneas que cumplen el patrón.
{ acción }El patrónEjecuta la acción en todas las líneas.
patrónLa acciónMuestra las líneas que cumplen el patrón, enteras.

Es un filtro como los anteriores: lee de un archivo o de su entrada estándar, escribe en su salida y no modifica el archivo. La diferencia con sed está en lo que hace entre leer y escribir, y es lo que el resto del tema desarrolla: separa en campos, compara, calcula y recuerda.

Las tres etapas de un programa de awk: el bloque BEGIN se ejecuta una vez antes de leer; las reglas de patrón y acción se ejecutan una vez por cada línea de la entrada; el bloque END se ejecuta una vez al terminar BEGIN { … } una vez, antes patrón { acción } por cada línea END { … } una vez, al final la línea siguiente
Un programa de awk completo. La parte central es el ciclo de siempre; los bloques BEGIN y END, que veremos en la sección 8, son los que permiten preparar un informe y cerrarlo con un total.

Un aviso sobre versiones. En Linux hay varias implementaciones: gawk, la de GNU, y mawk, más pequeña y rápida, que es la que Ubuntu instala con el nombre awk. Todo lo de este tema pertenece al lenguaje común y funciona igual en las dos.

2. Los campos: $1, $2… y $0

Al leer una línea, awk la separa en campos y los deja disponibles con un nombre:

NombreContiene
$1, $2, $3…El primer campo, el segundo, el tercero…
$0La línea entera, tal como se leyó.
NFLa cantidad de campos de la línea (number of fields).
$NFEl último campo, tenga la línea los que tenga.

Sin indicación en contrario, el separador es el espacio en blanco: cualquier cantidad de espacios o tabulaciones seguidos. Empezamos con tareas.txt, donde cada palabra resulta ser un campo:

terminal, en ~/practica
$ cd ~/practica
$ awk '{print $1}' tareas.txt        # la primera palabra de cada línea
Comprar
Llamar
Pagar
Estudiar
Regar
$ awk '{print $NF}' tareas.txt       # la última
pan
Luis
luz
vim
plantas
$ awk '{print NF}' tareas.txt        # cuántas palabras tiene cada línea
2
3
3
2
3

print es la acción de escribir. Sin nada detrás, {print} escribe la línea entera: es lo mismo que {print $0}.

El signo $ no tiene aquí el significado que tiene en la shell. En awk es un operador que quiere decir «el campo número…», y lo que le sigue puede ser cualquier número. NF vale 3 en una línea de tres campos; $NF es entonces $3, el último. Por la misma cuenta, $(NF-1) es el anteúltimo.

La ventaja sobre cut: los espacios repetidos

En el tema 26 vimos que cut -d' ' fracasa con las salidas alineadas con varios espacios, como la de ls -l o ps: cada espacio cuenta como un separador y aparecen campos vacíos. Para awk, una tira de espacios es un separador, y los del comienzo de la línea se ignoran. ls -l | awk '{print $5, $9}' entrega el tamaño y el nombre sin más trámite. Es la razón por la que awk '{print $N}' es, probablemente, el programa de awk más escrito del mundo.

terminal
$ echo '   uno     dos   tres' | cut -d' ' -f2       # un campo vacío

$ echo '   uno     dos   tres' | awk '{print $2}'
dos

3. El separador: -F

Para los archivos cuyos campos no están separados por espacios, la opción -F, de field separator, indica cuál es el separador. Se escribe pegado a la opción: -F, para un CSV, -F: para /etc/passwd.

terminal
$ awk -F, '{print $2}' ventas.csv | head -n 3
Luis
Marta
Ana
$ awk -F: '{print $1}' /etc/passwd | head -n 3
root
daemon
bin
$ awk '{print $2}' ventas.csv | head -n 2        # sin -F: la línea entera es un solo campo

El tercer comando muestra el olvido más común. Sin -F, awk separa por espacios; en ventas.csv no hay ninguno, así que cada línea es un único campo, $1, y $2 está vacío. No hay mensaje de error: un campo que no existe vale simplemente una cadena vacía, y se imprimen diez líneas en blanco.

OpciónSepara porPara
NingunaEspacios y tabulaciones, en cualquier cantidad.Salidas de comandos; texto corriente.
-F,Una coma.Archivos CSV.
-F:Dos puntos./etc/passwd, /etc/group, $PATH.
-F'\t'Una tabulación, y solo ella.Archivos TSV, con campos que contienen espacios.
-F'[,;]'Una coma o un punto y coma.El separador es una expresión regular.

La última fila es una capacidad que cut no tiene: cuando el separador tiene más de un carácter, awk lo interpreta como una expresión regular del tema 28. -F', *' separa por una coma seguida de los espacios que haya.

4. print: la coma y el pegado

print acepta varios valores, y la manera de separarlos al escribir cambia el resultado. Hay que distinguir dos formas:

terminal
$ awk -F, '{print $2, $6}' ventas.csv | head -n 2      # con coma: separados por un espacio
Luis 90
Marta 320
$ awk -F, '{print $2 $6}' ventas.csv | head -n 2       # sin coma: pegados
Luis90
Marta320
$ awk -F, '{print $2 " vendió por $" $6}' ventas.csv | head -n 2
Luis vendió por $90
Marta vendió por $320
FormaQué hace
print a, bEscribe los dos valores con el separador de salida entre ellos: por defecto, un espacio.
print a bConcatena: los pega sin nada en el medio. En awk, poner dos valores uno al lado del otro es unirlos.
print a " - " bConcatena intercalando un texto. Los textos literales van entre comillas dobles.

Las comillas tienen entonces dos niveles, y no se mezclan: las simples rodean el programa, para la shell; las dobles rodean los textos, para awk.

Los campos se pueden escribir en cualquier orden y cuantas veces se quiera. Lo que cut no podía hacer, y que sed resolvía con tres grupos y tres retrorreferencias, aquí es inmediato:

terminal: reordenar columnas
$ awk -F, '{print $4, $3, $2}' ventas.csv | head -n 2
teclado Rosario Luis
monitor Mendoza Marta

El separador de salida se guarda en una variable, OFS, de output field separator. Para que el resultado siga siendo un CSV se le asigna una coma, con la opción -v que veremos en la sección 12:

terminal
$ awk -F, -v OFS=';' '{print $2, $4, $6}' ventas.csv | head -n 2
Luis;teclado;90
Marta;monitor;320

El separador de entrada y el de salida son independientes: -F dice cómo leer, OFS dice cómo escribir. Que al leer con comas se escriba con espacios desconcierta la primera vez, y es simplemente el valor por defecto de OFS.

5. Patrones: elegir las filas

El patrón es la condición que decide si la acción se ejecuta. Aquí aparece lo que ninguna herramienta anterior ofrecía: la condición puede referirse a un campo determinado, y puede ser una comparación.

terminal
$ awk -F, '$6 > 100' ventas.csv               # el importe es mayor que 100
2026-10-01,Marta,Mendoza,monitor,1,320
2026-10-03,Ana,Mendoza,monitor,2,640
2026-10-06,Luis,Rosario,monitor,1,320
$ awk -F, '$2 == "Ana"' ventas.csv            # el vendedor es Ana
2026-10-02,Ana,Salta,mouse,5,75
2026-10-03,Ana,Mendoza,monitor,2,640
2026-10-05,Ana,Rosario,cable,4,16
$ awk -F, '$5 == 10' ventas.csv               # la cantidad es 10
2026-10-02,Luis,Rosario,cable,10,40

Los tres programas son solo un patrón, sin acción: muestran las líneas elegidas. Y los tres responden con exactitud preguntas que costaron trabajo en los temas anteriores. La tercera es la del tema 27, donde grep 10 coincidía con todas las líneas por las fechas; en el tema 28 se resolvió con ^([^,]*,){4}10,. Ahora es $5 == 10: se dice el campo, y se dice el valor.

PatrónElige las líneas…
$6 > 100, $6 >= 100, $6 < 100Donde el campo 6 es mayor, mayor o igual, menor que 100.
$2 == "Ana", $2 != "Ana"Donde el campo 2 es, o no es, exactamente «Ana».
/Salta/Que coinciden con la expresión regular, en cualquier parte. Como grep.
$4 ~ /^m/Donde el campo 4 coincide con la expresión.
$4 !~ /^m/Donde el campo 4 no coincide.
A && B, A || B, !AQue cumplen las dos condiciones; alguna de las dos; que no cumplen la condición.
/inicio/,/fin/Desde una línea que coincide con la primera hasta la siguiente que coincide con la segunda. El rango de sed.

El operador ~, la virgulilla, se lee «coincide con». Es lo que permite aplicar una expresión regular a un solo campo, sin las acrobacias de contar comas. Las expresiones son siempre extendidas, y se escriben entre barras.

terminal: condiciones combinadas, y una acción
$ awk -F, '$3 == "Rosario" && $6 > 50 {print $2, $4, $6}' ventas.csv
Luis teclado 90
Luis monitor 320
$ awk -F, '$4 ~ /^m/ {print $4}' ventas.csv | sort | uniq -c    # productos que empiezan con m
      3 monitor
      3 mouse
Dos signos iguales para comparar, y comillas para los textos

Son los dos tropiezos del principiante. Primero: comparar es ==; un solo = es una asignación. $2 = "Ana" no pregunta nada: escribe «Ana» en el segundo campo de todas las líneas, y las muestra todas, cambiadas. Segundo: un texto sin comillas es el nombre de una variable. En $2 == Ana, Ana es una variable que nadie definió, vale una cadena vacía, y no se encuentra nada. En los dos casos awk no avisa: los programas son válidos, y hacen otra cosa.

6. NR y NF: contar líneas y campos

Además de los campos, awk mantiene al día unas variables propias. Sus nombres van en mayúsculas y se usan sin signo $:

VariableContiene
NREl número de la línea actual (number of record). Al terminar, la cantidad de líneas leídas.
NFLa cantidad de campos de la línea actual.
FSEl separador de entrada. Es lo que fija -F.
OFSEl separador de salida de print. Por defecto, un espacio.
FILENAMEEl nombre del archivo que se está leyendo.
terminal
$ awk -F, '{print NR, NF, $2}' ventas.csv | head -n 3   # número de línea, campos, vendedor
1 6 Luis
2 6 Marta
3 6 Ana
$ awk 'NR == 3' tareas.txt                    # solo la línea 3
Pagar la luz
$ awk 'NR >= 2 && NR <= 4' tareas.txt         # de la 2 a la 4
Llamar a Luis
Pagar la luz
Estudiar vim
$ awk 'NF > 2' tareas.txt                     # las de más de dos palabras
Llamar a Luis
Pagar la luz
Regar las plantas

NR en un patrón hace el trabajo de las direcciones numéricas de sed. El patrón más usado de todos es NR > 1, «todas menos la primera»: saltea la fila de títulos de un archivo de datos, para que no entre en las cuentas.

NF en un patrón sirve para dos cosas. Como condición numérica, detecta líneas mal formadas: awk -F, 'NF != 6' ventas.csv muestra las que no tienen exactamente seis campos, y en nuestro archivo no muestra nada, que es la buena noticia. Y solo, como patrón, awk 'NF' archivo elimina las líneas vacías: en ellas NF vale cero, y el cero cuenta como falso.

7. Calcular con los campos

Un campo que contiene un número puede usarse en una cuenta, sin ningún trámite previo. Los operadores son los habituales: +, -, *, /, % para el resto de la división, y paréntesis para agrupar.

terminal: el precio de cada unidad, y el importe con un 21 % de recargo
$ awk -F, '{print $4, $6 / $5}' ventas.csv | head -n 4
teclado 45
monitor 320
mouse 15
cable 4
$ awk -F, '{print $2, $6, $6 * 1.21}' ventas.csv | head -n 3
Luis 90 108.9
Marta 320 387.2
Ana 75 90.75

El archivo guarda la cantidad y el importe de cada venta; el precio por unidad no está en ninguna parte. awk lo obtuvo dividiendo una columna por otra: produjo una columna nueva, calculada, que es lo que en una planilla se haría con una fórmula.

Los números de awk tienen decimales, a diferencia de la aritmética $(( )) de Bash, que solo conoce enteros. Por eso awk es también la calculadora de mano de la terminal: awk 'BEGIN {print 10 / 3}' responde 3.33333.

ReglaConsecuencia
Un mismo valor es número o texto según cómo se lo use.No se declaran tipos: $6 * 2 lo trata como número; $6 "x", como texto.
Un campo con forma de número se compara como número.$6 > 100 acepta 320 y rechaza 90. No hace falta nada como el -n de sort.
Un texto que no es un número vale cero en una cuenta.La fila de títulos suma cero, y no produce error. También un dato mal escrito, sin aviso.
Una variable que nunca se usó vale cero, o una cadena vacía.No hay que inicializar nada. Es lo que permite los programas de una línea de la sección siguiente.

8. BEGIN y END: sumar y contar

Hasta aquí cada línea se procesó y se olvidó, como en sed. Lo que distingue a awk es que tiene variables que conservan su valor de una línea a la siguiente. Con eso se puede acumular. Y para mostrar el resultado acumulado hace falta un lugar que se ejecute una sola vez, cuando ya no quedan líneas.

BEGIN y END son dos patrones especiales. La acción de BEGIN se ejecuta una vez, antes de leer la primera línea; la de END, una vez, después de la última.

terminal: el total vendido
$ awk -F, '{total += $6} END {print total}' ventas.csv
1621
el programa, leído
{total += $6}        en cada línea: sumar el campo 6 a la variable total
END {print total}    al terminar: mostrar lo acumulado

El programa tiene dos reglas. La primera no tiene patrón, de modo que se ejecuta en las diez líneas; el operador += suma a la variable lo que tiene a la derecha. La variable total no se declaró ni se puso en cero: la primera vez que se la usa vale cero. La segunda regla muestra el resultado. Sumar una columna, que con los filtros del tema 26 era imposible, cabe en media línea.

Con el mismo esquema se obtienen las demás medidas de una columna:

terminal
$ awk -F, '{s += $6} END {print "Ventas:", NR; print "Total:", s; print "Promedio:", s / NR}' ventas.csv
Ventas: 10
Total: 1621
Promedio: 162.1
$ awk -F, '$3 == "Rosario" {n++} END {print n}' ventas.csv      # contar las que cumplen
5
$ awk -F, '$6 > max {max = $6; quien = $2} END {print quien, max}' ventas.csv   # el máximo
Ana 640

Tres observaciones. Dentro de una acción, las instrucciones se separan con punto y coma. En el bloque END, NR conserva el número de la última línea leída, que es la cantidad de líneas: awk 'END {print NR}' archivo es wc -l. Y n++ suma uno a la variable; es la manera de contar.

El tercer programa encuentra el máximo con la técnica de siempre: se recuerda el mayor visto hasta el momento, y cada línea que lo supera lo reemplaza. De paso se guarda quién lo vendió, cosa que sort | head también resolvería, pero ordenando todo el archivo para quedarse con una línea.

BEGIN sirve para lo que debe ocurrir antes de empezar: escribir un encabezado, o fijar los separadores dentro del propio programa en lugar de hacerlo con opciones.

terminal: un programa con las tres partes
$ awk 'BEGIN {FS = ","; print "VENDEDOR IMPORTE"} $3 == "Salta" {print $2, $6; s += $6} END {print "TOTAL", s}' ventas.csv
VENDEDOR IMPORTE
Ana 75
Luis 45
TOTAL 120

Un programa puede tener varias reglas con patrones distintos. Cada línea se confronta con todas, en orden, y ejecuta las acciones de las que cumpla: /Salta/ {s++} /Mendoza/ {m++} END {print s, m} lleva dos cuentas en una sola pasada.

9. printf: dar formato

print escribe los valores como vienen, y el resultado queda desalineado: los nombres tienen largos distintos, y los números, cantidades distintas de cifras. Para un informe prolijo existe printf, que escribe según un molde.

anatomía de un printf
printf "%-8s %6.2f\n", $2, $6
        │    │    │   │   │
        │    │    │   └───┴─ los valores, en el orden de los moldes
        │    │    └─ el salto de línea: printf no lo agrega solo
        │    └─ un número, en 6 lugares, con 2 decimales
        └─ un texto, en 8 lugares, alineado a la izquierda

El primer argumento es el molde, entre comillas dobles. Cada marca que empieza con % es un hueco, y se rellena con los valores que siguen, en orden. Todo lo demás se copia tal cual.

MarcaEscribeCon el valorResultado
%sUn texto.AnaAna
%8sUn texto en 8 lugares, alineado a la derecha.Ana Ana
%-8sUn texto en 8 lugares, alineado a la izquierda.AnaAna
%dUn número entero.162.1162
%5dUn entero en 5 lugares.75 75
%.2fUn número con 2 decimales.162.1162.10
%8.2fLo mismo, en 8 lugares.162.1 162.10
%%Un signo de porcentaje.—%
terminal
$ awk -F, '{printf "%-8s %-8s %6.2f\n", $2, $4, $6}' ventas.csv | head -n 3
Luis     teclado   90.00
Marta    monitor  320.00
Ana      mouse     75.00

Las columnas quedaron alineadas: los textos a la izquierda, los números a la derecha y con sus decimales encolumnados, como en cualquier listado contable. La regla práctica es esa: %-Ns para los textos, %N.2f o %Nd para los números.

El \n final no es opcional. print termina cada línea por su cuenta; printf escribe exactamente lo que el molde dice, y sin el salto todas las líneas quedan pegadas en una sola. Es la misma orden printf que tiene la shell, con una diferencia de escritura: en awk, los argumentos se separan con comas.

10. Arreglos: agrupar y totalizar

La sección 8 calculó un total general. La pregunta con que abrimos el tema era otra: cuánto vendió cada vendedor. Haría falta una variable por vendedor, y no se sabe de antemano cuántos hay ni cómo se llaman. Para eso awk tiene su estructura más poderosa.

Un arreglo asociativo es una variable que guarda muchos valores, cada uno identificado por una clave que puede ser cualquier texto. Se escribe nombre[clave].

Si la clave es un campo, el arreglo abre por sí solo un casillero para cada valor distinto que ese campo tome:

terminal: el importe total de cada vendedor
$ awk -F, '{total[$2] += $6} END {for (v in total) print v, total[v]}' ventas.csv | sort
Ana 731
Luis 495
Marta 395
el programa, leído
{total[$2] += $6}        en cada línea: sumar el importe al casillero de ese vendedor
END {                    al terminar:
  for (v in total)         por cada clave v que haya en el arreglo
    print v, total[v]      mostrar la clave y lo acumulado
}

Al leer la primera línea, $2 vale «Luis» y la acción hace total["Luis"] += 90. En la segunda, total["Marta"] += 320. En la cuarta vuelve a aparecer Luis, y su casillero pasa de 90 a 130. Al final hay tres casilleros, uno por vendedor, sin que el programa mencione ningún nombre. Con un vendedor nuevo en el archivo, aparecería un cuarto.

La construcción for (v in total) recorre las claves del arreglo, poniendo cada una por turno en la variable v. Tiene una particularidad:

El recorrido de un arreglo no tiene orden

for (clave in arreglo) entrega las claves en un orden que no es el alfabético ni el de aparición, y que cambia de una implementación de awk a otra. Si el orden importa —y en un informe siempre importa—, la salida se pasa por sort. Es la división de tareas de toda tubería: awk calcula, sort ordena.

Para contar en lugar de sumar, se incrementa el casillero en uno. Es la tubería cut | sort | uniq -c del tema 26, en un solo programa:

terminal: cuántas ventas hubo en cada ciudad
$ awk -F, '{n[$3]++} END {for (c in n) print n[c], c}' ventas.csv | sort -rn
5 Rosario
3 Mendoza
2 Salta

Y se pueden llevar varios arreglos a la vez, con la misma clave. Reuniendo todo lo visto —arreglos, END, printf— sale el informe completo por vendedor: cantidad de ventas, importe total y promedio por venta, ordenado de mayor a menor importe.

terminal: el informe por vendedor
$ awk -F, '{n[$2]++; t[$2] += $6}
> END {for (v in t) printf "%-8s %3d %8.2f %8.2f\n", v, n[v], t[v], t[v] / n[v]}' ventas.csv | sort -k3 -rn
Ana        3   731.00   243.67
Luis       4   495.00   123.75
Marta      3   395.00   131.67

Mientras las comillas simples están abiertas, la shell deja continuar el programa en la línea siguiente, con el indicador >. Luis hizo más ventas, pero Ana vendió más, y por un importe medio que duplica al de sus compañeros. El archivo lo decía desde el principio; hacía falta una herramienta que supiera agrupar para leerlo.

11. Condiciones, funciones y campos modificados

Dentro de una acción se dispone de un lenguaje de programación entero, con sintaxis heredada de C. Tres de sus piezas alcanzan para casi todo el uso diario.

La primera es la condición. El patrón decide si la acción se ejecuta; if decide dentro de la acción, lo que permite tratar todas las líneas pero de manera distinta:

terminal: clasificar cada venta
$ awk -F, '{if ($6 >= 100) tipo = "grande"; else tipo = "chica"; print $2, $6, tipo}' ventas.csv | head -n 3
Luis 90 chica
Marta 320 grande
Ana 75 chica

La segunda son las funciones incorporadas, que trabajan sobre textos y números:

FunciónDevuelveEjemploResultado
length(t)La cantidad de caracteres.length("Rosario")7
toupper(t), tolower(t)El texto en mayúsculas; en minúsculas.toupper("Ana")ANA
substr(t, i, n)n caracteres desde la posición i. Sin n, hasta el final.substr("2026-10-01", 6, 2)10
index(t, x)La posición de x dentro de t, o cero.index("Rosario", "sa")3
sub(/r/, x, t), gsub(/r/, x, t)Sustituyen en t la primera coincidencia; todas. Sin t, actúan sobre la línea.gsub(/-/, "/", $1)2026/10/01
int(n)La parte entera.int(162.1)162
terminal
$ awk 'length($0) > 12' tareas.txt            # las líneas de más de 12 caracteres
Llamar a Luis
Regar las plantas
$ awk -F, '{print substr($1, 9), toupper($2)}' ventas.csv | head -n 2   # el día, y el nombre
01 LUIS
01 MARTA

sub y gsub son el s/// y el s///g de sed, con una ventaja: el tercer argumento los limita a un campo. «Cambiar los guiones por barras, pero solo en la fecha» es gsub(/-/, "/", $1).

La tercera pieza es que los campos son variables, y se les puede asignar. Al cambiar un campo, awk vuelve a armar la línea, $0, uniendo todos los campos con el separador de salida. De ahí una consecuencia que hay que prever:

terminal: poner la ciudad en mayúsculas
$ awk -F, '{$3 = toupper($3); print}' ventas.csv | head -n 1            # se perdieron las comas
2026-10-01 Luis ROSARIO teclado 2 90
$ awk -F, -v OFS=, '{$3 = toupper($3); print}' ventas.csv | head -n 1   # con OFS
2026-10-01,Luis,ROSARIO,teclado,2,90

Para modificar una columna de un archivo delimitado y conservar su formato, hay que fijar los dos separadores con el mismo valor. Dentro del programa se escribe BEGIN {FS = OFS = ","}. Con esa precaución se agrega también una columna nueva: {print $0, $6 / $5} añade al final el precio por unidad.

12. -v y -f: valores y programas

Las comillas simples que protegen el programa tienen un costo: dentro de ellas la shell no expande sus variables. Si el nombre a buscar está en una variable de Bash, '$2 == "$nombre"' no funciona: awk recibe el texto $nombre tal cual. Es el mismo problema que tuvimos con sed, y aquí la solución no es cambiar de comillas, sino usar una opción hecha para eso.

La opción -v nombre=valor crea, antes de empezar, una variable de awk con ese valor. Es la manera de pasarle datos al programa desde la shell.

terminal
$ nombre=Marta
$ awk -F, -v quien="$nombre" '$2 == quien {s += $6} END {print quien, s}' ventas.csv
Marta 395
$ awk -F, -v minimo=300 '$6 >= minimo {print $2, $6}' ventas.csv
Marta 320
Ana 640
Luis 320

La expansión de $nombre ocurre fuera del programa, en un argumento entre comillas dobles, donde la shell sí actúa. Dentro, quien es una variable de awk y se usa sin signo $. Así el programa queda fijo y protegido, y lo que cambia viaja por un canal aparte. La opción se puede repetir, y ya la usamos para OFS.

Cuando un programa crece más allá de una línea, conviene guardarlo en un archivo y ejecutarlo con -f. Allí se lo puede escribir con sangría, una instrucción por línea y comentarios, que empiezan con #. Con nano creamos ciudades.awk:

el archivo ciudades.awk
# Informe de importes por ciudad
BEGIN {
    FS = ","
    printf "%-10s %8s\n", "Ciudad", "Importe"
}

{
    total[$3] += $6
    suma += $6
}

END {
    for (c in total)
        printf "%-10s %8.2f\n", c, total[c]
    printf "%-10s %8.2f\n", "TOTAL", suma
}
terminal
$ awk -f ciudades.awk ventas.csv
Ciudad      Importe
Rosario      511.00
Mendoza      990.00
Salta        120.00
TOTAL       1621.00

Dentro del archivo no hay comillas simples ni nada que proteger de la shell: es awk puro. Las tres ciudades pueden aparecer en otro orden, por lo dicho sobre los arreglos. Es el mismo programa que habría cabido, apretado, en una línea; escrito así se puede leer, corregir y volver a usar el mes siguiente con otro archivo de ventas. Una regla debe respetarse al repartir el programa en líneas: la llave que abre una acción va en la misma línea que su patrón.

13. awk entre las otras herramientas

awk puede hacer casi todo lo que hacen los demás filtros, y eso tienta a usarlo para todo. El criterio es el del tema anterior: cuando una herramienta más simple dice lo mismo, se lee mejor.

TareaCon awkCon otra herramientaConviene
Mostrar las líneas que coincidenawk '/x/'grep 'x'grep
Una columna de un CSVawk -F, '{print $2}'cut -d, -f2cut
Una columna de una salida alineada con espaciosawk '{print $2}'tr -s ' ' | cut -d' ' -f2awk
Cambiar un texto por otroawk '{gsub(/x/, "y"); print}'sed 's/x/y/g'sed
Contar líneasawk 'END {print NR}'wc -lwc
Contar por categoríaawk '{n[$1]++} END {…}'cut … | sort | uniq -cCualquiera
Filtrar por el valor de una columnaawk -F, '$6 > 100'—awk
Reordenar columnasawk '{print $3, $1}'sed con gruposawk
Sumar, promediar, totalizar por grupoawk '{s += $1} END {…}'—awk
Dar formato de informeawk '{printf …}'—awk
Ordenar—sortsort

El territorio propio de awk son las últimas filas en negrita: todo lo que exige entender las columnas como datos. Con eso el bloque queda completo, y cada herramienta tiene su pregunta:

HerramientaVe el texto comoResponde a
grepLíneas que coinciden o no.¿Qué líneas?
sedLíneas de caracteres que se pueden editar.¿Qué cambio?
awkFilas y columnas de datos.¿Qué columnas, y cuánto suman?
sort, uniq, cut, trCada una, una sola operación.¿En qué orden, cuántas veces?

awk tiene también su límite, y es el de la herramienta siguiente en la escala. Un programa de cincuenta líneas, con datos de varios archivos y estructuras complejas, está pidiendo un lenguaje de propósito general, como Python. Y hay un caso que parece de awk y no lo es: los CSV «de verdad», donde un campo entre comillas puede contener comas, como "García, Ana". -F, corta en todas las comas, sin mirar las comillas.

14. Recetas y errores comunes

Quiero…Comando
La segunda columna de la salida de un comandocomando | awk '{print $2}'
La última columna de cada líneaawk '{print $NF}' archivo
Las filas donde una columna supera un valorawk -F, '$6 > 100' archivo
Las filas donde una columna es exactamente un textoawk -F, '$2 == "Ana"' archivo
Saltear la fila de títulosawk -F, 'NR > 1 {…}' archivo
Sumar una columnaawk -F, '{s += $6} END {print s}' archivo
El promedio de una columnaawk -F, '{s += $6} END {print s / NR}' archivo
El total por categoríaawk -F, '{t[$2] += $6} END {for (k in t) print k, t[k]}' archivo
El tamaño total de los archivos de un directoriols -l | awk '{s += $5} END {print s}'
Quitar las líneas vacíasawk 'NF' archivo
Quitar las líneas repetidas, sin ordenarawk '!visto[$0]++' archivo
Los usuarios que usan Bashawk -F: '$7 ~ /bash$/ {print $1}' /etc/passwd

La penúltima receta es célebre, y se entiende con lo visto. visto[$0] es un casillero por cada línea distinta. La primera vez que aparece una línea vale cero; el ! lo niega, el patrón resulta verdadero y la línea se muestra; después el ++ lo deja en uno. La segunda vez ya no es cero, y la línea no pasa. A diferencia de sort -u, conserva el orden original.

SíntomaCausaSolución
Salen líneas vacías, o la línea entera en $1.Falta -F: se está separando por espacios.awk -F,
Los campos salen vacíos y hay errores extraños.El programa está entre comillas dobles: la shell reemplazó $1, $2.Comillas simples.
$2 == Ana no encuentra nada.Sin comillas, Ana es una variable vacía.$2 == "Ana"
Se muestran todas las líneas, con un campo cambiado.Un = donde iba ==: asignó en lugar de comparar.==
Los valores salen pegados.Falta la coma en print.print $1, $2
Al modificar un campo, las comas se volvieron espacios.OFS sigue siendo un espacio.-v OFS=,
Toda la salida quedó en una sola línea.printf sin \n.Terminar el molde con \n.
El informe sale en otro orden cada vez, o en otra máquina.for (k in arreglo) no tiene orden.Agregar | sort.
division by zeroUn promedio sobre una entrada vacía, o un campo que vale cero.END {if (NR > 0) print s / NR}
Una variable de la shell no se reemplaza.Las comillas simples lo impiden.-v nombre="$variable"
El total incluye un dato de más, o falta uno.La fila de títulos, o una línea vacía al final.NR > 1; NF.

La referencia del tema:

Se escribeQué es
awk 'patrón {acción}' archivoLa forma general.
-F,El separador de campos de entrada.
$1, $2, $NF, $0Un campo; el último; la línea entera.
NR, NFEl número de línea; la cantidad de campos.
print a, bEscribe los valores separados por OFS.
printf "%-8s %6.2f\n", a, bEscribe con formato.
$6 > 100, $2 == "x", $4 ~ /x/Patrones sobre un campo.
&&, ||, !Y, o, no.
BEGIN {…}, END {…}Antes de la primera línea; después de la última.
s += $6, n++Acumular; contar.
t[$2] += $6, for (k in t)Acumular por clave; recorrer las claves.
-v nombre=valor, -f programa.awkPasar un valor; leer el programa de un archivo.

En el directorio de práctica quedó un archivo nuevo, ciudades.awk, que conviene conservar. Hasta aquí todos los filtros trabajaron sobre un archivo a la vez. En el tema 31 cerramos el bloque con las herramientas que miran dos: diff para ver qué cambió entre uno y otro, y join para cruzar dos listas.

15. Resumen

16. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Qué forma tiene una regla de awk, y qué ocurre si falta cada una de sus partes?

patrón { acción }. Sin patrón, la acción se ejecuta en todas las líneas. Sin acción, se muestran enteras las líneas que cumplen el patrón.

2. ¿Qué contienen $0, $3, NF y $NF?

La línea entera; el tercer campo; la cantidad de campos de la línea; y el último campo.

3. awk '{print $2}' ventas.csv muestra líneas vacías. ¿Por qué?

Porque falta -F,. Sin esa opción se separa por espacios, cada línea es un solo campo y $2 está vacío.

4. ¿Por qué awk '{print $2}' funciona con la salida de ls -l y cut -d' ' -f2 no?

Porque awk toma cualquier cantidad de espacios seguidos como un único separador, mientras que para cut cada espacio separa un campo, y aparecen campos vacíos.

5. ¿Qué diferencia hay entre print $1, $2 y print $1 $2?

Con la coma, los valores se escriben separados por OFS, por defecto un espacio. Sin la coma se concatenan, pegados.

6. ¿Qué muestra awk -F, '$6 < 50' ventas.csv?

Las líneas cuyo sexto campo, el importe, es menor que 50, comparado como número.

7. ¿Qué hace awk -F, '$2 = "Ana"' ventas.csv, y qué se quiso escribir?

Asigna «Ana» al segundo campo de todas las líneas y las muestra todas, modificadas. Se quiso comparar: $2 == "Ana".

8. ¿Qué diferencia hay entre /Ana/ y $2 ~ /Ana/ como patrones?

El primero elige las líneas que contienen «Ana» en cualquier parte. El segundo, solo aquellas donde lo contiene el segundo campo.

9. ¿Qué valen NR y NF, y qué hace el patrón NR > 1?

NR es el número de la línea actual y NF la cantidad de campos que tiene. NR > 1 elige todas las líneas menos la primera: saltea el encabezado.

10. ¿Cuándo se ejecutan las acciones de BEGIN y de END?

La de BEGIN, una vez antes de leer la primera línea. La de END, una vez después de procesar la última.

11. Escribe el comando que suma la quinta columna de ventas.csv.

awk -F, '{s += $5} END {print s}' ventas.csv. El resultado es 31.

12. ¿Qué escribe printf "%-6s|%5.1f|\n", "Ana", 7.5?

Ana | 7.5| y un salto de línea: el texto en seis lugares alineado a la izquierda, y el número en cinco lugares con un decimal.

13. ¿Qué hace {n[$4]++} END {for (p in n) print p, n[p]}?

Cuenta cuántas líneas hay para cada valor distinto del cuarto campo, y al terminar muestra cada valor con su cantidad. En ventas.csv, las ventas de cada producto.

14. ¿Por qué conviene agregar | sort después de un programa que recorre un arreglo?

Porque for (clave in arreglo) entrega las claves en un orden indefinido, que puede cambiar entre implementaciones de awk.

15. ¿Cómo se usa en un programa de awk el valor de una variable de la shell?

Pasándolo con -v: awk -v limite="$max" '$6 > limite' archivo. Dentro del programa se usa el nombre sin signo $.

Para practicar

  1. Con ventas.csv, muestra: solo el producto y la cantidad; la fecha y el importe separados por una tabulación (OFS='\t'); y cada línea precedida de su número.
  2. Selecciona las ventas de más de 2 unidades; las de Marta en Mendoza; las que no son de monitores; y las de los días 1 a 3 cuyo importe está entre 40 y 100.
  3. Calcula la cantidad total de unidades vendidas, el importe promedio de las ventas de Rosario, y la venta de menor importe con su vendedor. Para el mínimo necesitarás tratar aparte la primera línea: NR == 1 || $6 < min.
  4. Obtén el importe total por producto, ordenado de mayor a menor, y comprueba que los cuatro totales suman 1621.
  5. Cuenta las ventas de cada día de dos maneras: con cut | sort | uniq -c y con un arreglo de awk. Compara los resultados.
  6. Con printf, arma un listado alineado con el vendedor, el producto, la cantidad, el precio por unidad y el importe, con una fila de títulos escrita en BEGIN y una de totales en END.
  7. Guarda el programa anterior en listado.awk y ejecútalo con -f. Agrégale un comentario en cada bloque.
  8. En /etc/passwd: muestra el nombre y el directorio personal de las cuentas con UID mayor o igual que 1000; cuenta cuántas cuentas usan cada shell; y encuentra el UID más alto.
  9. Con ls -l /etc | awk, calcula el tamaño total de los archivos, muestra los tres más grandes con su nombre, y cuenta cuántas entradas son directorios ($1 ~ /^d/).
  10. Prueba awk -F, '$2 == Luis' ventas.csv, awk -F, "{print $2}" ventas.csv y awk -F, '{printf "%s", $2}' ventas.csv. Explica cada resultado y corrige los tres.