Gentoo Logo

Disclaimer : La versione originale di questo articolo è stata pubblicata da IBM developerWorks ed è di proprietà di Westtech Information Services. Questo documento è una versione aggiornata dell'articolo originale, e contiene numerosi miglioramenti apportati dal Gentoo Linux Documentation team.
Questo documento non è mantenuto attivamente.


Awk per esempi, Parte 2

Indice:

1.  Record, cicli, e array

Record multiriga

Awk è uno strumento efficiente per leggere ed elaborare dati strutturati, come il file di sistema /etc/passwd. /etc/passwd è il database degli utenti di un sistema UNIX, ed è un file separato da due punti (:) che contiene un sacco di informazioni importanti, fra cui tutti gli account utente e gli user-ID. Nel mio articolo precedente, vi ho mostrato come fare il parsing di questo file (dall'inglese to parse, analizzare). Tutto ciò che abbiamo dovuto fare è modificare il valore della variabile FS (Field Separator, separatore di campo) al valore ":".

Settando correttamente la variabile FS, awk può essere configurato per effettuare il parsing praticamente di ogni tipo di dato strutturato, a patto che ci sia un record per linea. Tuttavia, modificare semplicemente il valore di FS non ci servirà a niente nel caso in cui un record occupi più linee. In queste situazioni, abbiamo bisogno anche di modificare la variabile RS (Record Separator, separatore di record). La variabile RS informa awk della terminazione di un record e dell'inizio del record successivo.

Per esempio, diamo un'occhiata a come risolveremmo il problema di gestire una lista di indirizzi di partecipanti al Programma Nazionale di Protezione dei Testimoni.

Codice 1.1: Esempio di voci nella lista di indirizzi del Programma Nazionale di Protezione dei Testimoni

Giacomo il Faina
Viale dei Giardini 12
00100 Roma

Tonino
Largo Sconosciuto
20027 Spiate (MI)

Idealmente, vorremmo che awk riconoscesse ogni gruppo di tre righe come un singolo record, piuttosto che come come tre record separati. Il nostro codice diventerebbe molto più semplice se awk riconoscesse la prima riga dell'indirizzo come il primo campo ($1), l'indirizzo come secondo campo ($2), e il codice postale e la città come campo $3. Il seguente codice fa proprio quello che vogliamo:

Codice 1.2: Creare un campo dall'indirizzo

BEGIN {
    FS="\n"
    RS=""
}

Sopra settare FS a "\n", informa awk che ogni campo appare su una sua riga. Settando RS a "", diciamo anche ad awk che ogni record è separato dal successivo da una linea vuota. Una volta che awk sa come è formattato l'input, può fare tutto il lavoro di parsing per noi, e la restante parte dello script è semplice. Vediamo un esempio completo che fa il parsing dell'elenco di indirizzi e stampa ogni record su un'unica linea, separando i campi con una virgola.

Codice 1.3: Script completo

BEGIN {
    FS="\n"
    RS=""
}
{ print $1 ", " $2 ", " $3 }

Se questo script viene salvato come indirizzi.awk, e gli indirizzi sono contenuti in un file chiamato indirizzi.txt, potete eseguire questo script scrivendo awk -f indirizzi.awk indirizzi.txt. Questo codice produce il seguente output:

Codice 1.4: L'output dello script

Giacomo il Faina, Viale dei Giardini 12 , 00100 Roma
Tonino, Largo Sconosciuto, 20027 Spiate (MI)

Le variabili OFS e ORS

Nell'istruzione print di indirizzi.awk, potete notare che awk concatena (unisce) stringhe che sono poste una di fianco all'altra sulla stessa riga. Abbiamo usato questo metodo per inserire una virgola e uno spazio (", ") tra i tre campi che appaiono su una riga. Sebbene questo metodo funzioni, è un po' brutto da vedere. Invece di inserire la stringa costante ", " tra i nostri campi, possiamo istruire awk perchè lo faccia per noi, settando la variabile speciale OFS. Date un'occhiata a questo pezzo di codice:

Codice 1.5: Codice di esempio

print "Hey", "ciao", "Jim!"

Le virgole su questa linea di codice non sono stringhe costanti. Invece dicono ad awk che "Hey" "ciao" e "Jim!" sono campi (di uscita) distinti e che il contenuto della variabile OFS deve essere stampato tra ciascuna stringa. Di default, awk produce il seguente output:

Codice 1.6: Output prodotto da awk

Hey ciao Jim!

Questo ci mostra che, di default, OFS è settata a " ", un singolo carattere di spaziatura. Però possiamo facilmente ridefinire OFS in maniera che awk inserisca il nostro separatore di campo preferito. Di seguito abbiamo una versione rivista del nostro programma originale indirizzi.awk che usa OFS per mandare in uscita come separatore la stringa ", ":

Codice 1.7: Ridefinire OFS

BEGIN {
    FS="\n"
    RS=""
    OFS=", "
}
{ print $1, $2, $3 }

Awk ha anche una variabile speciale chiamata ORS, (output record separator, separatore dei record in uscita). Settando ORS, che di default vale il carattere "\n" che crea una nuova linea, possiamo controllare ciò che viene stampato automaticamente alla fine di un'istruzione di print. Il valore di default di ORS fà in modo che awk mandi in uscita ogni istruzione di print su una linea diversa. Se volessimo un output con interlinea doppia, dovremmo settare ORS al valore "\n\n". O, se volessimo che i record fossero separati da un singolo carattere di spaziatura (senza andare a capo), dovremmo settare ORS al valore " ".

Dal multiriga alle tabulazioni

Ipotizziamo di aver creato uno script che ha convertito il nostro elenco di indirizzi in un formato con un record per linea, in cui i campi sono delimitati da tabulazioni. Tutto questo perchè, per esempio, abbiamo bisogno di importare questi dati in un foglio elettronico. Dopo aver usato una versione leggermente modificata di indirizzi.awk, sarà diventato chiaro che il nostro programma funziona solamente per indirizzi su tre righe. Se awk incontrasse il seguente indirizzo, la quarta linea sarebbe ignorata e non stampata:

Codice 1.8: Esempio di voce

Cugino Vincenzo
Autorimessa Vincenzo
Via del Biscione, 657
40026 Imola (BO)

Per gestire situazioni come questa, sarebbe meglio che il nostro codice tenesse in conto il numero di righe per indirizzo, stampando ciascuna riga in ordine. Adesso, il codice stampa solamente i primi tre campi dell'indirizzo. Ecco un po' di codice che fa quello che ci serve:

Codice 1.9: Codice migliorato

BEGIN {
    FS="\n"
    RS=""
    ORS=""
}

{
    x=1
    while ( x<NF ) {
        print $x "\t"
        x++
    }
    print $NF "\n"
}

Prima settiamo il separatore di campo a "\n" e il separatore di record a "", cosicchè awk analizzi correttamente gli indirizzi su più righe, come prima. Poi settiamo il separatore di record in uscita ORS a "", in modo da non mandare in uscita una nuova riga alla fine di un'istruzione di print. Questo significa che, se vogliamo che il testo sia su una nuova linea, dovremmo scrivere esplicitamente print "\n".

Nel blocco principale, creiamo una variabile di nome x che contiene il numero del campo che stiamo attualmente processando. All'inizio è settata a 1. Poi usiamo un ciclo while (un costrutto iterativo identico a quello del C) per scorrere tutti i campi eccetto l'ultimo, stampando il campo e un carattere di tabulazione. Alla fine, stampiamo l'ultimo campo e andiamo a capo; ancora una volta, poichè ORS vale "", print non andrà a capo da solo. L'uscita del programma somiglierà a questa, proprio come volevamo.

Codice 1.10: L'uscita voluta. Non è carina a vedersi ma delimitata da tabulazione per una facile importazione in un foglio elettronico

Giacomo il Faina	Viale dei Giardini 12 	00100 Roma
Tonino	Largo Sconosciuto	20027 Spiate (MI)
Cugino Vincenzo	Autorimessa Vincenzo	Via del Biscione, 657	40026 Imola (BO)

Costrutti iterativi

Abbiamo già visto il costrutto per il ciclo while, identico al suo analogo in C. Awk ha anche un ciclo "do...while" che valuta la condizione alla fine del blocco di codice iterativo, piuttosto che all'inizio come uno ciclo while standard. E' simile ai cicli "repeat...until" che si possono trovare in altri linguaggi. Ecco un esempio.

Codice 1.11: Esempio di do...while

{
    count=1
    do {
        print "In ogni caso vengo stampato almeno una volta"
    } while ( count != 1 )
}

Siccome la condizione viene valutata dopo il blocco di codice, un ciclo "do...while", al contrario di un normale ciclo while, viene eseguito almeno una volta. In altre parole m un ciclo while standard non verrà mai eseguito se la condizione è falsa la prima volta che il ciclo viene incontrato.

Cicli for

Awk dà la possibilità di creare cicli for che, come i cicli while, sono identici ai loro analoghi in C:

Codice 1.12: Ciclo di esempio

for ( assegnamento iniziale; confronto; incremento ) {
    blocco di codice
}

Ecco un esempio veloce:

Codice 1.13: Esempio veloce

for ( x = 1; x <= 4; x++ ) {
    print "iterazione",x
}

Questo pezzetto di codice stamperà:

Codice 1.14: Output del pezzetto di codice sopra

iterazione 1
iterazione 2
iterazione 3
iterazione 4

Break e continue

Ancora una volta, proprio come il C, awk ci fornisce istruzioni di break e continue, Queste istruzioni ci permettono di avere un miglior controllo sui vari costrutti iterativi. Ecco un pezzetto di codice che ha un bisogno sdisperato di un'istruzione di break.

Codice 1.15: Pezzetto di codice che ha bisogno di un break

while (1) {
    print "Sempre e per sempre..."
}

Siccome 1 è sempre una condizione vera, questo ciclo eseguirà all'infinito. Ecco un ciclo che farà solo 10 iterazioni:

Codice 1.16: Ciclo che viene eseguito solo 10 volte

x=1
while(1) {
    print "iterazione",x
    if ( x == 10 ) {
        break
    }
    x++
}

Qui, l'istruzione di break è usata per rompere il ciclo più interno. "break" farà terminare il ciclo immediatamente e farà continuare l'esecuzione dall'istruzione successiva al codice del ciclo.

l'istruzione continue fa da complemento al break e funziona in questo modo:

Codice 1.17: L'istruzione continue è duale al break

x=1
while (1) {
    if ( x == 4 ) {
        x++
        continue
    }
    print "iterazione",x
    if ( x > 20 ) {
        break
    }
    x++
}

Questo codice stamperà "iterazione 1", "iterazione 2", ... fino a "iterazione 21", eccetto "iterazione 4". Infatti, quando x vale 4, esso viene incrementato e si esegue l'istruzione continue che fa iniziare una nuova iterazione senza eseguire il resto del blocco while. L'istruzione continue funziona, come la break, con ogni tipo di costrutto iterativo di awk. Quando è usata nel corpo di un ciclo for, continue fa sì che la variabile di controllo del loop sia automaticamente incrementata. Ecco un ciclo for equivalente:

Codice 1.18: Ciclo for equivalente

for ( x=1; x<=21; x++ ) {
    if ( x == 4 ) {
        continue
    }
    print "iterazione",x
}

Non è stato necessario incrementare x prima di chiamare continue come avevamo fatto con il ciclo while, poichè il for incrementa automaticamente x.

Gli array

Sarete felici di sapere che awk anche supporto per gli array. Però, in awk, gli indici degli array partono da 1 e non da zero:

Codice 1.19: Esempio di array in awk

myarray[1]="jim"
myarray[2]=456

Quando awk incontra il primo assegnamento, viene creato l'array myarray e l'elemento myarray[1] viene settato alla stringa "jim". Dopo che il secondo assegnamento viene valutato, l'array ha due elementi.

Una volta definito un array, ci fornisce un utile meccanismo per scorrerne gli elementi, come potete vedere nel codice seguente:

Codice 1.20: Scorrere tutti gli elementi di un array

for ( x in myarray ) {
    print myarray[x]
}

Questo codice stamperà ogni elemento dell'array myarray. In questo speciale tipo di for, usando la parola chiave "in", awk assegnerà ogni indice esistente dell'array myarray a x (la variabile di controllo del ciclo), eseguendo il corpo del for dopo ogni assegnamento. Sebbene questa sia una caratteristica molto utile di awk, soffre di uno svantaggio: quando awk scorre l'array, non segue un ordine particolare. Questo significa che non c'è modo di sapere se l'output del codice visto sopra sarà:

Codice 1.21: Output del codice visto sopra

jim
456

o

Codice 1.22: Altro possibile output del codice visto sopra

456
jim

Per parafrasare liberamente Forrest Gump, scorrere il contenuto di un array è come una scatola di cioccolatini: non sai mai cosa puoi trovare. Questo ha in qualche modo a che fare con la "stringosità" degli array awk, che adesso vedremo.

La stringosità degli indici degli array

Nel mio articolo precedente, vi ho mostrato che awk in realtà memorizza i valori numerici sotto forma di stringhe. Mentre awk realizza le operazioni di conversione necessarie a far funzionare il tutto, questo lascia la possibilità di scrivere del codice un po' strano:

Codice 1.23: Codice strano

a="1"
b="2"
c=a+b+3

Dopo l'esecuzione di questo codice, c vale 6. Poichè awk è "stringoso", sommare le due stringhe "1" e "2" è funzionalmente uguale a sommare i numeri 1 e 2. In entrambi i casi, awk realizzerà correttamente l'operazione. La natura "stringosa" di awk è discretamente intrigante; vi potete chiedere che cosa succede se usate delle stringhe come indici di array. Per esempio, prendete il seguente codice:

Codice 1.24: Codice di esempio

myarr["1"]="Sig. Rossi"
print myarr["1"]

Come vi potete aspettare, questo codice stamperà "Sig. Rossi". Ma cosa succede se togliamo le virgolette dal secondo indice "1"?

Codice 1.25: Senza le virgolette

myarr["1"]="Sig. Rossi"
print myarr[1]

Ipotizzare il risultato di questo pezzo di codice è più difficile. Awk considera myarr["1"] e myarr[1] come due elementi separati dell'array oppure no? La risposta è che si riferiscono allo stesso elemento e stamperanno "Sig. Rossi" come nel primo pezzo di codice. Sebbene possa sembrare strano, dietro le quinte awk ha sempre usato indici stringa per i suoi array.

Dopo aver imparato ciò, alcuni di voi potrebbero essere tentati di eseguire del codice strambo come questo:

Codice 1.26: Codice strambo

myarr["nome"]="Sig. Rossi"
print myarr["nome"]

non solo questo codice non genera errori, ma è funzionalmente identico al nostro esempio precedente, e stamperà "Sig. Rossi" proprio come prima! Come potete vedere awk non ci costringe ad usare come indici solo variabili intere; possiamo usare indici stringa se vogliamo, senza creare alcun problema. Ogni volta che usiamo indici non numerici come myarr["nome"], stiamo usando array associativi. Tecnicamente, dietro le quinte awk non fa niente di diverso quando usiamo un indice stringa (poichè anche se usiamo un indice intero, awk lo tratta sempre come una stringa). Ad ogni modo potete sempre chiamarli array associativi; sembrerà fico e farà impressione sul vostro capo. Questa cosa della stringosità degli indici sarà il nostro piccolo segreto. ;)

Strumenti per gli array

Parlando di array, awk ci dà un sacco di flessibilità. Possiamo usare indici stringa e non siamo obbligati a rispettare una sequenza numerica continua per gli indici (per esempio, possiamo definire myarr[1] e myarr[1000], ma lasciare gli altri elementi non definiti). Anche se questo può essere molto utile, in qualche circostanza può creare confusione. Fortunatamente, awk ci offre un paio di utili funzionalità per rendere gli array più maneggevoli.

Primo, possiamo cancellare gli elementi di un array. Se volete cancellare l'elemento 1 del vostro array fooarray, scrivete:

Codice 1.27: Cancellare elementi di un array

delete fooarray[1]

E, se volete vedere se un particolare elemento di un array esiste, potete usare lo speciale operatore booleano "in" come nell'esempio seguente:

Codice 1.28: Controllare se esiste un particolare elemento di un array

if ( 1 in fooarray ) {
    print "Sì!  C'è."
} else {
    print "No!  Non lo trovo."
}

La prossima volta

Abbiamo scoperto un bel po' di cose in questo articolo. La prossima volta affinerò la vostra conoscenza di awk mostrandovi come usare le funzioni matematiche e sulle stringhe e insegnandovi a creare delle vostre funzioni. Vi mostrerò anche la creazione di un programma per la contabilità. Per intanto vi incoraggio a scrivere qualche programma da soli e a controllare i seguenti riferimenti:

2.  Risorse



Stampa

Aggiornato il 31 ottobre 2005

Oggetto: Nella puntata successiva alla sua prima introduzione ad awk, Daniel Robbins continua ad esplorare awk, un grande linguaggio dal nome bizzarro. Daniel vi mostrerà come gestire record multiriga, come usare i costrutti iterativi, e come creare ed usare gli array. Alla fine di questo articolo, vi sarà familiare un'ampio insieme delle funzionalità di awk, e sarete in grado di scrivere di vostro pugno potenti script awk.

Daniel Robbins
Autore

Luca Martini
Traduzione

Donate to support our development efforts.

Copyright 2001-2014 Gentoo Foundation, Inc. Questions, Comments? Contact us.