GUIDA AD AWK, 00 – fondamenti

Fondamenti

 

In questo articolo faremo una carrellata di contenuti ed esempi che riguardano il linguaggio awk. Ho volutamente inserito parecchio materiale in modo da creare una base di consultazione abbastanza completa. Gli articoli successivi saranno più specifici, meno ampi e quindi molto più scorrevoli. Al bisogno sarà sempre possibile tornare a questa pagina per consultazione.

fonti:
http://tldp.org/LDP/abs/html/awk.html
http://areeweb.polito.it/didattica/operating_systems/SO/Esaula/man_awk.pdf
http://www.theunixschool.com/p/awk-sed.html
http://www.codingunit.com/printf-format-specifiers-format-conversions-and-formatted-output
http://ocs.unipa.it/AL/al239.htm

Awk è sostanzialmente un processore di testo, ideale per l’elaborazione di file di testo strutturato.
Ciascun record rappresenta una linea del file e ciascun campo una “parola” (i campi sono divisi tra loro dal carattere contenuto nella variabile FS che di default è lo spazio ed è configurabile). La struttura quindi è quella di una tabella divisa in righe e colonne.
Per indicare i campi (parole) della riga corrente si usano le variabili $0,$1,$2,….; la variabile $0 contiene l’intera riga (record) mentre $1 contiene il primo campo, $2 il secondo e così via.
Awk processa i record in successione, dal primo all’ultimo.

Esempi elementari di awk ‘{print}':

$ echo uno due | awk '{print $1}'
uno
$ echo uno due | awk '{print $2}'
due
# la variabile $0 corrisponde a tutti i campi
$ echo uno due | awk '{print $0}'
uno due

Il testo può essere passato ad awk o tramite pipe (|) come negli esempi precedenti oppure tramite file, nella forma:

$ cat pluto
casa mattoni grande
albero legno medio
strada asfalto lunga
borsa pelle piccola

$ awk '{print $2}' pluto
mattoni
legno
asfalto
pelle

$ awk '{print $0}' pluto
casa mattoni grande
albero legno medio
strada asfalto lunga
borsa pelle piccola

Di default awk stampa $0:

$ awk '{print}' pluto
casa mattoni grande
albero legno medio
strada asfalto lunga
borsa pelle piccola

I programmi awk sono così strutturati:

pattern { azione }
pattern { azione}

Quando il pattern E’ SODDISFATTO viene eseguita l’azione.

Lo “strong quoting” (‘ string ‘) e le parentesi graffe ({ string }) racchiudono blocchi di codice awk all’interno di uno script shell.

In generale la sintassi di awk è la seguente:

awk ‘pattern{action}’ file

I pattern possono essere delle semplici espressioni regolari racchiuse tra “/” ( es: /pippo/ ) oppure un’espressione logica o ancora le espressioni BEGIN ed END (vengono ritenute vere rispettivamente prima di incominciare a leggere il file in input e dopo averlo esaminato tutto).

Espressioni regolari:
\  ⇒ 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.

Le espressioni:
L’espressione è qualcosa che restituisce un valore. I tipi di valori gestiti da AWK sono 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
– Tutto il resto vale come VERO (anche la stringa “0” vale come Vero)

Le stringhe sono delimitate da doppi apici e possono contenere delle sequenze di escape, come ad esempio:

\\    ⇒   \
\”    ⇒   ”
\/    ⇒   /

