Come tutti i linguaggi di programmazione, awk, consente di utilizzare variabili all’interno del codice.
I nomi di variabili possono essere composti da lettere, digit e/o dal simbolo “_”, con l’unico vincolo che non possono iniziare per un digit.
Attenzione: l’utility awk interpreta le variabili in modo case-sensitive, I,e., le variabili “Var”, “var”, “VAR”, etc… sono diverse!
Alcune caratteristiche del linguaggio awk:
È possibile assegnare un valore ad una variabile utilizzando la seguente sintassi: nomevariabile=stringa
Ad es. X=7; y=7.8; z=1.2e3; (notazione base-esponente, I.e,. z=1200) argomento=”variabili”;
In alternativa, è possibile assegnare il valore della variabile da riga di comando utilizzando l’opzione -v dell’interprete.
Attenzione: A DIFFERENZA della shell, per utilizzare il valore di una variabile è sufficiente utilizzare il nome della variabile, E.g. x=y+z;
L’interprete awk converte automaticamente le stringhe in numeri e viceversa nel momento in cui il contesto del programma lo richiede. Ad esempio:
Si consideri il seguente esempio: Due=2; Tre=3; print (Due Tre)+4;
In questo caso: L’assenza di un operatore nell’espressione (Due Tre) indica all’interprete di eseguire una concatenazione (tra stringhe). Le variabili (numeriche) Due e Tre vengono convertite in stringhe, e viene effettuata la loro concatenazione ottenendo la stringa “23″.
La presenza dell’operatore (matematico) “+”, indica all’interprete di convertire la stringa “23″ in intero. Viene quindi effettuata la somma degli interi 23 e 4, il cui risultato viene visualizzato.
Le variabili numeriche, in awk, rappresentano sempre numeri con virgola. È errore comune, per chi scrive script shell ed awk, assumere che il risultato di una operazione sia sempre un intero.
È possibile utilizzare le seguenti operazioni su variabili numeriche:
Per le stringhe esiste solo l’operatore di concatenazione che consiste nella giustapposizione di stringhe.
In alcuni casi è necessario utilizzare le parentesi per forzare l’esecuzione della concatenazione prima di altri operatori. Riprendiamo un esempio dalla lezione 6:
print “Il record “NR…”> (“outfile_” NR);
L’omissione delle parentesi sarebbe stata interpretata come ‘print… > ‘”outfile_” NR’, generando un errore di sintassi visto che l’interprete si aspetta UN solo elemento a destra del simbolo >.
In awk non esistono, come in molti linguaggi di programmazione, costanti true e false.
L’utility awk interpreta:
L’interprete associa ad ogni costante ed ad ogni variabile un attributo numerico o stringa attraverso le seguenti regole:
Awk considera gli input dell’utente (e.g., i campi del record letto dal file) come valori numerici mentre le costanti definite nel codice, se anche composte da soli digit, vengono considerate stringhe.
Il confronto tra variabili e costanti avviene utilizzando gli operatori appositi a seconda degli attributi numerico/stringa degli item da confrontare.
In generale, l’interprete tende ad eseguire confronti tra stringhe. Solo nel caso in cui gli elementi da confrontare siano entrambi numerici (o “stringhe numeriche”) vengono utilizzati operatori numerici.
Il confronto tra item, siano essi variabili o costanti, numerici o stringhe, vengono effettuati dai medesimi operatori di confronto, indicati di seguito:
Una espressione booleana è una combinazione di operazioni di confronto o di matching attraverso i seguenti operatori booleani:
Gli statement di controllo consentono l’esecuzione condizionata di parti del codice. Awk fornisce i seguenti statement: [Mostra codice
Un esempio di utilizzo dello statement if-then-else: Mostra codice.
Un secondo esempio di script: Mostra codice.
Il costrutto if-then-else ha la seguente sintassi:
if (condizione) codice-then [ else codice-else ]
Se la condizione è vera, viene eseguito il codice-then, altrimenti, se presente, viene eseguito il codice-else. Lo statement else è opzionale.
La condizione può essere una qualsiasi espressione. Si rammenta che in awk, uno 0 od una stringa vuota equivale a "falso", mentre un valore numerico non nullo od una stringa non vuota equivale è "vero".
È possibile comporre espressioni elementari utilizzando gli operatori and (&&), or (||) e not (!).
Il costrutto while ha la seguente sintassi:
while (condizione) body
L’interprete verifica la condizione e, se questa è vera, esegue il body. Se la condizione è inizialmente falsa, il body non viene mai eseguito.
Se il body consiste di un’unica istruzione, può essere posto sulla stessa riga del while e non è necessario utilizzare il separatore “;”.
Se il body consiste di più comandi, è necessario utilizzare le parentesi graffe per definirne l’inizio e la fine.
Per la condizione valgono le stesse considerazioni fatte per il costrutto if-then-else.
In questo esempio: Mostra codice, nel blocco BEGIN vengono inizializzate le variabili somma, pari ed ind. Quest'ultima inizializzazione è inutile, se il file contiene più righe, visto che questo blocco viene eseguito una sola volta.
Il primo while visualizza gli elementi contenuti nella riga in esame. Utilizzando come input il seguente file: Mostra codice, viene scartata la prima riga visto che, su questa riga, quando ind=1, $ind=1.
A questo punto è necessario reinizializzare la variabile ind affinché il secondo while possa essere eseguito.
Si noti che l'operatore di post-incremento consente di verificare il valore di ind "prima" del suo incremento. In realtà, l'operatore incrementa la variabile ind ma ritorna il valore della variabile precedente l'operazione.
Il costrutto do-while ha la seguente sintassi:
do body while(condizione)
L’unica differenza tra I costrutti while e do-while è che quest’ultimo esegue il body prima di verificare la condizione.
È chiaro, quindi, che il body viene eseguito almeno una volta indipendentemente dal fatto che la condizione sia vera o falsa.
L’esempio: Mostra codice, riporta una variante dello script appena visto, riscritto con in costrutto do-while.
È da notare la diversa inizializzazione della variabile ind. A differenza dell'esempio precedente, il valore di ind viene incremento alla fine del costrutto. In questo caso è quindi necessario che ind punti direttamente il primo campo del record in esame.
Sebbene il do-while renda più immediato l'utilizzo dell'operatore di pre-incremento a fine ciclo ("esegui la prossima iterazione se il prossimo valore dell'indice è valido"), è necessario prestare massima attenzione alle condizioni di uscita. Si ricorda che i campi del record in esame vengono numerati da 1 a NF. Quando il valore di ind è pari a NF-1, il suo pre-incremento, accoppiato ad una condizione "
Infine si fa presente che la condizione di uscita da un ciclo do-while, come nel caso del costrutto while, può essere una qualunque espressione booleana.
Elemento distintivo dei costrutti while e do-while è la possibilità di definire condizioni di uscita arbitrarie da un loop.
Esistono molti casi in cui il numero di volte in cui deve essere eseguito il body è noto prima dell’esecuzione del ciclo, e.g., il numero di elementi n un array.
In questi casi, l’utilizzo del costrutto for è conveniente in quanto consente la definizione compatta delle fasi di inizializzazione, verifica della condizione di uscita e modifica dell’indice.
La sintassi del costrutto for è la seguente:
for (inizializzazione; condizione; incremento)
body
L’interprete awk, esegue una volta l’inizializzazione e, finché la condizione è vera, esegue il body e, successivamente, l’incremento.
Nel costrutto for:
Un esempio di utilizzo del ciclo for: Mostra codice
Lo statement switch consente di evitare l’annidamento di costrutti if-then-else. La sintassi del costrutto è la seguente: Mostra codice. Attenzione: questo costrutto potrebbe NON esistere o NON essere abilitato in alcuni interpreti.
A differenza della maggior parte dei linguaggi di programmazione, ogni istruzione case può contenere una costante (numerica o stringa) o una espressione regolare.
Quando l'espressione corrisponde ad uno dei valori specificati, l'azione associata viene eseguita. A quel punto l'interprete riprende l'analisi del valore contenuto al case successivo. Questo processo termina quando viene eseguito uno dei seguenti statement: break, continue, next, nextfile o exit.
Il seguente esempio: Mostra codice, mostra alcune peculiarità appena descritte.
L'interprete valuta l'espressione e confronta il suo valore con:
- L'intero 9. In questo caso, viene eseguita un confronto numerico. Visto che non c'è un break, anche in caso di uguaglianza, lo statement NON viene interrotto.
- La stringa "9": Viene eseguito il confronto convertendo il valore del risultati in stringa. Qualora vi sia una corrispondenza, la presenza di break interrompe l'esecuzione dello statement switch.
- Una espressione regolare: l'interprete verifica che la stringa ottenuta dalla conversione del risultato dell'espressione contenga il pattern specificato.
- Default: eseguito se nessuno dei casi precedenti è valido. Si noti che l'assenza di break nell'azione associata a default non impedisce l'analisi dell'ultimo case.
Lo statement break interrompe l’esecuzione di un ciclo, for, while o do-while e dello switch. Nel caso di loop annidati, viene interrotto solo il loop più interno.
L’interprete esegue la prima istruzione successiva alla fine del loop interrotto.
A differenza della shell, break NON prende parametri. Quindi non è possibile interrompere contemporaneamente più loop annidati.
Lo statement continue interrompe l’esecuzione di un ciclo, while o do-while. L’effetto di continue è quello di forzare l’interprete a “saltare” le istruzioni del body e di riprendere lo stesso loop dal ciclo successivo.
Come nel caso degli script shell, è necessario prestare la massima attenzione nell’utilizzo del continue nei cicli while e do-while. L’esecuzione di un continue prima della modifica delle variabili che controllano la condizione di uscita può portare ad un ciclo infinito.
Il seguente script Mostra codice riporta un esempio di utilizzo di break e continue.
Il while esterno incrementa la variabile i ed esegue il for interno solo se il valore di i è pari. Difatti lo statement continue viene eseguito quando la condizione è vera, i.e., i%2=1.
Il for interno incrementa l'indice j e visualizza le coppie i,j solo se i<j. Infatti quando le due variabili assumono lo stesso valore, viene eseguito il break che interrompe il ciclo for.
Output atteso: Mostra codice.
Lo statement next forza l’interprete ad interrompere l’elaborazione del record corrente ed a leggere il record successivo dal file.
Si noti che, nel caso in cui lo script awk sia composto da più regole, l’esecuzione di next NON interrompe solo l’esecuzione della “regola corrente” ma forza l’interprete a NON eseguire nessuna altra regola.
Lo standard POSIX NON definisce il comportamento dell’interprete nel caso in cui next sia eseguito nei blocchi BEGIN o END.
Lo statement nextfile forza l’interprete ad interrompere l’elaborazione del record corrente ed a leggere il primo record del file successivo al file corrente (se esiste).
Questo statement è, chiaramente, più forte rispetto allo statement next.
Il seguente script: Mostra codice, visualizza, per ogni file in input, tutte le righe di posizione pari. In questo caso, l'espressione FNR%2 ritorna uno ("vero") quando l'interprete analizza le righe dispari del file. In questo caso viene eseguito lo statement next che forza la lettura del record successivo, evitando la print $0.
Il seguente script: Mostra codice, invece, visualizza le righe pari dei soli file il cui nome corrisponde alla RE /^slide/, i.e., il cui nome inizi per 'slide'.
L'interprete scarta, invece, tutti i file che non corrispondono al criterio descritto, dopo averne letto il primo record.
L'utilizzo della variabile Fname è solo utile al fine di visualizzare una sola volta il nome del file.
Lo statement exit termina l’esecuzione dello script, con le seguenti regole:
Se exit è eseguito nel blocco BEGIN, l’interprete non legge alcun record dall’input ma esegue il blocco END.
Se exit è eseguito nel blocco END, l’interprete termina lo script immediatamente.
Se exit è eseguito all’interno di una regola, l’interprete interrompe la lettura dell’input ed esegue il blocco END.
Lo statement può ricevere un parametro intero opzionale. Il valore del parametro viene ritornato come exit value dall’interprete. Se exit è invocato senza parametri, l’interprete ritorna il valore zero. Si rammenta che per la shell, il valore zero equivale a “successo”.
Nel caso in cui l’exit viene eseguito:
Il valore di ritorno dell’interprete sarà x.
Un esempio di utilizzo di exit: Mostra codice
Come altri linguaggi di programmazione, awk fornisce la possibilità di utilizzare array. Esistono però, rispetto alla maggior parte dei linguaggi di programmazione, alcune differenze fondamentali:
In awk, gli array sono “associativi”, I.e., ogni elemento di un array è in realtà la coppia (indice, valore). Questo rende possibile:
Attenzione:
Per referenziare l’elemento “indice” dell’array “nomearray” è possibile utilizzare la sintassi nomearray[indice]
Il riferimento ad un indice che non è presente nell’array ritorna la stringa vuota “”.
È possibile effettuare le seguenti operazioni:
Attenzione: la cancellazione di un elemento con indice x da un array NON corrisponde ad assegnare il valore “” all’indice x.
Il seguente script: Mostra codice, riporta esempi di assegnazione ad un array.
Sono da notare:
L'ordine in cui vengono visualizzati gli elementi dell'array è dipendente dall'implementazione dell'interprete e NON è predicibile.
Il valore assegnato da a[1]="numero 1" viene sostituito da a["1"]="stringa 1" perché l'interprete converte il numero 1 nella stringa "1". Quindi la seconda assegnazione NON inserisce un nuovo elemento nell'array ma modifica un elemento esistente.
È possibile utilizzare valori numerici non interi come indici dell'array, come nel caso a[3.5]=3.5. Attenzione a non confondere I ruoli. In a[3.5], il numero "3.5" è l'indice dell'array, mentre l'elemento a destra dell'operatore di assegnazione è il valore assegnato.
Effetto della variabile CONVFMT: L'interprete trasforma ogni valore numerico non intero utilizzando il formato specificato in questa variabile. Lo script indica di mantenere solo I primi due digit dopo la virgola. Per questa ragione, sebbene I numeri 1.123 ed 1.12 siano diversi, all'atto della conversione saranno entrambi convertiti nelle stringhe "1.12". Quindi la seconda assegnazione porta ad una sovrascrittura del valore "Errore" assegnato all'indice 1.123.
La variabile CONVFMT ha effetto solo sulla parte decimale dei valori numerici. La conversione di interi avviene sempre nel modo "ovvio".
Un possibile output: Mostra codice
Un esempio di utilizzo di array e relativo output: Mostra codice
Lo script dell'esempio riporta un altro problema che può verificarsi a causa della modifica di CONVFMT.
Lo script assegna il valore 7 all'indice 3.15.
Successivamente viene modificato il criterio di conversione, riducendo ad uno il numero di digit utilizzati per la conversione.
Il test successivo di appartenenza fallisce, sebbene l'indice richiesto sia lo stesso utilizzato per l'inserimento.
Un esempio di cancellazione di elementi di array: Mostra codice.
L'array viene inizializzato con le coppie (I,I), con I=1...5.
Successivamente vengono cancellati gli elementi di posto dispari utilizzando lo statement delete.
Quindi vengono visualizzati gli elementi contenuti nell'array utilizzando due possibili opzioni:
Viene prima impiegato lo statement "in" per elencare tutti gli indici presenti nell'array
Viene eseguita una scansione su tutti I possibili indici tra 1 e 5 ed, utilizzando lo statement "in" nel costrutto if viene verificata la presenza di un elemento nell'array.
Successivamente viene eseguita una "cancellazione" (SBAGLIATA), assegnando il valore "" a tutti gli indici presenti nell'array.
L'errore si evince dal fatto che, a differenza della delete, rieseguendo la scansione dell'array, gli elementi che avevamo "cancellato", in realta', sono ancora presenti nell'array ma con un valore associato pari a "".
Infine la delete è utilizzata per cancellare tutti gli elementi dell'array.
Output: Mostra codice
All’interno di uno script awk è possibile invocare funzioni definite dal linguaggio per la manipolazione di variabili numeriche e stringhe.
Per invocare una funzione, è sufficiente scrivere il nome della funzione, indicando gli eventuali parametri tra parentesi tonde. Nel caso in cui la funzione non prende parametri, è comunque necessario riportare le parentesi tonde. Ad es., y=int(x); z=rand().
Sebbene eventuali spazi tra il nome della funzione built-in e le parentesi vengono scartati dall’interprete, è buona norma NON interporre spazi. Come vedremo, la presenza di questi spazi generano errori per le funzioni definite dall’utente.
Se uno dei parametri passati alla funzione è una espressione, si tenga conto che l’espressione viene sempre valutata completamente prima di invocare la funzione. Ad es., nell’invocazione di log(I++), nonostante l’operatore utilizzato sia di post-incremento, l’interprete valuta I++, incrementando il valore di I e, successivamente invoca log().
Qualora la funzione riceva in input più parametri, ed esistono più espressioni passate alla funzione, l’ordine di valutazione delle singole espressioni NON è definito. Ad es., per l’invocazione atan2(I++, 2*I), NON è possibile, a priori, sapere se il post-incremento viene eseguito prima o dopo il prodotto.
Nelle slide successive vengono riportati alcuni esempi di funzioni disponibili.
Le funzioni numeriche built-in:
int(x): intero compreo tra zero ed x,
troncato verso lo zero,
e.g. int(3.9)=3, int(-3.9)=-3
sqrt(x): radice quadrata di x
exp(x): ritorna e^x
log(x): logaritmo di x in base e
sin(x): seno di x
cos(x): coseno di x
atan2(y,x). Arcotangente di y/x in radianti.
rand(): ritorna un valore casuale
nell’intervallo [0,1)
srand(x): assegna il seed del generatore
pseudo-casuale per la generazione di valori
casuale ad x.
Se il parametro è omesso, viene utilizzato il valore corrente di data ed ora.
Esempi di funzioni built-in su stringhe:
index(sub, stringa): ritorna l’indice della prima occorrenza di sub in stringa.
Ritorna zero se sub non è presente in stringa.
length(x). Se x è una stringa, ritorna il numero di Caratterin in x.
Se x è un numero, viene convertito In stringa y e la funzione ritorna la lunghezza di y.
Se x è una espressione numerica, l’espressione viene valutata e la funzione ritorna la lunghezza della stringa che rappresenta il risultato.
substr(stringa, inizio, [lung]): estrae una sottostringa dal string, partendo dal carattere con indice inizio. Il parametro lung è opzionale. Se presente, la funzione estrae lung caratteri da stringa, a partire da inizio.
Il linguaggio consente di definire funzioni all’intero degli script. La sintassi per la definizione di una funzione è la seguente: Mostra codice.
È possibile definire una funzione tra qualsiasi coppia di regole (NON all'interno di una regola!). Non è necessario definire la funzione prima del suo utilizzo. Difatti l'interprete awk legge l'intero script prima di iniziare l'esecuzione.
La lista dei parametri è opzionale. Come in tutti i linguaggi di programmazione, i nomi dei parametri vengono utilizzati per mantenere una copia locale (alla funzione) del valore passato dalla funzione chiamante.
Una funzione può invocare funzioni ed, in particolare, può invocare se stessa. Awk, consente quindi la definizione di funzioni ricorsive.
Come per le funzioni built-in, l'invocazione di una funzione definita dall'utente avviene utilizzando il nome seguito dalla lista dei parametri inclusa tra parentesi tonde. NON è possibile lasciare spazi (o tab) tra il nome della funzione e l'elenco dei parametri.
Il passaggio dei parametri è fatto per valore, i.e., l'interprete esegue una copia del valore della variabile nella funzione chiamante e la funzione opera su questa copia.
Quando il parametro è un array, il passaggio dei parametri diventa per riferimento, i.e.,. Ogni modifica all'array nella funzione è visibile nella funzione chiamante.
Le funzioni possono ritornare una espressione utilizzando lo statement return espressione. Se espressione è omesso, il valore di ritorno è impredicibile. Quando l'interprete raggiunge la fine di una funzione senza trovare un return, aggiunge automaticamente un return senza espressione.
Esempio di script e relativo output: Mostra codice
Particolare attenzione deve essere posta allo scoping delle variabili.
Le uniche variabili locali alla funzione sono solo quelle indicate nell’elenco del parametri. Le altre variabili sono sempre “globali”. D’altro canto è possibile invocare una funzione passando un numero di parametri inferiore a quelli attesi dalla funzione. In questo caso, I parametri non utilizzati assumono il valore di variabili locali alla funzione con valore iniziale pari a “”.
Nell’esempio: Mostra codice, la funzione a riceve due parametri, b e d ed utilizza una variabile c. Semplicemente visualizza I valori di b, c e d, assegna loro il valore 1 e visualizza di nuovo il loro valore.
Il blocco BEGIN utilizza le stesse variabili assegnando loro il valore 7. La funzione a viene invocata passando come unico parametro la variabile b.
All'interno della funzione le variabili:
Output dello script: Mostra codice.
1. Introduzione ai sistemi Unix
2. Principi di programmazione Shell
3. Esercitazioni su shell scripting - parte prima
5. Esercitazioni su shell scripting - parte seconda
6. Espressioni Regolari ed Introduzione ad AWK
7. Esercitazioni su espressioni regolari ed awk scripting
9. Esercitazioni su awk scripting - parte seconda
10. Programmazione in linguaggio C: Input/Output di basso livello
11. Esercitazioni su I/O di basso livello
12. Interazione con file di sistema e variabili d'ambiente
13. Esercitazioni sulla gestione dei file di sistema e le variabili...
14. System call per la gestione di file e directory
15. Esercitazioni su gestione file e directory
16. La programmazione multi-processo
17. Esercitazioni su programmazione multi-processo
18. I Segnali
20. I Socket
Bash guide for beginners: Capitolo 6
GAWK: Effective Awk Programming: Cap. 6 (par. 4); Cap. 7 (par. 1-7); Cap. 8