Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D Le Miniguide all'orientamento Gli eBook di Federica La Corte in Rete
 
I corsi di Scienze Matematiche Fisiche e Naturali
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Clemente Galdi » 6.Espressioni Regolari ed Introduzione ad AWK


Il Comando grep

Il comando grep ricerca in un file di testo le righe che corrispondono ad un dato pattern. La sintassi del comando è la seguente:
grep [opzioni] pattern [nomefile]
Il pattern è una espressione regolare, nel caso più semplice, il pattern può essere una stringa senza caratteri speciali. Ad es., grep abc pippo.txt visualizza le righe di pippo.txt che contengono la stringa “abc
Se il parametro nomefile non è specificato, grep legge da standard input, i.e., è possibile utilizzare grep congiuntamente alla redirezione.
L’operazione di default eseguita da grep è la visualizzazione delle righe che corrispondono al pattern.
Alcune Opzioni:

  • -v stampa le righe che non corrispondono al pattern
  • -n l’output è nel formato: <indice>:<riga>
    • dove <indice> corrisponde al numero di <riga> all’interno del file
  • -c visualizza solo il numero di occorrenze della stringa nel file
  • -i rende il comando case-insensitive.

Il Comando grep: Esempi

Ricerca e visualizza le righe del file /etc/passwd contenenti la stringa ‘root’
lso:~>grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

Ricerca e visualizza le righe del file /etc/passwd contenti la stringa ‘root’, indicando il numero di riga
lso:~>grep -n root /etc/passwd
1:root:x:0:0:root:/root:/bin/bash
12:operator:x:11:0:operator:/root:/sbin/nologin

Visualizza il numero di righe del file /etc/passwd contenti la stringa ‘root’
lso:~>grep -c root /etc/passwd
2

Il Comando grep: Esempi (segue)

Visualizza le righe del file /etc/passwd che NON contengono la stringa ‘bash’.
Tra queste, ricerca e visualizza le stringhe che NON contentono la stringa ‘nologin’.
lso:~>grep -v bash /etc/passwd |grep -v nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
news:x:9:13:news:/etc/news:

Il Comando grep: Esempi (segue)

Visualizza il numero di le righe del file /etc/passwd contenti la stringa ‘account’.
lso:~>grep -c account /etc/passwd
0

L’output del comando precedente è 0 visto che nel file /etc/passwd esiste una riga contenente la stringa ‘Account’. Difatti, utilizzando l’opzione -i che rende il comando grep case insensitive, l’output del comando cambia.
lso:~>grep -c -i account /etc/passwd
1

Si noti che è possibile ottenere lo stesso effetto componendo comandi diversi attraverso l’operatore shell “|” (pipe).
lso:~>grep -i account /etc/passwd |wc -l
1

Eliminando l’opzione -c possiamo visualizzare la stringa che corrisponde al pattern.
lso:~>grep -i account /etc/passwd
lso:x:501:501:LSO Account:/home/lso:/bin/bash

Espressioni Regolari

Una espressione regolare (in breve RE o regexp, acronimi di Regular Expression) è un stringa che descrive un insieme di stringhe.
L’elemento atomico delle espressioni regolari è il carattere.
Quasi tutti i caratteri possono essere visti come espressioni regolari elementari.

  • Un carattere è una espressione regolare che descrive se stesso
  • Ad esempio la RE “a” descrive “l’insieme di stringhe {a}”

Esistono caratteri speciali, detti metacaratteri, che vengono utilizzati per descrivere operazioni tra/sui pattern. Ad esempio:

  • il pattern “^a” indica l’insieme di “tutte le stringhe che cominciano per il pattern “a”.
  • Il pattern “a|b” indica l’insieme di “tutte le stringhe che contengono il pattern a oppure il pattern b”.

Partendo da espressioni regolari semplici è possibile ottenere espressioni regolari via via più complesse attraverso la loro composizione.
Le RE trovano ampia applicazione all’interno dei linguaggi di scripting per la ricerca e/o sostituzione di pattern all’interno di stringhe.
Esistono comandi come sed, awk e grep che interpretano le RE a tale scopo.
La shell bash, dalla sua versione 3, ha un interprete built-in per le RE.
Se un testo contiene almeno una delle stringhe descritte dalla RE allora diremo che il testo corrisponde al pattern.