Operazioni e Operatori:
(<exp>) -> Valuta l’espressione contenuta tra parentesi prima di analizzare la parte esterna.
++<op> -> Incrementa di un’unità l’operando prima che venga restituito il suo valore.
<op>++ -> Incrementa di un’unità l’operando dopo averne restituito il suo valore.
–<op> -> Decrementa di un’unità l’operando prima che venga restituito il suo valore.
<op>– -> Decrementa di un’unità l’operando dopo averne restituito il suo valore.
+<op> -> Non ha alcun effetto dal punto di vista numerico.
-<op> -> Inverte il segno dell’operando numerico.
<op1> + <op2> -> Somma i due operandi numerici.
<op1> – <op2> -> Sottrae dal primo il secondo operando numerico.
<op1> * <op2> -> Moltiplica i due operandi numerici.
<op1> / <op2> -> Divide il primo operando per il secondo.
<op1> % <op2> -> Modulo: il resto della divisione tra il primo e il secondo operando.
<op1> ^ <op2> -> Esponente: eleva il primo operando alla potenza del secondo.
<var> = <valore> -> Assegna alla variabile il valore alla destra, e restituisce lo stesso valore.
<op1> += <op2> -> <op1> = <op1> + <op2>
<op1> -= <op2> -> <op1> = <op1> – <op2>
<op1> *= <op2> -> <op1> = <op1> * <op2>
<op1> /= <op2> -> <op1> = <op1> / <op2>
<op1> %= <op2> -> <op1> = <op1> % <op2>
<op1> ^= <op2> -> <op1> = <op1> ^ <op2>
<op1> && <op2> -> AND logico, con cortocircuito.
<op1> || <op2> -> OR logico, con cortocircuito.
! <op> -> NOT logico.
<op1> > <op2> -> Vero se il primo operando è maggiore del secondo.
<op1> >= <op2> -> Vero se il primo operando è maggiore o uguale al secondo.
<op1> < <op2> -> Vero se il primo operando è minore del secondo.
<op1> <= <op2> -> Vero se il primo operando è minore o uguale al secondo.
<op1> == <op2> -> Vero se i due operandi sono uguali.
<op1> != <op2> -> Vero se i due operandi sono diversi.
<stringa> ~ <regexp> -> Vero se l’espressione regolare ha una corrispondenza con la stringa.
<stringa> !~ <regexp> -> Vero se l’espressione regolare non ha alcuna corrispondenza.
<stringa1> <stringa2> -> Concatena le due stringhe.

———————————–

ALCUNE VARIABILI PREDEFINITE (BUILT-IN) DI AWK:
FS: fileds separator
OFS: output field separator (per print)
NR: number of rows
NF: number of fields
RS: record separator
ORS: output record separator (per print)
FILENAME: filename, of course. Questa variabile è indefinita all’interno del blocco BEGIN e contiene “-” se non sono specificati file nella linea di comando.
<vedi articolo dedicato -> awk_1.2>
————————————

Esempi per evidenziare la sintassi pattern-azione:

Esempio 1

$ sudo awk ‘BEGIN{FS=”:”} ($2==”*”) {print $1 ” ” $3}’ /etc/shadow

Stampa username e UID (separati da uno spazio ” “) di tutti gli utenti del sistema che sono senza password
BEGIN{FS=”:”} -> AZIONE: stabilisce “:” come separatore
($2 == “*”) -> PATTERN: SE il secondo campo è uguale a “*”
{print $1 ” ” $3} -> AZIONE stampa il campo 1, uno spazio, il campo 3 (username e UID)

Esempio 2:

$ cat tabella
1 2 3 4 5
3 2 1 5
5 3 5 1
9 4 3 2
0 0 0
10 12 16 1
1 0 0 00 0 0 0 0 0 1

$ awk ‘{if ($1>max){max = $1}} END {print max}’ tabella

output: 10

Cerca il valore massimo della prima colonna del file tabella e stampalo

if ($1>max){max = $1} -> PATTERN: se il valore del primo campo è superiore alla variabile “max” AZIONE: imposta la variabile uguale al primo campo
END {print max} -> AZIONE: alla fine della lettura del file stampa il valore di “max”.
Di fatto quindi la variabile “max” sarà sovrascritta quando, durante la lettura di tutte le righe, sarà trovato un valore superiore al preesistente.

Esempio 3:

$ cat successione
prima riga uno 1 I
seconda riga due 2 II
terza riga tre 3 III
quarta riga quattro 4 IV
quinta riga cinque 5 V
sesta riga sei 6 VI
settima riga sette 7 VII
ottava riga otto 8 VIII
nona riga nove 9 IX
decima riga dieci 10 X

$ awk ‘(NR % 2) {print}’ successione

Estrai le righe dispari del file successione
dettaglio:
(NR % 2) {print} -> % è il simbolo del “resto”. PATTERN: quando il risultato dell’espressione è DIVERSO da zero (ovvero: quando il resto dell’espressione è diverso da zero) viene verificato e AZIONE: viene eseguita l’azione “print”

Esempio 4:

$ awk ‘{for(i = 1;i <= NF;i+=2){printf(“%s “, $i)} printf(“\n”)}’ successione

CicloFor

Stampa solo i campi dispari di ciascuna riga mediante la scansione delle variabili $1,…,$NF.
NB: nel ciclo for(exp1,exp2,exp3) exp1 è eseguita all’inizializzazione del ciclo, exp2 fa sì che si esca dal ciclo (è una condizione tipo while) se è falsa, exp3 viene eseguito all’inizio di ogni ciclo.

