[>a2249.html>] [<a2247.html<] [^a2.html^]
AWK è un linguaggio di programmazione nato fondamentalmente per l'analisi e la rielaborazione di file di testo organizzati in una qualche forma tabellare. AWK potrebbe essere usato per fare anche di più, solo che quando si supera un certo limite di complessità, non è più conveniente il suo utilizzo.
AWK è un interprete, nel senso che i programmi fatti secondo questo linguaggio, vengono eseguiti direttamente, senza essere compilati, come nel caso degli script di shell. Un programma AWK può essere scritto in un file di testo normale, oppure può essere fornito come argomento della riga di comando dell'interprete: il binario awk. Volendo automatizzare l'avvio dell'interprete per l'esecuzione di uno script (che abbia i permessi di esecuzione opportuni), lo si può iniziare con la direttiva comune agli script di shell:
#!/usr/bin/awk -f
Nei sistemi Unix esistono diversi tipi differenti di interpreti AWK. Con GNU/Linux potrebbe essere disponibile la versione GNU (gawk), che ha molte estensioni rispetto agli standard, oppure ci potrebbe essere mawk. In questo, e negli altri capitoli dedicati a AWK, si vuole fare riferimento allo standard POSIX, senza nemmeno approfondire troppo l'utilizzo di questo linguaggio.
Il programma AWK tipico, è qualcosa che legge i dati provenienti da uno o più file, li analizza in qualche modo, e genera un risultato che viene visualizzato direttamente o indirizzato a un altro file. Questo indica implicitamente due cose: un programma AWK non dovrebbe essere fatto per modificare i file di partenza; inoltre, si dà per scontato che ci sia una lettura dei file di origine, e infatti ciò avviene di solito senza una richiesta esplicita.
Dal punto di vista di AWK, un file che viene analizzato è composto da record, corrispondenti normalmente alle righe del file di testo stesso, dove però il codice di interruzione di riga può essere specificato espressamente come qualcosa di diverso rispetto al solito.
Un programma AWK è composto fondamentalmente da regole, che stabiliscono il comportamento da prendere nei confronti dei dati in ingresso. I commenti sono introdotti dal simbolo #, e terminano alla fine della riga; inoltre, le righe vuote e quelle bianche vengono ignorate nello stesso modo. La struttura delle regole di un programma AWK si può esprimere secondo lo schema seguente:
criterio_di_selezione { azione }
In pratica, ogni regola si suddivide in due parti: un'istruzione iniziale che definisce quali record prendere in considerazione, e un'azione (più o meno articolata), indicata all'interno di parentesi graffe, da eseguire ogni volta che si incontra una corrispondenza con il criterio di selezione stabilito. Questa descrizione è solo una semplificazione che per il momento serve a iniziare la comprensione di questo linguaggio.
Una regola di un programma AWK può contenere l'indicazione esplicita del solo criterio di selezione, o della sola azione da compiere. Ciò perché in tal caso si utilizza un'azione o un criterio di selezione predefinito (questo particolare verrà ripreso quando verranno mostrati i primi esempi).
L'azione di una regola AWK è molto simile a un programma C, o Perl, con tante semplificazioni, dove il record selezionato viene passato attraverso dei campi, che ricordano i parametri delle shell comuni: $0, $1, $2,... In pratica, un'azione di una regola AWK è un programma a sé stante, che viene eseguito ogni volta che il criterio di selezione della regola si avvera.
Una regola che non contenga l'indicazione del criterio di selezione, fa sì che vengano prese in considerazione tutte le righe dei dati in ingresso. In AWK, il valore booleano Vero si esprime con qualunque valore differente dallo zero e dalla stringa nulla, dal momento che entrambi questi rappresentano invece il valore Falso in un contesto booleano. In altre parole, una regola che non contenga l'indicazione del criterio di selezione, è come se avesse al suo posto il valore uno, che si traduce ogni volta in un risultato booleano Vero, cosa che permette la selezione di tutti i record.
Una regola che non contenga l'indicazione dell'azione da compiere, fa riferimento a un'azione predefinita, che in pratica fa sì che venga emessa attraverso lo standard output ogni riga che supera il criterio di selezione. Praticamente, è come se venisse usata l'azione { print }. Per essere precisi, dal momento che in AWK il concetto di «predefinito» può riguardare diversi livelli, si tratta dell'azione { print $0 }.
In pratica, se si unisse il criterio di selezione predefinito, e l'azione predefinita, si avrebbe:
1 { print }
che riemette attraverso lo standard output tutti i record che legge dai file in ingresso. Bisogna ricordare però che almeno una delle due parti deve essere indicata esplicitamente: o il criterio di selezione, o l'azione.
Si è accennato al fatto che il testo analizzato da un programma AWK, viene visto generalmente come qualcosa composto da record suddivisi in campi. I record vengono individuati in base a un codice che li separa, e che generalmente corrisponde al codice di interruzione di riga, per cui si ottiene la corrispondenza tra record e righe. I campi sono separati in modo analogo, attraverso un altro codice opportuno.
Eccezionalmente, quando il codice indicato per individuare la suddivisione in campi è <SP>, cioè lo spazio normale, diventa indifferente la quantità di spazi utilizzati tra un campo e l'altro, ed è possibile utilizzare anche i caratteri di tabulazione. |
Se per il codice che definisce la fine di un record e l'inizio di quello successivo, viene indicata la stringa nulla, (""), si intende che i record siano separati da una o più righe bianche o vuote. |
Ogni record può avere un numero variabile di campi, e al loro contenuto si può fare riferimento attraverso il simbolo $ seguito da un numero che ne indica la posizione: $n è il campo n-esimo del record attuale, ma in particolare, $0 rappresenta il record completo. Il numero in questione può anche essere rappresentato da un'espressione (per esempio una variabile), che si traduce nel numero desiderato. Per esempio, se pippo è una variabile contenente il valore due, $pippo è il secondo campo.
Il criterio di selezione dei record è generalmente un'espressione che viene valutata per ognuno di questi, in ordine, e quando si avvera, permette l'esecuzione dell'azione corrispondente. Oltre a queste situazioni generali, esistono due istruzioni speciali da utilizzare come criteri di selezione: BEGIN e END. Queste due parole chiave vanno usate da sole, e rappresentano rispettivamente il momento iniziale prima di cominciare la lettura dei dati in ingresso, e il momento finale successivo alla lettura ed elaborazione dell'ultimo record dei dati. Le azioni che si abbinano a queste condizioni particolari servono a preparare qualcosa e a concludere un'elaborazione.
Esiste un altro caso di criterio di selezione speciale, e si tratta di due espressioni separate da una virgola, come si vede nello schema seguente:
espressione_1, espressione_2
La prima espressione serve ad attivare il passaggio dei record; la seconda serve a disattivarlo. In pratica, quando si avvera la prima espressione, quel record e i successivi possono passare, fino a quando si avvera la seconda. Quando si avvera la seconda espressione (dopo che si era avverata la prima), il record attuale passa, ma quelli successivi non più. Se in seguito si riavvera la prima condizione, la cosa ricomincia.
Tabella 191.1. Schema complessivo dei diversi tipi di criteri di selezione in AWK.
Per mostrare il funzionamento di un programma AWK viene mostrato subito un esempio banale. Come è già stato descritto, la cosa più semplice che possa fare un programma AWK, è la riemissione degli stessi record letti in ingresso, senza porre limiti alla selezione.
1 { print $0 }
Come è già stato descritto, la regola mostrata è molto semplice: il numero uno rappresenta in pratica un valore corrispondente a Vero, dal punto di vista booleano, per cui si tratta di un'espressione che si avvera sempre, e così seleziona tutti i record; l'azione richiede l'emissione della riga attuale, rappresentata da $0.
Se si realizza un file contenente la regola che è stata mostrata, supponendo di averlo chiamato banale
, per avviarlo basta il comando seguente:
$
awk -f banale
Nel comando non è stato specificato alcun file da analizzare, per cui l'interprete awk lo attende dallo standard input, in questo caso dalla tastiera. Per terminare la prova basta concludere l'inserimento attraverso la combinazione [Ctrl+d].
Un programma così breve può essere fornito direttamente nella riga di comando:
$
awk '1 { print $0 }'
Per realizzare uno script, basta mettere l'intestazione corretta al file del programma, ricordando poi di rendere eseguibile il file:
#!/usr/bin/awk -f 1 { print $0 }
Prima di proseguire, è il caso di vedere come funzionano i criteri di selezione BEGIN e END:
BEGIN { print "Inizio del programma" } 1 { print $0 } END { print "Fine del programma" }
In questo modo, prima di iniziare la riemissione del testo che proviene dal file in ingresso, viene emesso un messaggio iniziale, e alla fine, un altro messaggio conclusivo.
AWK ha ereditato dalle shell l'idea delle variabili predefinite, con le quali si può modificarne l'impostazione. Le variabili predefinite si distinguono dalle altre perché sono tutte espresse attraverso nomi con lettere maiuscole.
Due di queste variabili sono fondamentali: RS, Record Separator, e FS, Field Separator. La prima serve a definire il carattere da prendere in considerazione per separare i dati in ingresso in record; la seconda serve a definire il codice da prendere in considerazione per separare i record in campi. Per la precisione, nel caso della variabile FS, può trattarsi di un carattere singolo, oppure di un'espressione regolare.
I valori predefiniti di queste variabili sono rispettivamente <LF>, ovvero il codice di interruzione di riga dei file di testo normali, e uno spazio normale, che rappresenta una situazione particolare, come è già stato descritto. Questi valori possono essere cambiati: la situazione tipica in cui si deve intervenire nella variabile FS è quella della lettura di file come /etc/passwd
, e simili, dove si assegna generalmente alla variabile FS il valore :, che è effettivamente il carattere utilizzato per separare i campi.
Idealmente, un programma AWK potrebbe essere rappresentato in modo più esplicito, secondo lo schema sintattico seguente, dove le parentesi graffe vanno considerate in modo letterale:
[function nome_funzione (parametri_formali) { istruzioni }] ... [BEGIN { azione }] [BEGIN { azione }] ... espressione_di_selezione { azione } ... [END { azione }] [END { azione }] ...
L'ordine indicato non è indispensabile, tuttavia è opportuno. In pratica, prima vengono eseguite le azioni abbinate alle condizioni BEGIN, ammesso che esistano, quindi viene letto il file in ingresso, e per ogni record vengono valutate le espressioni di selezione, e per ogni espressione che si avvera, viene eseguita l'azione corrispondente (se più espressioni si avverano simultaneamente, vengono eseguite ordinatamente tutte le azioni relative). Alla fine, vengono eseguite le azioni abbinate alle condizioni END.
Un programma AWK potrebbe essere composto anche solo da regole di tipo BEGIN o END. Nel primo caso non è nemmeno necessario leggere i dati in ingresso, mentre nel caso ci sia una regola di tipo END, ciò diventa indispensabile, perché l'azione relativa potrebbe utilizzare le informazioni generate dalla lettura stessa.
AWK mette a disposizione una serie di funzioni predefinite, e consente la dichiarazione di altre funzioni personalizzate. L'ordine in cui appaiono queste funzioni non è importante: una funzione può richiamare anche un'altra funzione dichiarata in una posizione successiva.
L'interprete di un programma AWK è l'eseguibile awk, che di solito è un collegamento alla realizzazione di AWK che risulta installata effettivamente: in un sistema GNU/Linux potrebbe trattarsi di mawk o gawk (quest'ultimo è la versione GNU di AWK). La sintassi standard di un interprete AWK dovrebbe essere quella seguente:
awk [-F separazione_campi] [-v variabile=valore] -f file_contenente_il_programma [--] [file_in_ingresso...]
awk [-F separazione_campi] [-v variabile=valore] [--] 'testo_del_programma' [file_in_ingresso...]
I due schemi alternativi riguardano la possibilità di far leggere all'interprete il programma contenuto in un file, indicato attraverso l'opzione -f, oppure di fornirlo direttamente nella riga di comando, delimitandolo opportunamente perché venga preso dalla shell come un argomento singolo.
Se non vengono forniti i file da usare come dati in ingresso, l'interprete attende i dati dallo standard input.
-F separazione_campi
Definisce in che modo devono essere distinti i campi dei record, modificando così il valore predefinito della variabile FS. Come è già stato descritto, può trattarsi di un carattere singolo, oppure di un'espressione regolare.
-v variabile=valore
Assegna un valore a una variabile. La variabile in questione può essere predefinita, oppure una nuova che viene utilizzata nel programma per qualche motivo.
-f file_programma_awk
Indica espressamente il file contenente il programma AWK del quale deve essere iniziata l'interpretazione.
--
Una coppia di trattini dichiara la conclusione delle opzioni normali, e l'inizio degli argomenti finali (può essere usato per evitare ambiguità, nel caso ce ne possano essere). Gli argomenti successivi possono essere il programma stesso, se non è stata utilizzata l'opzione -f, e quindi i file da fornire in ingresso per l'elaborazione.
$
awk -f programma.awk elenco
Avvia l'esecuzione del programma contenuto nel file programma.awk
, per l'elaborazione del file elenco
.
$
cat elenco | awk -f programma.awk
Esattamente come nell'esempio precedente, con la differenza che il file elenco
viene fornito attraverso lo standard input.
$
awk -f programma.awk -F : /etc/passwd
Esegue una qualche elaborazione, attraverso il programma programma.awk
, sui dati del file /etc/passwd
. Per questo motivo, viene definito l'utilizzo del carattere : come separatore dei campi che compongono i record di quel file.
$
awk -f programma.awk -v FS=: /etc/passwd
Esattamente come nell'esempio precedente, intervenendo direttamente sulla variabile predefinita FS.
L'espressione è qualcosa che restituisce un valore. I tipi di valori gestiti da AWK sono pochi: numerici (numeri reali), stringhe e stringhe numeriche. I valori booleani non hanno un tipo indipendente: lo zero numerico e la stringa nulla valgono come Falso, mentre tutto il resto vale come Vero (anche la stringa "0" vale come Vero, a differenza di quanto accade con il linguaggio Perl).
Le costanti sono espressioni elementari che restituiscono un valore in base a una simbologia convenuta.
I valori numerici si esprimono in forma costante nei modi comuni anche agli altri linguaggi di programmazione. I valori interi si possono indicare come una serie di cifre numeriche, non delimitate, che esprimono il valore secondo una numerazione a base decimale; i valori non interi possono essere espressi utilizzando il punto come separatore tra la parte intera e la parte decimale; sia i valori interi che gli altri, possono essere espressi secondo la notazione esponenziale. Le costanti numeriche che appaiono di seguito, sono esempi di rappresentazione dello stesso valore: 100,5.
100.5 1.005e+2 1005e-1
Le stringhe sono delimitate da apici doppi, come si vede nell'esempio seguente:
"questa è una stringa"
Le stringhe possono contenere delle sequenze di escape, come elencato nella tabella 191.2.
Escape | Significato |
\\ | \ |
\" | " |
\/ | / |
\a | <BEL> |
\b | <BS> |
\f | <FF> |
\n | <LF> |
\r | <CR> |
\t | <HT> |
\v | <VT> |
\nnn | il valore ottale nnn |
Tabella 191.2. Sequenze di escape utilizzabili all'interno delle stringhe costanti.
AWK gestisce anche un tipo speciale di costante, che è da considerare come un tipo speciale di stringa: l'espressione regolare costante. Questa è una stringa delimitata all'inizio e alla fine da una barra obliqua normale. Per esempio,
/ciao/
è un'espressione regolare che corrisponde alla sottostringa ciao. Anche le espressioni regolari costanti ammettono l'uso di sequenze di escape, e precisamente le stesse che si possono usare per le stringhe.
Le espressioni regolari di AWK sono quelle estese, ovvero quelle definite da POSIX come ERE. Tuttavia, la grammatica effettiva di queste dipende dalla realizzazione dell'interprete particolare di cui si dispone. In generale dovrebbero essere disponibili gli operatori riassunti nella tabella 191.3, tenendo presente che le espressioni regolari di AWK ammettono la presenza di sequenze di escape per rappresentare caratteri che non potrebbero essere indicati altrimenti (la tabella 191.2).
Operatore | Descrizione |
\ | Protegge il carattere seguente da un'interpretazione diversa da quella letterale. |
^ | Ancora dell'inizio di una stringa. |
. | Corrisponde a un carattere qualunque. |
$ | Ancora della fine di una stringa. |
| | Indica due possibilità alternative alla sua sinistra e alla sua destra. |
( ) | Definiscono un raggruppamento. |
[ ] | Definiscono un'espressione tra parentesi quadre. |
[xy...] | Un elenco di caratteri alternativi. |
[x-y] | Un intervallo di caratteri alternativi. |
[^...] | I caratteri che non appartengono all'insieme. |
x* | Nessuna o più volte x. Equivalente a x{0,}. |
x? | Nessuna o al massimo una volta x. Equivalente a x{0,1}. |
x+ | Una o più volte x. Equivalente a x{1,}. |
x{n} | Esattamente n volte x. |
x{n,} | Almeno n volte x. |
x{n,m} | Da n a m volte x. |
Tabella 191.3. Elenco degli operatori standard delle espressioni regolari estese.
In generale, è improbabile che siano disponibili i simboli di collazione e le classi di equivalenza, come definito dallo standard POSIX per le espressioni tra parentesi quadre. Nel caso particolare della versione GNU di AWK, si possono usare le classi di caratteri (nella forma [:nome:]). Anche a causa di queste carenze, ogni realizzazione di AWK utilizza le proprie estensioni particolari, che di solito sono rappresentate da sequenze di escape particolari. La tabella 191.4 riepiloga le estensioni GNU, che riguardano quindi gawk.
Operatore | Descrizione |
\y | La stringa nulla all'inizio o alla fine di una parola. |
\B | La stringa nulla interna a una parola. |
\< | La stringa nulla all'inizio di una parola. |
\> | La stringa nulla alla fine di una parola. |
\w | Un carattere di una parola, praticamente [[:alnum:]_]. |
\W | L'opposto di \w, praticamente [^[:alnum:]_]. |
Tabella 191.4. Elenco delle estensioni GNU alle espressioni regolari di AWK.
Le variabili sono espressioni elementari che restituiscono il valore che contengono.
AWK gestisce una serie di variabili predefinite, che possono essere lette per conoscere delle informazioni sui dati in ingresso, oppure possono essere modificate per cambiare il comportamento di AWK. Oltre a queste, si possono utilizzare le variabili che si vogliono, e per farlo è sufficiente assegnare loro un valore, senza bisogno di definirne il tipo.
Se in un'espressione si fa riferimento a una variabile che non è mai stata assegnata, questa restituisce la stringa nulla (""), che in un contesto numerico equivale allo zero. In questo senso, non c'è bisogno di inizializzare le variabili prima di usarle, dal momento che è noto il loro valore iniziale.
Eventualmente, una variabile può essere inizializzata a un valore determinato già al momento dell'avvio dell'interprete, attraverso l'opzione -v che è già stata descritta.
I nomi delle variabili sono sensibili alla differenza che c'è tra la collezione alfabetica maiuscola e quella minuscola. In particolare si può osservare che, convenzionalmente, i nomi di tutte le variabili predefinite sono espressi con lettere maiuscole, mentre le variabili definite all'interno del programma tendono a essere espresse utilizzando prevalentemente lettere minuscole. |
All'interno di un programma AWK, i riferimenti ai campi del record attuale si fanno attraverso la forma $n, dove n rappresenta il campo n-esimo. Il riferimento a un campo può essere ottenuto anche utilizzando il risultato di un'espressione, quando questa è preceduta dal dollaro. In particolare, è ammissibile anche l'assegnamento di un valore a un campo, per quanto questo sia una pratica sconsigliabile, dal momento che questo fatto non ha alcun significato nei confronti dei dati originali.
Gli operatori usati per le espressioni numeriche sono più o meno gli stessi del linguaggio C. Per quanto riguarda le stringhe, è previsto il concatenamento, che si ottiene senza alcun operatore esplicito, affiancando variabili o costanti stringa. Inoltre, dovendo gestire le espressioni regolari, si aggiungono due operatori speciali per il confronto di queste con delle stringhe. La tabella 191.5 raccoglie l'elenco degli operatori disponibili in AWK.
Tabella 191.5. Riepilogo degli operatori principali utilizzabili nelle espressioni di AWK.
Un tipo particolare di operatore logico è l'operatore condizionale, che permette di eseguire espressioni diverse in relazione al risultato di una condizione. La sua sintassi si esprime nel modo seguente:
condizione ? espressione1 : espressione2
In pratica, se l'espressione che rappresenta la condizione si avvera, viene eseguita la prima espressione che segue il punto interrogativo, altrimenti viene eseguita quella che segue i due punti.
Per quanto riguarda il confronto tra stringhe ed espressioni regolari, si deve tenere presente che lo scopo è solo quello di conoscere se c'è o meno una corrispondenza tra il modello e la stringa. Inoltre, è molto importante tenere in considerazione il fatto che un'espressione regolare costante, che non si trovi alla destra di un operatore ~, o !~, viene interpretata come una forma contratta dell'espressione $0 ~/regexp/, ovvero, si considera un confronto con il record attuale.
Come è già stato descritto, AWK gestisce solo due tipi di dati: stringhe e numeri (reali). In base al contesto, i numeri vengono convertiti in stringhe e viceversa, e questo avviene di solito in modo abbastanza trasparente. In particolare, una stringa che non possa essere interpretata come un numero, equivale a zero.
In generale, il concatenamento di stringhe, impone una trasformazione in stringa, mentre l'uso di operatori aritmetici impone una trasformazione in numero. Si osservi l'esempio:
uno = 1 due = 2 (uno due) + 3
Si tratta di tre istruzioni in sequenza, dove le prime due assegnano un valore numerico ad altrettante variabili, e l'ultima fa qualcosa di incredibile: concatena le due variabili, che di conseguenza vengono trattate come stringhe, e generano la stringa "12"; quindi, la stringa viene riconvertita in numero, a causa dell'operatore +, che richiede la somma con il numero tre. Alla fine, il risultato dell'ultima espressione è il numero 15.
La conversione da numero a stringa è banale quando si tratta di numeri interi, dal momento che il risultato è una stringa composta dalle stesse cifre numeriche che si utilizzano per rappresentare un numero intero. Al contrario, in presenza di numeri con valori decimali, entra in gioco una conversione per mezzo della funzione sprintf() (equivalente a quella del linguaggio C), che utilizza la stringa di formato contenuta nella variabile predefinita CONVFMT. Di solito, questa variabile contiene il valore "%.6g", che indica una precisione fino a sei cifre dopo la virgola, e una notazione che può essere esponenziale, oppure normale (intero.decimale), in base alla necessità. Le tabelle 191.6 e 191.7 riepilogano i simboli utilizzabili nelle stringhe di formato di sprintf(). Eventualmente, per una descrizione più dettagliata, si può leggere la pagina di manuale sprintf(3).
Simbolo | Corrispondenza |
%% | Segno di percentuale. |
%c | Un carattere corrispondente al numero dato. |
%s | Una stringa. |
%d, %i | Un intero con segno a base 10. |
%o | Un intero senza segno in ottale. |
%x | Un intero senza segno in esadecimale. |
%X | Come %x, ma con l'uso di lettere maiuscole. |
%e | Un numero a virgola mobile, in notazione scientifica. |
%E | Come %e, ma con l'uso della lettera E maiuscola. |
%f | Un numero a virgola mobile, in notazione decimale fissa. |
%g | Un numero a virgola mobile, secondo la notazione di %e o %f. |
%G | Come %g, ma con l'uso della lettera E maiuscola (se applicabile). |
Tabella 191.6. Elenco dei simboli utilizzabili in una stringa formattata per l'utilizzo con sprintf().
Simbolo | Corrispondenza |
spazio | Il prefisso di un numero positivo è uno spazio. |
+ | Il prefisso di un numero positivo è il segno +. |
- | Allinea a sinistra rispetto al campo. |
0 | Utilizza zeri, invece di spazi, per allineare a destra. |
# | Prefissa un numero ottale con uno zero, e un numero esadecimale con 0x... |
n | Un numero definisce la dimensione minima del campo. |
.n | Per i numeri interi indica il numero minimo di cifre. |
.n | Per i numeri a virgola mobile esprime la precisione, ovvero il numero di decimali. |
.n | Per le stringhe definisce la lunghezza massima. |
Tabella 191.7. Elenco dei simboli utilizzabili tra il segno di percentuale e la lettera di conversione.
In generale, sarebbe bene non modificare il valore predefinito della variabile CONVFMT, soprattutto non è il caso di ridurre la precisione della conversione, dal momento che la perdita di informazioni che ne deriverebbe, potrebbe creare anche dei gravi problemi a un programma. In altri termini, il formato di conversione condiziona la precisione dei valori che possono essere gestiti in un programma AWK.
Prima di proseguire con la descrizione del linguaggio AWK vengono mostrati alcuni esempi di programmi banali, in cui tutto si concentra sulla definizione delle espressioni per definire la selezione dei record. L'azione che si abbina è molto semplice: l'emissione del record selezionato, attraverso l'istruzione print.
$
ls -l /etc | awk '$1 == "-rw-r--r--" { print $0 }'
L'esempio appena mostrato fornisce all'interprete AWK il programma come argomento nella riga di comando. Come si vede, il risultato del comando ls -l /etc viene incanalato attraverso una pipeline, e fornito in ingresso al programma AWK, che si limita a selezionare i record in cui il primo campo corrisponde esattamente alla stringa "-rw-r--r--". In pratica, vengono selezionati i record contenenti informazioni sui file che hanno solo i permessi 06448. L'esempio seguente ottiene lo stesso risultato, attraverso la comparazione con un'espressione regolare:
$
ls -l /etc | awk '$1 ~ /-rw-r--r--/ { print $0 }'
I due esempi successivi sono equivalenti, e servono a selezionare tutti i record che non corrispondono al modello precedente.
$
ls -l /etc | awk '!( $1 == "-rw-r--r--" ) { print $0 }'
$
ls -l /etc | awk '!( $1 ~ /-rw-r--r--/ ) { print $0 }'
L'esempio seguente utilizza due espressioni, per attivare e disattivare la selezione dei record:
$
awk '$0 ~ /\/\*/, $0 ~ /\*\// { print $0 }' prova.c
In questo caso, i dati in ingresso provengono dal file prova.c
, che si intende essere un programma scritto in linguaggio C. Le due espressioni servono a selezionare le righe che contengono commenti nella forma /*...*/. Si osservi l'uso della barra obliqua inversa per proteggere i caratteri che altrimenti sarebbero stati interpretati diversamente.
La variante seguente è funzionalmente identica all'esempio precedente, dal momento che un'espressione regolare costante da sola, equivale a un'espressione in cui questa si paragona al record attuale.
$
awk '/\/\*/, /\*\// { print $0 }' prova.c
Nel linguaggio AWK, le istruzioni possono apparire nell'ambito della dichiarazione delle azioni abbinate a un certo criterio di selezione dei record, oppure nel corpo della dichiarazione di una funzione.
Le istruzioni di AWK terminano normalmente alla fine della riga, salvo quando nella parte finale della riga appare una virgola (,), una parentesi graffa aperta ({), una doppia e-commerciale (&&), o una doppia barra verticale (||). Eventualmente, per continuare un'istruzione nella riga successiva, si può utilizzare una barra obliqua inversa esattamente alla fine della riga, come simbolo di continuazione (\).
Un'istruzione può essere terminata esplicitamente con un punto e virgola finale (;), e in questo modo è anche possibili collocare più istruzioni in sequenza sulla stessa riga.
Come è già stato descritto, le righe vuote e quelle bianche vengono ignorate; inoltre, ciò che è preceduto dal simbolo #, fino alla fine della riga, è considerato un commento.
Le istruzioni di AWK possono essere delle espressioni di assegnamento, delle chiamate di funzione, oppure delle strutture di controllo.
Le istruzioni fondamentali di AWK sono quelle che permettono di emettere del testo attraverso lo standard output. Si tratta di due funzioni, che però possono essere usate anche in forma di «operatori»: print e printf. La prima di queste due permette l'emissione di una stringa o più stringhe, mentre la seconda permette di definire una stringa in base a un formato indicato, e di emetterne il risultato. In pratica, printf si comporta in modo analogo alla funzione omonima del linguaggio C.
print espressione_1[, espressione_1]...
print( espressione_1[, espressione_1]...)
Quelli che si vedono sono gli schemi sintattici della funzione (o istruzione) print. Se non vengono specificati degli argomenti (ovvero dei parametri), si ottiene l'emissione del testo del record attuale. Se invece vengono indicati degli argomenti, questi vengono emessi in sequenza, inserendo tra l'uno e l'altro il carattere definito dalla variabile OFS (Output Field Separator), che di solito corrisponde a uno spazio normale. In tutti i casi, il testo emesso da print termina con l'inserimento del carattere contenuto nella variabile ORS (Output Record Separator), che di solito corrisponde al codice di interruzione di riga.
In altri termini, nel primo caso viene emessa la stringa corrispondente al concatenamento $0 ORS; nel secondo e nel terzo viene emessa la stringa corrispondente al concatenamento espressione_1 OFS espressione_2 OFS ... espressione_n ORS.
printf stringa_di_formato, espressione_1[, espressione_2]...
printf( stringa_di_formato, espressione_1[, espressione_2]... )
L'istruzione, ovvero la funzione printf, si comporta come la sua omonima del linguaggio C: il primo argomento è una stringa di formato, contenente una serie di simboli che iniziano con il simbolo %, che vanno rimpiazzati ordinatamente con gli argomenti successivi. Le tabelle 191.6 e 191.7 riepilogano i simboli utilizzabili nelle stringhe di formato di sprintf. Eventualmente, per una descrizione più dettagliata, si può leggere la pagina di manuale sprintf(3).
A differenza di print, printf non fa uso delle variabili OFS e ORS, dal momento che quello che serve può essere inserito tranquillamente nella stringa di formato (il carattere <LF>, corrispondente al codice di interruzione di riga, viene indicato con la sequenza di escape \n).
L'output generato dalle istruzioni print e printf può essere ridiretto all'interno del programma AWK stesso, utilizzando gli operatori >, >> e |. Questo permette di ridirigere i dati verso file differenti; diversamente, converrebbe intervenire all'esterno del programma, per mezzo del sistema operativo.
print ... > file
printf ... > file
print ... >> file
printf ... >> file
print ... | comando
printf ... | comando
Utilizzando l'operatore > si ridirigono i dati verso un file, che viene azzerato inizialmente, oppure viene creato per l'occasione; con l'operatore >> si accodano dati a un file già esistente; con l'operatore | si inviano dati allo standard input di un altro comando. È importante osservare che i file e i comandi in questione, vanno indicati in una stringa. Si osservino gli esempi seguenti.
# annota il secondo campo nel file /tmp/prova print $2 > "/tmp/prova"
# accoda il secondo campo nel file /tmp/prova print $2 >> "/tmp/prova"
# definisce un comando per riordinare i dati e salvarli nel file /tmp/prova comando = "sort > /tmp/prova" #seleziona alcuni campi e poi invia al comando di riordino print $2 $4 $5 | comando
Il linguaggio AWK offre alcune strutture di controllo di flusso comuni agli altri linguaggi di programmazione. In particolare, come nel linguaggio C, è possibile raggruppare alcune istruzioni delimitandole con le parentesi graffe ({...}).
Le strutture di controllo permettono di sottoporre l'esecuzione di una parte di codice alla verifica di una condizione, oppure permettono di eseguire dei cicli, sempre sotto il controllo di una condizione. La parte di codice che viene sottoposta a questo controllo, può essere un'istruzione singola, oppure un gruppo di istruzioni. Nel secondo caso, è necessario delimitare questo gruppo attraverso l'uso delle parentesi graffe, a cui si è appena accennato.
Dal momento che è comunque consentito di realizzare un gruppo di istruzioni che in realtà ne contiene una sola, probabilmente è meglio utilizzare sempre le parentesi graffe, in modo da evitare equivoci nella lettura del codice.
La tabella 191.8 riassume la sintassi di queste strutture, la maggior parte delle quali dovrebbero essere già note dal linguaggio C, o da altri linguaggi simili.
Tabella 191.8. Istruzioni per le strutture di controllo del flusso in AWK.
Data la natura di AWK, esiste un'istruzione particolare: next. Questa serve a passare immediatamente al record successivo.
if ( $1 > 100 ) print $2
Se il primo campo del record attuale contiene un valore numerico superiore a 100, emette il contenuto del secondo campo.
if ( $1 > 100 ) { print $2 contatore++ } else { print $3 }
Se il primo campo del record attuale contiene un valore numerico superiore a 100, emette il contenuto del secondo campo, e incrementa la variabile contatore di un'unità. Altrimenti, emette solo il contenuto del terzo campo.
i = 1 while (i <= 10) { print i i++ }
Emette i numeri da 1 a 10.
for ( i = 1; i <= 10; i++ ) { print i }
Esattamente come nell'esempio precedente, utilizzando un ciclo enumerativo.
for ( i = 1; i <= 20; i++ ) { if ( i != 13 ) { print i } }
Emette i numeri da 1 a 20, escluso il 13.
for ( i = 1; i <= 20; i++ ) { if ( i != 13 ) { continue } print i }
Come nell'esempio precedente, utilizzando una tecnica diversa (l'istruzione continue fa riprendere il ciclo prima di avere completato le altre istruzioni).
i = 1 while (1) { if ( i > 10 ) { break } print i i++ }
Emette i numeri da 1 a 10, utilizzando un ciclo iterativo perpetuo (il numero 1 equivale a Vero per AWK), che viene interrotto dall'istruzione break.
La chiamata di una funzione avviene come nel linguaggio C, tenendo conto che per evitare ambiguità, è importante mettere sempre la parentesi iniziale del gruppo dei parametri, attaccata al nome della funzione stessa:
funzione(elenco_parametri)
I parametri sono separati attraverso delle virgole, e in linea di principio, si possono omettere quelli finali (si possono omettere tutti i parametri a partire da una certa posizione). I parametri che non vengono forniti sono equivalenti a stringhe vuote, e in certi casi, ci sono funzioni predisposte per riconoscere la mancata indicazione di tali informazioni, che così gestiscono attribuendo valori predefiniti.
Come nel linguaggio C, il passaggio dei parametri avviene per valore (salvo eccezioni), per cui i parametri in una chiamata possono essere delle espressioni più o meno articolate, che vengono valutate (senza un ordine preciso) prima della chiamata stessa.
Di seguito vengono descritte brevemente le funzioni interne (predefinite) di AWK. In particolare, le funzioni numeriche comuni sono elencate nella tabella 191.9.
Funzione | Descrizione. |
atan2(y, x) | Arcotangente di y/x in radianti. |
cos(x) | Coseno di x espresso in radianti. |
exp(x) | Funzione esponenziale (e^x). |
int(x) | Parte intera di un numero reale. |
log(x) | Logaritmo naturale (base e). |
rand() | Numero casuale compreso tra zero e uno. |
sin(x) | Seno di x espresso in radianti. |
sqrt(x) | Radice quadrata di x. |
Tabella 191.9. Elenco delle funzioni numeriche principali.
index( stringa, sottostringa_cercata)
La funzione index() cerca la stringa indicata come secondo parametro nella stringa indicata come primo, cominciando da sinistra. Se trova la corrispondenza, restituisce la posizione iniziale di questa, altrimenti restituisce zero.
index( "Tizio", "zio" )
L'espressione mostrata come esempio, restituisce il valore tre, corrispondente al primo carattere in cui si ottiene la corrispondenza della stringa zio in Tizio.
length([stringa])
La funzione length() restituisce la lunghezza della stringa fornita come parametro, oppure, in sua mancanza, la lunghezza di $0, ovvero del record attuale. Si osservino gli esempi.
length( "Tizio" )
Restituisce il valore cinque, dal momento che la stringa è composta da cinque caratteri.
length( 10 * 5 )
Dal momento che il parametro della funzione è un'espressione numerica, prima calcola il valore di questa espressione, ottenendo il numero 50, quindi lo trasforma in stringa e restituisce il valore due. In pratica, il numero 50 espresso in stringa è lungo due caratteri.
match( stringa, regexp)
La funzione match() cerca una corrispondenza per l'espressione regolare fornita come secondo parametro, con la stringa che appare come primo parametro. L'espressione regolare dovrebbe poter essere fornita in forma costante, senza che questo fatto venga inteso come un confronto implicito con il record attuale.
Se il confronto ha successo, viene restituita la posizione in cui inizia la corrispondenza nella stringa; inoltre, le variabili predefinite RSTART e RLENGTH vengono impostate rispettivamente a questa posizione e alla lunghezza della corrispondenza. Se il confronto fallisce, la funzione restituisce il valore zero, e così viene impostata la variabile RSTART, mentre RLENGTH riceve il valore -1.
sprintf( stringa_di_formato, espressione[,...])
La funzione sprintf() restituisce una stringa in base alla stringa di formato indicata come primo parametro, in cui le metavariabili %... vengono sostituite, nell'ordine, dai parametri successivi. Le metavariabili in questione sono state elencate nelle tabelle 191.6 e 191.7.
importo = 1000000 sprintf( "Il totale è di L. %i + IVA", importo )
L'espressione finale dell'esempio restituisce la stringa: «Il totale è di L. 1000000 + IVA».
sub( regexp, rimpiazzo[, stringa_da_modificare])
La funzione sub(), cerca all'interno della stringa fornita come ultimo parametro, oppure all'interno del record attuale, la prima corrispondenza con l'espressione regolare indicata come primo parametro. Quindi, sostituisce quella corrispondenza con la stringa fornita come secondo parametro. L'espressione regolare dovrebbe poter essere fornita in forma costante, senza che questo fatto venga inteso come un confronto implicito con il record attuale.
L'ultimo parametro deve essere una variabile, dal momento che viene passata per riferimento, e il suo contenuto deve essere modificato dalla funzione. |
La stringa di sostituzione (il secondo parametro), può contenere il simbolo &, che in tal caso viene sostituito con la sottostringa per la quale si è avverata la corrispondenza con l'espressione regolare. Volendo inserire una e-commerciale letterale, si deve usare la sequenza \&.
La funzione sub() restituisce il numero di sostituzioni eseguite, pertanto può trattarsi del valore uno o di zero.
frase = "ciao, come stai?" sub( /ciao/, "salve", frase )
L'espressione finale dell'esempio restituisce il valore uno, dal momento che la sostituzione ha luogo, e la variabile frase contiene alla fine la stringa: «salve, come stai?».
frase = "ciao, come stai?" sub( /ciao/, "& amico", frase )
Quest'altro esempio riutilizza la sottostringa della corrispondenza, attraverso il riferimento ottenuto con la e-commerciale. Alla fine, la variabile frase contiene: «ciao amico, come stai?».
gsub( regexp, rimpiazzo[, stringa_da_modificare])
La funzione gsub(), cerca all'interno della stringa fornita come ultimo parametro, oppure all'interno del record attuale, tutte le corrispondenze con l'espressione regolare indicata come primo parametro. Quindi, sostituisce quelle corrispondenze con la stringa fornita come secondo parametro. In pratica, si tratta di una variante di sub(), in cui la sostituzione avviene in modo «globale». Valgono tutte le altre considerazioni fatte sulla funzione sub().
substr( stringa, inizio[, lunghezza])
La funzione substr() restituisce una sottostringa di quanto fornito come primo parametro, prendendo ciò che inizia dalla posizione del secondo parametro, per una lunghezza pari al terzo parametro, oppure, fino alla fine della stringa di partenza.
substr( "ciao come stai", 6, 4 )
L'espressione dell'esempio restituisce la stringa «come».
tolower( stringa )
La funzione tolower() restituisce la stringa fornita come parametro trasformata utilizzando solo lettere minuscole.
toupper( stringa )
La funzione toupper() restituisce la stringa fornita come parametro trasformata utilizzando solo lettere maiuscole.
La tabella 191.10 riepiloga le variabili predefinite principali di AWK. In particolare, sono state escluse quelle che riguardano la gestione degli array.
Tabella 191.10. Elenco delle variabili predefinite principali di AWK.
È il caso di ribadire alcuni concetti fondamentali riferiti alle variabili FS e RS.
I record in ingresso sono distinti in base al contenuto della variabile RS. Per restare aderenti allo standard POSIX, questa può contenere un carattere, oppure la stringa nulla. Di solito, la variabile RS contiene il carattere <LF>, ovvero il codice di interruzione di riga comune nei sistemi Unix. Nel caso in cui sia indicata la stringa nulla, si è di fronte a una situazione particolare: i record sono separati da una o più righe bianche o vuote.
I campi dei record in ingresso sono distinti in base al contenuto della variabile FS. Questa variabile può contenere un carattere singolo, oppure un'espressione regolare (senza delimitatori). La corrispondenza con il carattere, o con l'espressione regolare rappresenta ciò che viene considerato il separatore dei campi. Di solito, la variabile FS contiene il carattere <SP>, ovvero lo spazio, e questo costituisce una situazione particolare: la separazione tra i campi è costituita da qualunque spazio orizzontale (<SP> o <HT>), di qualunque lunghezza. Questa eccezione permette di leggere agevolmente i listati tabellari in cui i dati sono incolonnati in qualche modo, attraverso spaziature più o meno ampie.
Gli esempi che vengono mostrati qui sono molto banali, e sono tratti prevalentemente da Effective AWK Programming di Arnold D. Robbins. Tuttavia, qui sono mostrati come script autonomi, utilizzando una notazione che potrebbe sembrare ridondante, ma che può essere utile per non confondere il principiante. Trattandosi di script autonomi, questi ricevono i dati in ingresso solo attraverso lo standard input.
#!/usr/bin/awk -f 1 { if (length($0) > max) { max = length($0) } } END { print max }
Questo esempio serve a trovare la riga di lunghezza massima di un file di testo normale. In pratica, viene scandito ogni record, e viene memorizzata la sua lunghezza se questa risulta superiore all'ultima misurazione effettuata. Alla fine, viene emesso il contenuto della variabile che è stata usata per annotare questa informazione.
#!/usr/bin/awk -f length($0) > 80 { print $0 }
Questo esempio emette tutte le righe di un file di testo che superano la lunghezza di 80 caratteri.
#!/usr/bin/awk -f NF > 0 { print $0 }
In questo caso vengono emesse tutte le righe di un file di testo che hanno almeno un campo. In pratica, vengono escluse le righe bianche e quelle vuote.
#!/usr/bin/awk -f 1 { totale += $5 } END { print "totale:" totale "byte" }
Questo programma è fatto per sommare i valori del quinto campo di ogni record. In pratica, si tratta di incanalare nel programma il risultato di un comando ls -l, in modo da ottenere il totale in byte.
#!/usr/bin/awk -F : -f 1 { print $1 }
Questo programma è banale, ma ha qualcosa di speciale: la riga iniziale, indica che si tratta di uno script di /usr/bin/awk
, che deve essere avviato con le opzioni -F : -f. In pratica, rispetto al solito, è stata aggiunta l'opzione -F :, con la quale si specifica che la separazione tra i campi dei record è data dal carattere :. Il programma, di per sé, è fatto per leggere un file separato in questo modo, come nel caso di /etc/passwd
, e per emettere solo il primo campo, che, sempre nel caso si tratti di /etc/passwd
, corrisponde al nominativo-utente.
#!/usr/bin/awk -F : -f BEGIN { print "Gli utenti seguenti accedono senza password:" } $2 == "" { print $1 }
Si tratta di una variante dell'esempio precedente, dove si presume che i dati in ingresso provengano sicuramente dal file /etc/passwd
. In questo caso, vengono visualizzati i nomi degli utenti che non hanno una password nel secondo campo.
#!/usr/bin/awk -f END { print NR }
Legge il file fornito attraverso lo standard input, ed emette il numero complessivo di record che lo compongono.
#!/usr/bin/awk -f (NR % 2) == 0 { print }
In questo caso, vengono emesse solo i record pari. In pratica, l'espressione (NR % 2) == 0 si avvera solo quando non c'è resto nella divisione della variabile NR per due.
Arnold D. Robbins, Effective AWK Programming, Free Software Foundation
1) Dalla descrizione fatta, è chiaro che le parentesi graffe, indicate nello schema sintattico, fanno parte delle regole e vanno intese in senso letterale.
2) Le espressioni regolari GNU prevedono normalmente la sequenza di escape \b come riferimento alla stringa nulla all'inizio o alla fine di una parola. Tuttavia, dal momento che con AWK questa sequenza deve rappresentare il carattere <BS> (backspace), allora viene sostituita dalla sequenza \y.
3) Dato che le parentesi graffe sono usate nel linguaggio AWK, se queste appaiono nei modelli sintattici indicati, queste fanno parte delle istruzioni e non della sintassi.
4) L'indicazione di una e-commerciale letterale può essere un problema. In generale sarebbe meglio evitarlo. In ogni caso, è necessario leggere la documentazione specifica per il tipo di interprete AWK che si utilizza, per sapere come comportarsi esattamente.