Comando awk in Linux: estrarre campi, filtrare righe e calcolare totali

awk legge il testo come una sequenza di record, li divide in campi e applica condizioni e operazioni. È utile per estrarre colonne, selezionare righe e calcolare totali; per ottenere un risultato affidabile, bisogna prima definire separatori, intestazioni e formato dei numeri.

Nel comportamento predefinito, un record corrisponde a una riga e i campi sono separati da gruppi di spazi o tabulazioni. Il linguaggio permette di associare una condizione a un’azione: per ogni record che soddisfa la condizione, awk esegue l’azione indicata.

Sintassi, record e variabili dei campi

awk [opzioni] 'condizione { azione }' [file...]

Se omettete i file, awk legge dallo standard input e può seguire una pipe. Se omettete la condizione, l’azione si applica a ogni record; una condizione senza azione equivale a stampare l’intero record corrispondente. Tenete il programma tra virgolette singole, così la shell non espande riferimenti come $1.

  • $0 indica il record corrente completo; $1 e $2 indicano il primo e il secondo campo.
  • NF è il numero di campi del record corrente; una riga vuota può avere zero campi.
  • NR conta i record letti complessivamente, anche attraverso più file.
  • FNR conta i record nel file corrente e ricomincia da 1 al file successivo.

Con il separatore predefinito, gli spazi iniziali e finali vengono ignorati nella suddivisione e più spazi consecutivi non creano campi vuoti. $0 conserva comunque il testo originale finché non lo modificate, per esempio assegnando un nuovo valore a un campo.

Laboratorio completo: colonne, filtro, totale e dato non valido

Eseguite l’intero blocco in Bash, con un’implementazione che supporti la destinazione diagnostica /dev/stderr, come GNU gawk. Tutti i file vengono preparati in una nuova directory temporanea dentro una subshell: la directory di lavoro della shell chiamante non cambia e i file già presenti non vengono sovrascritti. Il percorso della prova viene stampato alla fine; ispezionatelo e rimuovete soltanto i dati della prova quando non servono più.

(
set -eu
labdir=$(mktemp -d)
cd "$labdir"
printf 'name amount\nAna 12\nBob 7\nCara 11\n' > amounts.txt
awk 'NR > 1 {print $2}' amounts.txt
awk 'NR > 1 && $2 >= 10 {print $1}' amounts.txt
awk 'NR > 1 {sum += $2} END {print sum}' amounts.txt
printf 'Dana missing\n' >> amounts.txt
awk 'NR == 1 {next}
     NF != 2 || $2 !~ /^[0-9]+([.][0-9]+)?$/ {
         print "invalid row " NR ": " $0 > "/dev/stderr"
         bad = 1; next
     }
     {sum += $2}
     END {if (bad) exit 1; print sum}' amounts.txt || test "$?" -eq 1
printf 'Lab files: %s\n' "$labdir"
)

La prima chiamata salta l’intestazione con NR > 1 e stampa la seconda colonna: 12, 7 e 11, su tre righe. La seconda richiede anche $2 >= 10 e stampa Ana e Cara. La terza somma il secondo campo e stampa 30 in END, dopo aver letto tutti i record.

La riga Dana missing viene poi aggiunta intenzionalmente allo stesso file. L’ultimo programma segnala invalid row 5: Dana missing sullo standard error e termina con stato 1, senza stampare un totale come risultato valido. Nel laboratorio, || test "$?" -eq 1 accetta proprio questo errore previsto per proseguire fino alla stampa del percorso; non copiate quel controllo in una procedura che deve fallire sui dati non validi.

La validazione richiede due campi e un importo formato da cifre, eventualmente seguito da un punto e altre cifre: accetta per esempio 0 e 12.50, ma non -3, 1e3, .5 o 12,50. È una regola del dataset di esempio, non una definizione universale di numero. Adattatela al formato realmente ammesso prima di calcolare un totale.

Opzioni e regole BEGIN ed END

Forma Funzione
-F separatore Imposta il separatore dei campi in ingresso. Un carattere come : separa campi delimitati da quel carattere; espressioni più lunghe richiedono attenzione alle regole delle espressioni regolari.
-v nome=valore Imposta una variabile prima dell’esecuzione del programma, comprese le regole BEGIN.
BEGIN { ... } Esegue l’azione prima della lettura dell’input; serve, per esempio, a inizializzare variabili o stampare un’intestazione.
END { ... } Esegue l’azione alla fine dell’elaborazione; serve, per esempio, a stampare una somma accumulata.