Le istruzioni (contenute in AZIONE) fondamentali di awk sono “print” e “printf”.
Mediante il primo di ottiene l’emissione del record corrente o, se vengono indicati argomenti, questi vengono emessi in sequenza separati da OFS (che di default è lo spazio). L’output termina con l’inserimento del carattere ORS (output record separator), che di solito corrisponde all’interruzione di riga.
Mediante printf (funzione del linguaggio C), invece, è possibile selezionare e formattare l’output in modo desiderato settando in modo adeguato il primo argomento con una serie di simboli che iniziano con “%”:

Specificatori di formato:
%i or %d -> int (intero)
%c -> char (un carattere)
%f -> float (numero con virgola )
%s -> string (stringa)

Caratteri di escape:
\n -> newline
\t -> tab
\b -> backspace
\r -> ritorno carrello

Con numeri:
%d -> stampa come decimale intero
%6d -> stampa come decimale intero con una lunghezza di almeno 6 caratteri
%f -> stampa con virgola flottante
%4f -> stampa con virgola flottante con una lunghezza di almeno 4 caratteri
%.4f -> stampa con virgola flottante con una precisione di 4 caratteri dopo la virgola (decimali)
%3.2f -> stampa con virgola flottante di almeno 3 caratteri e 2 caratteri dopo la virgola (decimali)

Con stringhe:
%s:\n -> stampa e vai a capo
%8s:\n -> stampa la stringa con lunghezza minima di 8 caratteri. Se la stringa è più piccola i restanti caratteri saranno riempiti con whitespace
%.8s:\n -> stampa solamente 8 caratteri della stringa
%-8s:\n -> stampa ALMENO 8 caratteri della stringa

Ad esempio:
{printf “%s “, $0} -> stampa $0 (intero record) come valore stringa seguito da uno spazio.
————————————————-

Una istruzione AWK appartiene ai seguenti 6 tipi:

1. Assegnazione: var = exp dove exp calcola il valore di un’espressione e lo assegna alla variabile var ( es: doppio = pluto * 2 )

2. Statement if : if(exp)statement1 [else statement2] dove se exp è
diverso da zero viene eseguito statement1, altrimenti statement2

3. Ciclo while: while(exp)statement dove statement viene eseguito finchè exp
continua ad avere un valore diverso da zero

4. Ciclo for: for(exp1;exp2;exp3) statement dove exp1 è eseguita al momento
dell’inizializzazione del ciclo, exp3 viene eseguita all’inizio di ogni ciclo e exp2 fa si che si esca dal ciclo quando diventa falsa

5. Ciclo for in: for(var in arrayname)statement simile al ciclo for della shell,
fa sì che alla variabile var vengano assegnati ad uno ad uno i valori contenuti nel vettore (unidimensionale) arrayname

6. Stampa: print exp,[exp,…,exp] in cui ogni espressione exp viene calcolata e stampata nello standard output. I valori delle varie exp saranno distanziati dal carattere contenuto nella variabile OFS che di default è lo spazio. Se print viene usata senza exp viene eseguita la print $0

esistono poi le istruzioni:
break: (esce dal ciclo while o for attivo)
continue: (fa partire l’iterazione seguente del ciclo while o for ignorando le istruzioni rimanenti del ciclo)
next: (salta le istruzioni rimanenti del programma AWK; viene principalmente utilizzato per terminare l’elaborazione di una riga e passare alla successiva)
exit: fa terminare immediatamente AWK

——————————————
Inciso sull’operatore ternario di C:
exp1 ? exp2 : exp3
Corrisponde al blocco if-else:
if (exp1)
{
exp2;
} else {
exp3;
}

Significa: se exp1 è vera esegui exp2, altrimenti esegui exp3
——————————————–

Esempi esplicativi (non sintatticamente completi):

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”.
Proviamo ora per esercizio a simulare il comportamento di alcuni semplici comandi Unix:

– cat
awk ‘{print}’
stampa l’intero file ( si ricorda che “print” e uguale a “print $0″ )

– cat –n
awk ‘{print NR,$0}’
Es. awk ‘{print NR,$0}’ vmnet313.patch
stampa l’intero file “vmnet313.patch” includendo i numeri di riga

– wc –l
awk ‘END {print NR}’
Es awk ‘END {print NR}’ vmnet313.patch
stampa il numero di righe del file (in realtà stampa il numero dell’ultima riga)
——————————————-

Altri esempi:

#!/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 particolare: 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.

————————————

INDICE

Lascia una risposta

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *

È possibile utilizzare questi tag ed attributi XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>