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, iEND { … }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
| Element | Què és |
|---|---|
$0 | La línia sencera |
$1, $2… | El primer camp, el segon… |
$NF | L’últim camp |
NF | Nombre de camps de la línia |
NR | Número de línia (comptant totes les línies llegides) |
FNR | Número de línia dins del fitxer actual |
FS / -F | Separador de camps d’entrada (per defecte, espais i tabuladors) |
OFS | Separador de camps de sortida (per defecte, un espai) |
-v var=valor | Passa una variable al programa |
-f fitxer.awk | Llegeix 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
grep: buscar línies.- Manipular text amb les GNU textutils:
cut,sort,uniq,trised. du -k --max-depth=1 | sort | awk: un exemple d’awkper formatar mides.- Analitzar l’access.log per detectar atacs:
awkaplicat als registres d’Apache. - /bin/bash: guia completa.