Comandes LinuxLinuxVeure el contingut d'un fitxer a Linux

La comanda awk a Linux: processar text per columnes, amb exemples



awk és una eina per processar text línia a línia i columna a columna: busca les línies que compleixen una condició i hi fa alguna cosa (mostrar-ne uns camps, sumar, comptar, reformatar…). De fet, és un petit llenguatge de programació, però la major part de les vegades n’hi ha prou amb una sola línia. És imprescindible per treure dades de la sortida d’altres comandes, de fitxers de registre o de fitxers CSV.

Com funciona

Un programa d’awk és una llista de regles amb aquesta forma:

patró { acció }

awk llegeix l’entrada línia a línia (cada línia és un registre) i la divideix en camps separats per espais o tabuladors. Per a cada línia, comprova els patrons i, si es compleixen, n’executa l’acció.

  • Si no hi ha patró, l’acció s’aplica a totes les línies.
  • Si no hi ha acció, es mostra la línia sencera ({ print }).
  • BEGIN { … } s’executa abans de llegir res, i END { … } quan s’ha acabat l’entrada.

La sintaxi bàsica és:

awk [opcions] 'programa' [fitxer...]
comanda | awk 'programa'

Posa sempre el programa entre cometes simples, perquè el shell no interpreti els $1, $2…

Variables i opcions bàsiques

ElementQuè és
$0La línia sencera
$1, $2…El primer camp, el segon…
$NFL’últim camp
NFNombre de camps de la línia
NRNúmero de línia (comptant totes les línies llegides)
FNRNúmero de línia dins del fitxer actual
FS / -FSeparador de camps d’entrada (per defecte, espais i tabuladors)
OFSSeparador de camps de sortida (per defecte, un espai)
-v var=valorPassa una variable al programa
-f fitxer.awkLlegeix el programa d’un fitxer

A Debian i Ubuntu, la comanda awk és per defecte mawk, una implementació ràpida i lleugera. A Rocky, Fedora i altres, és gawk (GNU awk), que té més funcions. Tots els exemples d’aquest article funcionen amb totes dues.

Fitxer d’exemple

Per als exemples farem servir vendes.txt:

Anna nord 120
Pere sud 80
Joan nord 45
Marta est 200
Pere sud 80

Filtrar línies

awk '/nord/' vendes.txt                  # línies que contenen «nord»
awk '!/nord/' vendes.txt                 # línies que NO el contenen
awk '/nord/ && /Anna/' vendes.txt        # contenen tots dos patrons
awk '/est/ || /sud/' vendes.txt          # contenen l'un o l'altre
awk '/^A/' vendes.txt                    # comencen per «A»
awk '/0$/' vendes.txt                    # acaben en «0»
awk '/[0-9]/' fitxer.txt                 # contenen algun número

Fins aquí, awk fa el mateix que grep. La diferència és que també pots posar condicions sobre un camp concret:

$ awk '$3 > 100' vendes.txt
Anna nord 120
Marta est 200

$ awk '$2 == "sud" {print $1, $3}' vendes.txt
Pere 80
Pere 80

Altres filtres útils:

awk 'length($0) > 12' vendes.txt         # línies de més de 12 caràcters
awk 'NR==2, NR==4' vendes.txt            # de la línia 2 a la 4
awk '/inici/, /fi/' fitxer.txt           # des de la línia amb «inici» fins a la línia amb «fi»
awk NF fitxer.txt                        # treu les línies buides
awk '/^[[:alpha:]]+$/' fitxer.txt        # línies només amb lletres
awk '!/[^[:print:]]/' fitxer.txt         # treu les línies amb caràcters no imprimibles

Seleccionar i reformatar columnes

$ awk '{print $1}' vendes.txt
Anna
Pere
Joan
Marta
Pere
awk '{print $NF}' vendes.txt             # l'últim camp
awk '{print NR": "$0}' vendes.txt        # numera les línies
awk '{print toupper($1)}' vendes.txt     # en majúscules (tolower per a minúscules)
awk '{print length}' fitxer.txt          # nombre de caràcters de cada línia
awk '{print $1 * $3}' fitxer.txt         # multiplica dues columnes

Invertir l’ordre dels camps de cada línia:

$ awk '{for (i = NF; i >= 1; i--) printf "%s ", $i; print ""}' vendes.txt
120 nord Anna
80 sud Pere
...

Amb printf pots alinear la sortida, i amb BEGIN, posar-hi una capçalera:

$ awk 'BEGIN {printf "%-8s %s\n", "Nom", "Import"} {printf "%-8s %5d\n", $1, $3}' vendes.txt
Nom      Import
Anna       120
Pere        80
Joan        45
Marta      200
Pere        80

Un altre separador

Amb -F canvies el separador de camps. Per exemple, /etc/passwd fa servir :. Els usuaris normals (UID entre 1000 i 65533) i el seu directori personal:

$ awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $6}' /etc/passwd
oscar /home/oscar

I amb OFS tries el separador de sortida:

$ awk -F: -v OFS=';' '{print $1, $7}' /etc/passwd | head -n 2
root;/bin/bash
daemon;/usr/sbin/nologin

Comptar els camps d’una sortida separada per comes:

echo "a,b,c" | awk -F, '{print NF}'

Comptar, sumar i fer mitjanes

awk 'END {print NR}' vendes.txt                          # nombre de línies
awk '{suma += $3} END {print suma}' vendes.txt          # suma de la 3a columna: 525
awk '{paraules += NF} END {print paraules}' fitxer.txt  # nombre de paraules
awk '/error/ {n++} END {print n+0}' registre.log        # línies amb «error»

Mitjana, amb dos decimals:

$ awk '{suma += $3} END {printf "Mitjana: %.2f\n", suma/NR}' vendes.txt
Mitjana: 105.00

Mitjana només dels valors que compleixen una condició. Comprova que n’hi ha algun abans de dividir, o tindràs un error de divisió per zero:

awk '$3 > 50 {suma += $3; n++} END {if (n) print suma/n}' vendes.txt

Màxim d’una columna i longitud de la línia més llarga:

awk 'NR == 1 || $3 > max {max = $3} END {print max}' vendes.txt
awk '{if (length($0) > max) max = length($0)} END {print max}' vendes.txt

Mostrar el fitxer i afegir-hi el total al final:

$ awk '{print; suma += $3} END {print "Total:", suma}' vendes.txt
Anna nord 120
Pere sud 80
Joan nord 45
Marta est 200
Pere sud 80
Total: 525

Agrupar amb arrays

Els arrays d’awk es poden indexar amb text, i això els fa ideals per agrupar. Total de vendes per zona:

$ awk '{total[$2] += $3} END {for (z in total) print z, total[z]}' vendes.txt
sud 160
est 200
nord 165

L’ordre en què for (… in …) recorre l’array no està garantit (canvia entre mawk i gawk). Si el necessites ordenat, passa el resultat per sort.

Freqüència de cada paraula d’un fitxer:

awk '{for (i = 1; i <= NF; i++) n[$i]++} END {for (p in n) print n[p], p}' fitxer.txt | sort -rn | head

Treure les línies repetides mantenint l’ordre (a diferència de sort -u, que les ordena):

awk '!vistes[$0]++' vendes.txt          # línies idèntiques
awk '!vistes[$1]++' vendes.txt          # només la primera aparició de cada nom

Mostrar un fitxer de l’última línia a la primera (com tac):

awk '{a[i++] = $0} END {for (j = i-1; j >= 0; j--) print a[j]}' vendes.txt

Unir dos fitxers línia a línia (com fa paste):

awk 'NR == FNR {a[FNR] = $0; next} {print a[FNR], $0}' fitxer1.txt fitxer2.txt

Mentre es llegeix el primer fitxer, NR i FNR són iguals; així se sap quan s’és al primer i quan al segon.

Substituir text

gsub substitueix totes les aparicions; el 1 del final és un patró sempre cert que fa que es mostri cada línia:

awk '{gsub("nord", "NORD")} 1' vendes.txt     # a tota la línia
awk '{gsub("nord", "NORD", $2)} 1' vendes.txt # només al 2n camp

Per a substitucions simples, sed (sed 's/antic/nou/g') és més directe.

Enviar la sortida a fitxers o a altres comandes

Repartir les línies en fitxers segons el valor d’una columna (aquí, nord.txt, sud.txt i est.txt):

awk '{print > ($2 ".txt")}' vendes.txt

Posa el nom del fitxer entre parèntesis: sense, algunes versions d’awk interpreten malament la concatenació.

Ordenar des de dins d’awk (tot i que normalment és més clar fer awk … | sort):

awk '{print $0 | "sort -k3 -n"}' vendes.txt

Exemples pràctics d’administració

Particions que estan per sobre del 50% d’ocupació:

$ df -h | awk 'NR > 1 && $5+0 > 50 {print $6, $5}'
/mnt/disco 64%

($5+0 converteix 64% en el número 64.)

Les 10 IP que fan més peticions a un servidor web:

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

Les línies d’un registre d’un dia concret. Compte amb el format: als registres clàssics de syslog, els dies d’una xifra porten dos espais (Jan 1):

awk '/^Jan  1 10:/' fitxer.log

Passar un valor des del shell amb -v en lloc de posar-lo dins de les cometes:

minim=100
awk -v minim="$minim" '$3 >= minim' vendes.txt

Programes en un fitxer

Quan el programa creix, posa’l en un fitxer:

# informe.awk
BEGIN { print "Informe de vendes" }
$3 > 100 { grans++ }
{ total += $3 }
END {
    print "Total:", total
    print "Vendes de més de 100:", grans
}
awk -f informe.awk vendes.txt

Comandes relacionades