Un esempio autonomo con separatore esplicito è printf 'Ana:12\nBob:7\n' | awk -F ':' '{print $1, $2}': stampa Ana 12 e Bob 7. La virgola fra le espressioni di print inserisce il separatore di output, uno spazio per impostazione predefinita; -F non imposta il separatore di output.

Per cambiare una soglia senza modificare il testo del programma, usate printf 'Ana 12\nBob 7\nCara 11\n' | awk -v minimo=10 '$2 >= minimo {print $1}'. Il risultato è ancora Ana e Cara. Per verificare l’inizializzazione prima dell’input, awk -v minimo=10 'BEGIN {print minimo}' stampa 10 e termina, perché il programma contiene soltanto BEGIN.

Perché validare i dati prima della somma

Una conversione numerica non sostituisce una verifica del formato. Una stringa non numerica può contribuire come zero a un calcolo e una stringa con un prefisso numerico può produrre un valore parziale. Per questo il laboratorio controlla l’intero secondo campo con un’espressione delimitata da ^ e $, prima di eseguire sum += $2.

next passa al record seguente, saltando le regole successive per il record corrente. bad conserva l’informazione che almeno una riga è stata rifiutata; il controllo in END impedisce di presentare come completo il totale delle sole righe accettate.

Per input vuoto o contenente soltanto l’intestazione, il laboratorio non verifica che esista almeno una riga di dati valida. In un’applicazione reale, definite se quel caso deve produrre zero, un messaggio oppure un errore, e aggiungete un conteggio esplicito dei record accettati. Anche la precisione richiesta e l’interpretazione del separatore decimale dipendono dal tipo di dati e dall’ambiente: il semplice esempio con piccoli interi non dimostra l’idoneità per ogni calcolo numerico.

Il controllo NR == 1 salta una sola intestazione all’inizio dell’intero input. Se fornite più file, ciascuno con una propria intestazione e lo stesso schema, valutate FNR == 1 {next}. Prima verificate che ogni file abbia davvero quella struttura, altrimenti perdereste il suo primo record di dati.

Separatori, CSV e differenze fra implementazioni

awk -F ',' divide sul carattere virgola, senza interpretare le regole CSV per virgolette, virgole incorporate o record su più righe. Per esempio, in "Rossi, Ana",12 la virgola nel nome non deve creare un campo in più: il solo -F non è sufficiente.

Per CSV reali usate un parser adatto. Le versioni di GNU gawk che offrono --csv hanno un’apposita modalità CSV, descritta nel manuale corrente: non equivale a -F ',' e non è una caratteristica da presumere in qualunque comando chiamato awk. Controllate gawk --version, gawk --help e il manuale installato prima di basare uno script su un’estensione.

Le forme comuni -F, -v, BEGIN ed END sono descritte da POSIX. Le estensioni GNU e nomi speciali come /dev/stderr richiedono invece una verifica dell’implementazione. Il laboratorio usa dati semplici e la destinazione diagnostica supportata da GNU gawk; non presume una compatibilità identica con ogni sistema.

I comandi di lettura mostrati non modificano il file di input; il laboratorio aggiunge una riga solo tramite l’esplicito >> amounts.txt. Per salvare un rapporto, scegliete un nuovo percorso di output. Non redirigete l’output sullo stesso file che state leggendo: la shell potrebbe troncarlo prima dell’elaborazione.

Domanda frequente

Qual è la differenza fra $0 e $1 in awk?

$0 è l’intero record corrente, mentre $1 è il primo campo ottenuto con il separatore in uso. Per esempio, con la riga Ana 12, $0 vale Ana 12 e $1 vale Ana; cambiando separatore può cambiare anche il valore dei campi.

Documentazione di riferimento

La specifica POSIX.1-2024 di awk definisce il comportamento standard. La pagina di manuale del progetto GNU gawk descrive anche le estensioni GNU e la modalità CSV; rimanda al manuale completo, consultabile con info gawk quando installato.