Espressioni Regolari (segue)

Una RE è un insieme di caratteri contenente una o più occorrenze dei seguenti elementi:

  • Una sequenza di caratteri. Se la sequenza non contiene metacaratteri, questo pattern identifica una sola stringa.
  • Un’ancora. Per ancora si intende una posizione all’interno del testo a cui la RE deve corrispondere. Esempi di ancore sono ^ (Inizio riga) e $ (fine riga).
  • Un Modificatore: I modificatori vengono utilizzati per estendere o ridurre l’insieme di stringhe definite dal pattern.

Una RE può essere definita ricorsivamente a partire dalle seguenti regole:

  • Un carattere (o un metacarattere preceduto da “\”) è una RE;
  • La ripetizione di una RE è una RE;
  • La concatenazione di due RE è una RE;
  • La composizione attraverso il carattere “|” (OR) di due RE è una RE.

Espressioni Regolari (segue)

Esistono due tipi di RE, le RE di base e le RE estese. L’unica differenza tra i due tipi di RE sta nel fatto che:

  • nelle RE di estese i simboli “?”, “+”, “{“, “}”, “|”, “(” e “)” vegono interpretati come metacaratteri.
  • nelle RE di base gli stessi simboli sono interpretati come caratteri “normali”.
  • Dal punto di vista dell’espressività, quindi, le due versioni NON sono equivalenti.

Alcune implementazioni consentono, però, di definire RE di base in cui i caratteri sopra elencati possono assumere il significato di metacarattere se preceduti dal carattere di escape “\”.
Ad esempio: la RE estesa “(a|b){2}” equivale alla RE di base “\(a\|b\)\{2\}“.
In questo caso, quindi, le RE di base ed estese sono, “equivalenti” dal punto di vista della espressività.

Espressioni regolari ed interazione con la shell

Attenzione: Alcuni caratteri vengono interpretati in modo diverso dalla shell bash e nella definizione di RE. Ad esempio, il carattere $:

  • Viene interpretato dalla shell come operatore per la valutazione di una variabile, e.g., “echo $A” corrisponde a “Visualizza il valore della variabile A”;
  • Definisce “fine riga” in una espressione regolare, e.g, “A$” corrisponde a “la RE A a fine riga”.

Quando è necessario utilizzare RE all’interno di script shell (o anche direttamente da riga di comando) è sempre opportuno evitare che la shell interpreti i caratteri che definiscono la RE attraverso il quoting utilizzando apici singoli.

  • Come già detto nelle lezioni precedenti, gli apici doppi NON proteggono I caratteri $ ` e \

Il comando grep può interpretare sia RE estese che RE di base (comportamento di default per backward compatibility).
È possibile indicare al comando grep che la RE da interpretare è estesa utilizzando l’opzione -E o, in alternativa utilizzando il comando egrep.

Espressioni regolari ed interazione con la shell (segue)

L’esecuzione del primo comando genera un errore perché la shell interpreta il simbolo “|” come pipe e tenta di eseguire il comando “^r,*37″
lso:~>egrep ^r.*n$|^r.*37 /etc/passwd
-bash: ^r.*37: command not found

Proteggendo la RE tramite il quoting consente alla shell di invocare correttamente il comando egrep con argomento ^r.*n$|^r.*37
lso:~>egrep '^r.*n$|^r.*37' /etc/passwd
rpm:x:37:37::/var/lib/rpm:/bin/bash
rpc:x:32:32:Portmapper RPC user:/:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin

Il primo comando grep della coppia che segue non restituisce alcun risultato perché grep interpreta, per default, RE di base. Quindi il carattere “|” NON viene interpretato come operatore OR.
La seconda esecuzione di grep, invece, descrive correttamente la RE anteponendo a “|” il carattere di escape “\” che indica a grep di interpretare “|” come metacarattere.
lso:~>grep '^r.*n$|^r.*37' /etc/passwd
lso:~>grep '^r.*n$\|^r.*37' /etc/passwd
rpm:x:37:37::/var/lib/rpm:/bin/bash
rpc:x:32:32:Portmapper RPC user:/:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin

Espressioni Regolari: Operatori di ripetizione

Definiamo di seguito il significato dei singoli operatori utilizzabili nella definizione delle RE. Utilizzeremo il carattere “E” per identificare una RE.
Un operatore di ripetizione consente, a partire da una espressione regolare E, di definire una nuova RE ottenuta replicando E più volte.

  • E* (Asterisco): Corrisponde una RE in cui il pattern E compare zero o più volte.
    • Ad es., d* Corrisponde all’insieme di RE {“stringa vuota”, d, dd, ddd…}
  • E+ : Corrisponde una RE in cui il pattern E compare una o piu’ volte.
    • Ad es. d+ Corrisponde all’insieme di RE {d, dd, ddd…}
  • E{N}: La RE E compare esattamente N volte, consecutivamente, nella stringa.
  • E{N,}: La RE E compare almeno N volte, consecutivamente, nella stringa

Espressioni Regolari: Operatori di ripetizione (segue)

  • E{N,M}: La RE E compare almeno N ed al più M volte, consecutivamente, nella stringa.
    • Attenzione: Le espressioni b{2}, b{2,} e b{2,15} corrispondono tutte alle stringhe “bb, bbb, bbbb…”. Difatti, la stringa bbb contiene una sottostringa
      • Composta “esattamente” 2 occorrenze consecutive della RE “b”
      • Contenente “almeno” 2 occorrenze consecutive della RE “b”
      • Contenente “almeno” 2 ed al piu’ 15 occorrenze consecutive della RE “b”
  • La differenza tra questi operatori diventa chiara quando si considerano RE in cui l’operatore di ripetizione è incluso tra due espressioni regolari. Ad es.
    • Ab{2}A: Corrisponde alla (sola) stringa AbbA
    • Ab{2,}A: Corrisponde alle stringhe AbbA, AbbbA, AbbbbA, AbbbbbA,…
    • Ab{2,3}A: Corrisponde alle (sole) due stringhe AbbA e AbbbA

Espressioni Regolari: Estensioni, Riduzioni ed Ancore

  • E. (Punto): Corrisponde ad una RE in cui il pattern E è seguito da un qualsiasi altro carattere.
    • Ad es.: Abcd. Corrisponde a {Abcda,Abcdb, … Abcd1, “Abcd ” (spazio), Abcd?…}
  • ^E: definisce un pattern che corrisponde a tutte le righe che iniziano per E.
  • E$ : definisce un pattern che corrisponde a tutte le righe che terminano per E.
  • \<E: definisce un pattern che corrisponde a tutte le righe che contengono una parola che inizia per E.
  • E\>: definisce un pattern che corrisponde a tutte le righe che contengono una parola che termina per E.

Espressioni Regolari: Insiemi di caratteri

È possibile definire insiemi di caratteri da utilizzare per la costruzione di RE. Il modo più semplice è quello di elencare I caratteri dell’insieme, senza separatori, all’interno di parentesi quadre. Ad esempio:

  • [ade] Corrisponde all’insieme {“a”, “d”, “e”}
  • [a d,e] Corrisponde all’insieme {“a”, “d”, “e”, ” “, “,”}

L’utilizzo del carattere “-” consente di definire intervalli di caratteri, senza dover elencare tutti gli elementi dell’intervallo. Ad esempio:

  • [A-D] corrisponde all’insieme {“A”, “B”, “C”, “D”}
  • La scrittura [a-d0-4A-D] definisce l’insieme ottenuto dall’unione (algebrica) degli insiemi [a-d], [0-4] e [A-D]

Esistono classi di caratteri predefinite cui è possibile far riferimento utilizzando la scrittura [[:CLASS:]] dove CLASS è una delle seguenti stringhe: “alnum”, “alpha”, “ascii”, “blank”, “cntrl”, “digit”, “graph”, “lower”, “print”, “punct”, “space”, “upper”, “word” or “xdigit”. Ad esempio:

  • [[:lower:]] corrisponde all’insieme [a-z]
  • [[:alphanum:]] corrisponde all’insieme [A-Za-z0-9].

Nel caso degli insiemi di caratteri, il simbolo “^” assume il valore di negazione. Ad es. [^a-c] corrisponde a “tutti I caratteri tranne quelli appartenenti all’intervallo [a-c]“.

Espressioni Regolari: Composizione

Come già accennato, è possibile comporre espressioni regolari mediante:

  • Ripetizione: Attraverso l’utilizzo degli operatori di ripetizione appena visti;
  • Concatenazione. La concatenazione di due RE è ottenuta giustapponendo la due RE da concatenare. Ad esempio: La concatenazione di “a{2}” e “b*” è, semplicemente, “a{2}b*”
  • Attraverso l’operatore “|”. Se A e B sono due RE, E=A|B corrisponde all’unione dell’insieme delle stringhe definite da A e dell’insieme delle stringhe definite da B. Allo stesso modo, si può dire che una stringa corrisponde ad E se essa corrisponde a A oppure se essa corrisponde ad B.

Se in una RE compaiono più operatori di composizione, vale il seguente ordine di precedenza:

  1. Ripetizione
  2. Concatenazione
  3. Operatore “|”

È possibile ridefinire l’ordine di esecuzione degli operatori di composizione utilizzando le parentesi tonde.

Espressioni Regolari: Esempi

lso:~>egrep '5|1:+' /etc/passwd
In questo esempio sono presenti l’operatore di ripetizione “+”, l’operazione di concatenazione delle RE “1″ e “:” e l’operatore “|”.
In base alle regole di precedenza, gli operatori vengono eseguiti in questo ordine:

  1. Ripetizione: Viene considerato l’operatore + applicato alla RE “:”, generando una RE che contiene una o più occorrenze del carattere “+”
  2. Concatenazione: Tra le RE “1″ e “:+”
  3. L’OR tra le espressioni regolari “5″ ed “1:+”

La RE corrisponde quindi a tutte le stringhe che (a) contengono un “1″ seguito da almeno una occorrenza di “:”, I.e., 1:, 1::, 1::: … OPPURE (b) contengono “5″.
È possibile ridefinire l’ordine di esecuzione degli operatori utilizzando le parentesi ()

Espressioni Regolari: Esempi (segue)

lso:~>egrep '(5|1):+' /etc/passwd
In questo caso, le parentesi indicano che l’OR deve essere eseguito prima degli altri operatori, mentre l’ordine degli altri operatori rimane inalterato.

  1. L’OR tra le RE “5″ ed “1″
  2. Ripetizione: Viene considerato l’operatore + applicato alla RE “:”, generando una RE che contiene una o più occorrenze del carattere “+”
  3. Concatenazione: Tra le RE “5″ e “:+” e la le RE “1″ e “:+”

La RE corrisponde quindi a tutte le stringhe che (a) contengono un “1″ seguito da almeno una occorrenza di “:”, I.e., 1:, 1::, 1::: … OPPURE (b) contengono “5″ seguito da almeno una occorrenza di “:”, I.e., 5:, 5::, 5::: …

Espressioni Regolari: Esempi (segue)

lso:~>egrep '(501:){2}' file.txt
Ritorna tutte le righe del file file.txt che contengono due occorrenze consecutive della stringa 501:
lso:~>egrep '501:{2}' file.txt
Ritorna tutte le righe del file file.txt che contengono la (sola) stringa 501:: (due occorrenze di “:”)
lso:~>egrep '50(1:){2,}' file.txt
Ritorna tutte le righe del file file.txt che contengono la stringa 50 seguita da almeno due occorrenze consecutive della stringa 1:, I.e., 501:, 501:1:, 501:1:1:….
lso:~>egrep '\<[[:lower:]]' file.txt
Ritorna tutte le righe del file file.txt che contengono una parola che inizia per una lettera minuscola. Per (e)grep una “parola” è una stringa composta dai caratteri appartenenti alla classe alnum e/o dal carattere "_".
lso:~>egrep 'casa' file.txt
lso:~>egrep '\<casa\>' file.txt
La differenza tra questi due comandi sta nel fatto che il primo ritorna le righe che hanno la stringa casa coma sottostringa. Il secondo comando, invece, ritorna del righe del file che contengono a parola casa. Ad esempio il primo comando ritorna una stringa contenete la parola casale che verrebbe scartata dal secondo.

Espressioni Regolari: Esempi (segue)

lso:~>egrep '^A(50(1:)+|2k*a){2}B$' /etc/passwd
In questo caso si noti che la presenza di parentesi modifica le priorità di esecuzione dei seguenti operatori:

  • La concatenazione “1:” viene eseguita prima della ripetizione “+”;
  • L’OR viene eseguito prima della ripetizione {2}

Ritorna tutte le righe del file /etc/passwd in cui:

  • La riga inizia per il carattere A
  • La riga termina per il carattere B
  • La riga ha la forma AXXB, dove A e B sono caratteri, X (replicata esattamente due volte) può essere una delle seguenti:
    • Una stringa che inizia per 50 seguita da almeno una occorrenza della coppia 1:, e.g., 501:, 501:1:, 501:1:1:…
    • Una stringa che inizia per 2, seguita da zero o più occorrenze del carattere k e termina per a, E.g., 2a, 2ka, 2kka, 2kkka….

Espressioni Regolari: Esempi (segue)

Riassumendo, riportiamo di seguito alcuni esempi di righe che corrispondono alla RE descritta:
A501:1:501:1:B
A501:1:1:1:1:501:1:1:1:1:B
A2kka2kkaB
A2kkkkkkkkkka2kkkkkkkkkkaB
Si noti che, in genarale, la RE definisce una sottostringa di una riga. Nelle RE del tipo ^XYZ$ viene richiesto, invece, che l’intera riga corrisponda alla RE.

AWK: Introduzione

awk è una utility per l’interpretazione di uno specifico linguaggio di programmazione.
Il nome deriva dalle iniziali dei suoi progettisti, Alfred V. Aho, Peter J. Weinberger and Brian W. Kernighan, che lo progettarono nel 1977 nei laboratori Bell Labs dell AT&T.
Caratteristica principale di awk è la semplicità con cui è possibile analizzare e processare file di testo. Nello specifico, awk ricerca all’interno di file di testo le righe che corrispondono ad un dato pattern e, su queste righe, esegue una sequenza di istruzioni.
Vedremo che la semplicità di utilizzo deriva anche dal fatto che awk suddivide automaticamente la linea da analizzare in parole, consentendo l’accesso alla singola parola indipendentemente dalle altre.
Alcuni interpreti awk potrebbero mostrare comportamenti difformi dagli esempi riportati. L’interprete che utilizzeremo in questo corso è gawk, l’interprete di default utilizzato dai sistemi Linux.
Il linguaggio interpretato da awk è data-driven, i.e., uno o piu’ pattern descrivono i i dati su cui uno specifico codice verrà eseguito. Sintatticamente un programma awk ha la seguente forma:

BEGIN{ codice di “inizializzazione”; eseguito una sola volta prima dell’inizio dell’analisi dell’input}
pattern1{codice associato al pattern1}
pattern2{codice associato al pattern2}

END{ codice di “conclusione”; eseguito una sola volta dopo la terminazione dell’analisi dell’input}

È possibile scrivere script contenenti solo il blocco BEGIN. In questo caso l’interprete non attende input ed esegue una sola volta il codice all’interno del blocco.

AWK: Introduzione (segue)

Per eseguire un programma awk è possibile utilizzare una delle seguenti possibilità:
L’interprete awk consente di scrivere il programma direttamente su riga su riga di comando utilizzando la seguente sintassi:

  • awk 'programma' inputfile1 inputfile2...
  • Si noti che, in questo caso, la stringa ‘programma’ NON definisce “il nome del file contenente il programma” ma il programma vero e proprio protetto da quoting con apice singolo. Ad es.: awk '{print $1}' file1.txt
  • Come nel caso delle RE, è necessario proteggere il programma attraverso il quoting con apice singolo.

Nella maggior parte dei casi, è opportuno scrivere il programma in un file di testo ed indicare all’interprete il nome del file.
awk -f nomefile inputfile1 inputfile2...

AWK: Introduzione (segue)

Come per gli script shell, è possibile rendere uno script awk “eseguibile” utilizzando i seguenti accorgimenti:

  • La prima riga dello script awk deve contenere l’indicazione dell’interprete, e.g. #!/usr/bin/awk -f
    • Si noti che il nome dell’interprete, /usr/bin/awk, è seguito dall’opzione “-f” che indica all’interprete che il programma è contenuto in un file.
  • L’utente ha settato i permessi di esecuzione per lo script.

Se i nomi dei file di input sono omessi, awk legge l’input da standard input. È possibile, quindi, utilizzare la redirezione, ad esempio, le seguenti invocazioni sono equivalenti
awk 'programma' file.txt
awk 'programma' < file.txt
cat file.txt | awk 'programma'

AWK: I pattern

L’esecuzione di uno script awk può essere schematizzata come segue. L’interprete:

  • Legge una riga dal file di input. Quando sono presenti più file di input, l’interprete legge i file nell’ordine in cui sono stati indicati su riga di comando, sempre una riga per volta. La riga letta è anche detta record.
  • Verifica se il record corrisponde ad uno dei pattern indicati nel programma, nel qual caso, esegue il codice associato.

L’esecuzione termina quando non esistono più record da analizzare.

Un pattern è una espressione regolare racchiusa tra due simboli ‘/’.

I pattern vengono, in genere, utilizzati per filtrare i record da processare anteponendo la descrizione della RE al blocco di codice.
In questo caso, l’intero record viene esaminato per verificare se corrisponde o meno al pattern.
Il seguente esempio di script awk: Mostra codice descrive uno script awk che processa I record attraverso due blocchi di codice.
Si noti che:

  • Il blocco di codice opera solo sui record che corrispondono al pattern
  • Lo stesso record può corrispondere a più pattern. Nell'esempio, un record composto da sole "a" corrisponde ad entrambi i pattern, e quindi, entrambi i blocchi di codice verranno eseguiti su quel record specifico.

AWK: I pattern (segue)

È possibile verificare pattern anche all’interno del codice. A tal fine, awk mette a disposizione l’operatore booleano ~ (tilde) la cui sintassi è la seguente:
exp ~ /regexp/

L’operatore ritorna “vero” se l’espressione exp corrisponde al pattern regexp.

Esempi di utilizzo dell’operatore ~: Mostra codice
L'esempio riporta tre esempi equivalenti di programmi awk.
Il primo esempio riporta l'esecuzione dell'interprete da riga di comando. Il programma controlla se il record corrisponde al pattern. In assenza del codice associato ad un pattern, l'interprete visualizza semplicemente il record.
script1.awk contiene semplicemente l'indicazione del pattern.
In script2.awk, le istruzioni nell'unico blocco di codice vengono eseguite su tutti I record ma il codice contiene un test per verificare se il record corrisponde al pattern. In questo caso, quindi, il filtro viene eseguito dal codice del programma.

Awk possiede anche un operatore di negazione "!" che può essere utilizzato in congiunzione con l'operatore tilde.

Gli operatori ~ e ! Possono essere utilizzati per la definizione di pattern o nei costrutti if, for, while do che vedremo.

Esempi di utilizzo dell'operatore !: Mostra codice

AWK: Variabili built-in

Come per la shell, l’interprete awk mantiene una serie di variabili built-in a cui il programma può accedere. Di seguito ne riportiamo alcune.
FILENAME: Abbiamo già accennato che l’interprete awk legge l’input dai file indicati all’atto della sua invocazione o da standard input. Più precisamente, l’interprete legge una riga per volta dal file. Se esistono più file di input, l’interprete analizza i file nell’ordine in cui sono presentati sulla riga di comando. Il nome del file che l’interprete sta correntemente utilizzando e’ memorizzato nella variabile built-in FILENAME.
FNR, NR: L’interprete awk mantiene il numero di record processati per il file corrente nella variabile FNR. Inoltre memorizza anche il numero totale di record processati dal processo corrente nella variabile NR. Chiaramente le due variabili possono assumere valori diversi se il numero di file in input allo script è almeno due.
RS: Il carattere utilizzato come separatore tra due record è memorizzato nella variabile RS (Record Separator). Il valore di default per RS è newline (quindi un record corrisponde ad una linea nel file (di testo) in input. L’utente può assegnare alla variabile RS una qualsiasi espressione regolare.

AWK: Variabili built-in (segue)

NF: Ogni record letto viene automaticamente suddiviso in campi (fields). Questa variabile built-in contiene il numero di campi nel record corrente.
FS: Il separatore tra i campi è contenuto nella variabile FS (Field Separator), il cui valore di default è lo spazio. Quindi l’interprete suddivide la riga in un numero di campi pari al numero di “parole” presenti sulla riga. È possibile assegnare ad FS una qualsiasi RE.
Per accedere ai valori dei singoli campi è necessario utilizzare il metacarattere $ seguito dall’indice del campo. Ad esempio $3 corrisponde al valore del terzo campo del record corrente. In awk è possibile far riferimento a campi la cui posizione non è costante, ad esempio $(NF-1) indica il penultimo campi del record corrente. Si noti che NF NON è una costante ed NF-1 è, in realtà, la valutazione di una espressione aritmetica.
In awk è possibile modificare il valore dei campi all’interno di un blocco di codice.

AWK Input: Esempi

Il seguente script: Mostra codice riporta un semplice esempio di utilizzo delle variabili built-in.
Si noti che, a differenza degli script shell, l'accesso al valore delle variabile NON richiede l'utilizzo del metacarattere $, se non nel caso dell'accesso ai campi del record.

La prima riga visualizza le variabile NR e FNR che memorizzano il numero di record processati, rispettivamente, dall'inizio dello script e per il file corrente.

La seconda riga visualizza il nome del file corrente.

La terza riga visualizza il numero di campi nel record corrente. Vengono quindi visualizzati l'intero record, il primo e l'ultimo campo.
Si noti che l'accesso all'ultimo campo è effettuato attraverso un indirizzamento indiretto attraverso il valore della variabile NF.

AWK Input: Esempi (segue)

Come detto le variabili built-in possono essere modificate. Difatti lo script appena visto modifica il valore della variabile $1 (il primo campo del record) per poi visualizzarne il nuovo valore. Chiaramente, in questo esempio, si assume che il primo campo sia numerico.
Attenzione: La modifica di una variabile built-in permane fino all’eventuale aggiornamento della stessa da parte dell’interprete.
Si supponga di eseguire lo script nella figura in alto con input i due file nella figura in basso.
Nel seguente esempio: Mostra codice le variabili FNR e FILENAME vengono modificate dall'utente quando NR<2. Quindi:

  • Durante l'analisi del primo record, NR=1, le variabili FNR e FILENAME vengono modificate dall'utente rispettivamente a 0 (zero) e "Miofile".
  • Al secondo record, l'interprete incrementa la variabile FNR, portandola al valore 1 e non modifica FILENAME, che rimane "Miofile"
  • Al terzo record l'interprete reinizializza le variabili FNR ad 1 e FILENAME al valore "file2.txt".

Dal terzo record in poi, quindi, le modifiche fatte dall'utente vengono perse.
Si noti che le modifiche fatte alle variabili contenenti i campi vengono perse quando l'interprete legge il record successivo.

I file di input: Mostra codice

AWK Input: Esempi (segue)

Il seguente esempio: Mostra codice mostra le possibilità offerte dalla modifica dei separatori di record e di campi.
Lo script modifica il separatore di record di default assegnando alla variabile RS l'espressione regolare ;|\n. Questa assegnazione indica all'interprete che la fine di un record è identificata dal carattere newline (\n) o dal carattere ";".
Allo stesso tempo, al separatore dei campi FS viene assegnata l'espressione regolare /|+ che indica all'interprete che la fine di un campo è identificata dal carattere "/" o dal carattere "+".
Queste modifiche consentono all'interprete di analizzare il file "File.txt" riportato nella figura in basso, suddividendolo nel seguenti 5 record:

  • Primo record composto dai campi 1, 2 e 3
  • Secondo record composto dai campi 4, 5, 6 e 7
  • Terzo record composto dai campi 8 e 9
  • Quarto record composto dai campi 10, 11 e 12
  • Quinto record composto dai campi 13, 14 e 15.

L'output atteso dall'esecuzione dello script è riportato di seguito:Mostra codice

AWK: Output

Abbiamo già utilizzato lo statement print per la visualizzazione di messaggi. Questo statement può essere utilizzato per visualizzare qualsiasi tipo di valore ma NON consente di controllare il modo in cui le informazioni vengono visualizzate. Awk fornisce anche uno statement printf che consente di descrivere la formattazione dell’output.
Lo statement print può essere usato nei seguenti modi:

  • print item1, item2, item3…: Visualizza il valore degli item, divisi (per default) da spazi. Un item può essere una variabile, una costante od una espressione aritmetica. Omettendo a virgola, gli item vengono concatenati e visualizzati senza separatori.
  • print: Senza parametri lo statement visualizza il valore della variabile $0
  • print “”:Visualizza una riga vuota
  • print “”: Visualizza la stringa costante contenuta tra doppi apici.
  • print $i: Visualizza il valore della campo i-esimo

Il separatore di default tra due item visualizzati da print è lo spazio. In realtà è possibile indicare all’interprete la stringa da utilizzare assegnando un valore alla variabile OFS (Output Field Separator).
Allo stesso modo, l’output di ogni comando print viene terminato dal valore della variabile ORS (Output Record Separator), il cui valore di default è il carattere newline. È possibile indicare a print il formato in cui visualizzare i valori numerici assegnando opportunamente la variabile OFMT, ad es., OFMT=”%3d”.
Come è semplice intuire, la sintassi della variabile OFMT è la stessa del parametro “format” utilizzato nel linguaggio C per la funzione print.

AWK: Output (segue)

Awk fornisce una seconda funzione di visualizzazione, printf, che consente di definire esplicitamente la formattazione dell’output.
La sintassi di printf è la seguente:
printf formato, item1, item2, item3...
Anche in questo caso il formato ha la stessa sintassi del parametro format della funzione printf del linguaggio C.

Come negli script shell, l’interprete awk consente la redirezione dell’output di print e printf.
La sintassi è esattamente la stessa della shell bash. In particolare:
print item1,... > nomefile
L’interprete scrive l’output di print nel file nomefile. Se il file esiste, il suo contenuto viene cancellato, altrimenti il file viene creato. Nomefile può essere una qualsiasi espressione. Prima della redirezione l’interprete converte l’espressione in una stringa che utilizza come nome del file su cui scrivere.
print item1,... >> nomefile
L’interprete accoda l’output di print al contenuto del file nomefile.
print item1,... | comando
È possibile inviare l’output di print ad un comando tramite una pipe.

AWK output esempio

Il seguente script: Mostra codice, è un esempio di utilizzo degli statement print e printf. L'esempio: Mostra codice, riporta l'output solo per il primo record.
Lo script modifica le variabili ORS ed OFS come segue:

  • ORS assume il valore " +ORS+ \n". Questa variabile viene accodata all'output argomento di print. Difatti, visto che lo script, per ogni record, include due invocazioni di print, nell'output riportato nella figura in basso è possibile identificare la stringa descritta esattamente due volte.
  • OFS assume il valore " -OFS- ". Il valore di questa variabile viene utilizzato come separatore tra gli item. Ogni "," che separa due item viene sostituita da OFS.

È possibile concatenare stringhe e valori, per giustapposizione. Nell'esempio, la concatenazione della stringa costate "Il record" e del valore della variabile NR è ottenuta utilizzando la sintassi "Il record " NR.
Si noti che lo spazio tra "il record" ed NR può essere omesso, I.e, le scritture '"Il record " NR' e '"Il record "NR' sono equivalenti.

L'invocazione della printf assume la definizione del formato da utilizzare per i parametri tramite la stessa sintassi del linguaggio C.

Si noti, infine, che le variabili ORS ed OFS hanno effetto SOLO sullo statement print e NON su printf.

Questo, in realtà, non è una limitazione visto che è possibile ridefinire, in ogni invocazione di printf, quali siano i separatori tra gli item e il "fine riga".

AWK output esempio (segue)

Il seguente script: Mostra codice, è una variante dello script appena visto che utilizza la redirezione dell'output.
Lo script crea un file per ogni record nel file. Il nome del file è calcolato attraverso l'espressione ("outfile_"NR), i.e., l'ouput del record i-esimo verrà scritto nel file outfile_i
Le successive invocazioni di print e printf redirigono l'output sul file attraverso gli operatori > e >>. In particolare:

  • la prima invocazione di print crea il file se non esiste o cancella il suo contenuto;
  • le successive invocazioni di print e printf accodano l'output al contenuto del file.

Un esempio di input con relativo output generato dallo script: Mostra codice

I materiali di supporto della lezione

Bash guide for beginners: Capitolo 4

Advanced Bash Scripting: Capitolo 17

GAWK: Effective Awk Programming: Capitoli 1, 2 (par. 1,6), 3 (par. 1-5) 4 (par. 1-6)

  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion