GUIDA AD AWK – indice

GUIDA AD AWK, 00: Fondamenti

GUIDA AD AWK, 01: Programmi awk su file eseguibile

GUIDA AD AWK, 02: Le variabili predefinite

GUIDA AD AWK, 03: Lettura file e split del contenuto

GUIDA AD AWK, 04: Settare variabili in awk e scambiarle con la shell

GUIDA AD AWK, 05: Join di righe

GUIDA AD AWK, 06: Cercare un pattern in un file, nell’intera riga o in una colonna

GUIDA AD AWK, 07: Raggruppare dati in un file csv o txt

GUIDA AD AWK, 08: Elaborare dati in file con delimitatori multipli

GUIDA AD AWK, 09: Inserire, rimuovere, aggiornare campi in un file csv

GUIDA AD AWK, 10: Gli array

GUIDA AD AWK, 11: Le funzioni predefinite “LENGTH”, “SPLIT” E “GETLINE”

GUIDA AD AWK, 12: Scripts “interessanti”

GUIDA AD AWK, 13: To do: spunti da sviluppare

GUIDA AD AWK, 13 – to do

To do: spunti da sviluppare

 

http://www.thedevopsblog.com/sysadmin/how-tos/geekish-one-liner-linux-command-sequences/
ordinamento crescente/decrescente
echo -e “1\n \b2\n \b3\n \b4\n \b5\n \b6\n \b7\n \b8\n \b9″ | awk ‘{a[$1]}END{for (i in a) print i}’
Creazione di un array:

 echo -e "1\n \b2\n \b3\n \b4\n \b5\n \b6\n \b7\n \b8\n \b9" | awk '{a[$1]}END{for (i in a) print i}'

Inserimento dati da file (numeri) in array “a” (non ordinato):

 $ cat numeri
 01
 02
 03
 04
 05
 06
 07
 08
 09
 10
 11
 12

$ awk ‘{a[$1]}END{for (i in a) print i}’ numeri

Gawk per il calcolo del tempo (systime etc):
http://www.theunixschool.com/2013/01/gawk-date-and-time-calculation-functions.html
http://www.theunixschool.com/2013/01/gawk-calculate-date-time-difference-timestamp.html

Miscellanea: come stampare solo i primi caratteri di una stringa:
http://www.theunixschool.com/2012/05/different-ways-to-print-first-few.html

printf in C:
http://www.codingunit.com/printf-format-specifiers-format-conversions-and-formatted-output

Devops Blog:
http://www.thedevopsblog.com/sysadmin/how-tos/geekish-one-liner-linux-command-sequences/

Geek! awk add a string:
http://nixtricks.wordpress.com/2009/10/24/awk-add-another-column-from-a-second-fil/

Miscellanea molto interessante:
http://www.pement.org/awk/awk1line.txt

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

INDICE

 

GUIDA AD AWK, 12 – scripts interessanti

Scripts “interessanti”

 

ORDINAMENTO DECRESCENTE DI UNA SERIE NUMERICA

Le funzioni “asort” e “asorti” di gawk permettono l’ordinamento dei valori di un array.
Tali funzioni, però, non sono disponibili in awk e, se si vuole ordinare una serie numerica, occorre eseguire una successione di istruzioni piuttosto articolata. Di seguito la “mia” soluzione. Conoscete soluzioni migliori? Fatemelo sapere!

1. popolare due array contenenti la serie numerica che si intende ordinare
2. impostare una variabile con il numero di elementi della serie numerica
3. Iniziare 3 cicli nidificati di loop con for: nei loop interno ed intermedio confrontiamo ogni valore del primo array con ogni valore del secondo e impostiamo la variabile “max” col valore più alto per poi concatenarla alla variabile “sorted”. Prima di uscire dal loop intermedio cancello l’elemento del primo array corrispondente al valore più alto trovato. In tal modo ottengo che nel loop esterno ad ogni ricorsione l’array risulterà mancante dell’ultimo elemento cancellato.
4. Alla fine dei loop stampare la variabile “sorted”.

Ecco lo script prima nella forma finale e poi nella forma che produce un output verboso, per chi volesse visualizzare i vari passaggi:

---------------------------------------
#!/bin/bash

## se lanciato senza argomento, avviso ed esco
if [ "$1" = "" ]; then
echo "Inserire una serie numerica con valori separati da virgola";
exit;
fi
echo "$1" |
## Creo gli array arr1 e arr2 con lo split delle serie in input:
awk '{split($0,arr1,",");split($0,arr2,",");}
## imposto la var c con il count degli elementi
{c=split($0,arr1,",");}
## inizio i cicli di confronto fra i valori
{
for (k=c;k>=1;k--) {
## all inizio di ogni ciclo svuoto le variabili con
## il valore massimo precedente
max=""; d="";
for (i=c;i>=1;i--) {
for (n=c;n>=1;n--) {
## imposto max con valore massimo
if (arr1[i]>=arr2[n]) {
if (max<arr1[i]) {
max=arr1[i]; d=i
}
}
}
}
## accodo max a sorted
sorted=sorted max",";
## cancello dall array l elemento con valore massimo
delete arr1[d];
}
## stampo la serie ordinata
print "Serie ordinata: " sorted;
}'
---------------------------------

Stesso script con output verboso:

#!/bin/bash

## se lanciato senza argomento, avviso ed esco
if [ "$1" = "" ]; then
echo "Inserire una serie numerica con valori separati da virgola";
exit;
fi
echo "$1" |
## Creo gli array arr1 e arr2 con lo split delle serie in input:
awk '{split($0,arr1,",");split($0,arr2,","); \
## e stampo e due array
print " Array arr1 in input: "; for (a in arr1) print "["a"]" " = " arr1[a];print \
" Array arr2 in input: "; for (b in arr2) print "["b"]" " = " arr2[b];}
## imposto la var c con il count degli elementi
{c=split($0,arr1,",");print "numero elementi: " c;}
## inizio i cicli di confronto fra i valori
{

for (k=c;k>=1;k--) {
## all inizio di ogni ciclo svuoto le variabili con
## il valore massimo precedente
max=""; d="";
for (i=c;i>=1;i--) {
for (n=c;n>=1;n--) {
## stampo i numeri che confronto
printf " "arr1[i] " <<<<----->>>> " arr2[n]" " "\n"
if (arr1[i]>=arr2[n]) {
if (max<arr1[i]) {
max=arr1[i]; d=i
}
}

}

}
## stampo numero di loop e relativo valore massimo max
print "valore maggiore nel loop " ++l " : "max;
## accodo max a sorted
sorted=sorted max","; print sorted;
## cancello dall array l elemento con valore massimo
print " proseguo cancellando il valore trovato ";
delete arr1[d]; print " cancello ""arr1["d"]";
## stampo l array dopo cancellazione
print "nuovo array: ";
for (a in arr1) print "["a"]" " = " arr1[a];
}
## stampo la serie ordinata - fine
print " SERIE ORDINATA: " sorted;

}'
-------------------------------------

Se vogliamo rendere lo script più interattivo, possiamo sostituire le prime righe con queste:

#!/bin/bash

echo "Prego inserire serie numerica con separatore virgola, seguita da [ENTER]:"
read serie
echo "$serie" |

## Creo gli array arr1 e arr2 con lo split delle serie in input:
awk '{split($0,arr1,",");split($0,arr2,",");}
(...)

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

INDICE

GUIDA AD AWK, 11 – funzioni

Le funzioni predefinite “LENGTH”, “SPLIT” E “GETLINE”

 

– La funzione “length”:

#!/usr/bin/awk -f
1 {
 if (length($0) > max) {
 max = length($0)
 }
}
END {
 print max
}

La funzione “lenght” restituisce la lunghezza della stringa fornita come parametro. Nell’esempio 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.
– La funzione “split”:

http://wwwcdf.pd.infn.it/AppuntiLinux/awk_funzioni_e_array.htm

http://star-www.rl.ac.uk/docs/sc4.htx/node39.html

E’ possibile convertire automaticamente una stringa in un array awk attraverso la funzione interna “split()”. La sintassi è:

split( <stringa>, <array>[, <separatore>])

Per esempio

split( “uno-due-tre”, elenco, “-” )

Crea l’array (o lo ricrea se già esistente, eliminando i precedenti elementi) “elenco” con tre elementi “uno”, “due”, “tre” e con indice (automatico) “1”, “2”, “3”. Quindi crea:

elenco[1] = “uno”
elenco[2] = “due”
elenco[3] = “tre”

In pratica:

$ cat pippo.txt
uno-due-tre-quattro
$ awk '{split($0,splitarray,"-")}END{for (i in splitarray) print "splitarray["i"]" " = " splitarray[i];}' pippo.txt
splitarray[1] = uno
splitarray[2] = due
splitarray[3] = tre
splitarray[4] = quattro

Un altro esempio che comprende anche l’ordinamento crescente dei valori mediante “sort” della bash

$ echo "5,3,1,8,20" | awk '{split($0,arr,","); for (i in arr) printf ("%s-%d\n", i, arr[i]) | "sort -t- -k2 -n"}'
3-1
2-3
1-5
4-8
5-20

– La funzione “getline”:

La funzione predefinita di awk “getline” permette di reindirizzare il contenuto di un file nell’azione corrente. Permette in tal modo di operare con valori provenienti da più file.
Vediamo come lavora “getline” dapprima con un esempio elementare e poi con esempi via via più complessi:

$ cat file-1
A 1
B 2
C 3
$ cat file-2
D 4
E 5
F 6
$ awk '{if (3 == 3){getline < "file-1"; print $0}}' file-2
A 1
B 2
C 3

Questo primo esempio verifica la condizione sempre vera “3 == 3″ e reindirizza $0 di file-2 nell’azione corrente per poi stamparne il contenuto. In questo comando “print $0″ agisce su “file-1″ e non su file-2.

$ awk '{str1=$1; str2=$2; getline < "file-2"; print str1" \t "str2" \t "$2 }' file-1
A 1 4
B 2 5
C 3 6

In questo secondo esempio, invece, l’output contiene le due colonne di file-1 più la seconda colonna di file-2 come terza colonna.
Le prime due variabili “str1″ e “str2″ contengono la prima e la seconda colonna di “file-1″.
La funzione “getline” consente una redirezione di $2 di “file-2″, premettendo quindi di ottenere un output composto da stringhe di file diverse ($1 e $2 di file-1 e $2 di file-2).

L’output può essere a sua volta reindirizzato in un terzo file “file-3″:

$ awk '{str1=$1; str2=$2; getline < "file-2"; print str1" \t "str2" \t "$2 > "file-3"}' file-1
$ cat file-3
A 1 4
B 2 5
C 3 6

E’ facile intuire come la fuzione getline offra molteplici possibilità di interoperabilità tra file.

Ora qualcosa di un po’ più avanzato. Abbiamo due file strutturati, “compare” e “compare1″ e vogliamo che i valori siano inseriti in due array distinti (uno per file). Il nome degli array sarà uguale al nome del file origine dei dati. Il comando deve stampare i valori di entrambe gli array:

$ cat compare
A 10
B 50
C 2
D 100
E 1200
F 1500
$ cat compare1
A 15
B 5
C 3
D 500
E 900
F 1500
$ awk '{var1=$1; var2=$2; compare[var1]=var2;}{getline < "compare1"; compare1[$1]=$2;}END{for(i in compare) print i " " compare[i]; compare1[$1]=$2; for (i in compare1) print i " " compare1[i] }' compare
A 10
B 50
C 2
D 100
E 1200
F 1500
A 15
B 5
C 3
D 500
E 900
F 1500

Ora che gestiamo tutti i valori possiamo iniziare a fare qualcosa di più interessante: fare delle comparazioni. Vogliamo che awk compari i valori del primo array con i corrispondenti valori del secondo array e ci restituisca se il primo è minore, maggiore o uguale al secondo:

$ awk ‘{var1=$1; var2=$2; compare[var1]=var2;}{getline < “compare1″; compare1[$1]=$2;} END{for (i in compare){ if (compare[i] < compare1[i]){ print ” < “}; if (compare[i] > compare1[i]){ print ” > “} if (compare[i] == compare1[i]){ print ” = “} }}’ compare
<
>
<
<
>
=

Ora vogliamo che l’output sia più leggibile. Immaginiamo di avere dei listini molto lunghi che vogliamo confrontare per evidenziare i costi maggiori, minori o uguali di due distributori differenti, calcolando anche la differenza di costi:

$ awk '{var1=$1; var2=$2; compare[var1]=var2; getline < "compare1"; compare1[$1]=$2;} END{for (i in compare){ if (compare[i] < compare1[i]){ print i " \t" compare[i] " \t"" < " " \t" compare1[i] " \t" "differenza:" (compare[i] - compare1[i]) }; if (compare[i] > compare1[i]){ print i " \t" compare[i] " \t" " > " " \t" compare1[i] " \t" "differenza:" (compare[i] - compare1[i])} if (compare[i] == compare1[i]){ print i " \t" compare[i] " \t" " = " " \t" compare1[i] " \t" "differenza:" (compare[i] - compare1[i])} }}' compare
A 10 < 15 differenza:-5
B 50 > 5 differenza:45
C 2 < 3 differenza:-1
D 100 < 500 differenza:-400
E 1200 > 900 differenza:300
F 1500 = 1500 differenza:0

Ovviamente quest’ultimo script risulta MOLTO più leggibile se scritto in un file eseguibile. A tal fine creiamo un file di testo chiamato “comparing.awk” contenente il testo qui di seguito:

#!/usr/bin/awk -f
{
var1=$1;
var2=$2;
}
{
compare[var1]=var2;
getline < "compare1"; compare1[$1]=$2;
}
END{
for (i in compare){
if (compare[i] < compare1[i]){
print i " \t" compare[i] " \t" " < " " \t" compare1[i] \
" \t" "differenza:" (compare[i] - compare1[i]) };
if (compare[i] > compare1[i]){
print i " \t" compare[i] " \t" " > " " \t" compare1[i] \
" \t" "differenza:" (compare[i] - compare1[i])};
if (compare[i] == compare1[i]){
print i " \t" compare[i] " \t" " = " " \t" compare1[i] \
" \t" "differenza:" (compare[i] - compare1[i])};
}
}

Lo rendiamo eseguibile (“chmod a+x comparing.awk”) e poi lo lanciamo dando come argomento lo stesso argomento che abbiamo dato al comando su linea di comando, ovvero il file “compare”:

$ ./comparing.awk compare
A 10 < 15 differenza:-5
B 50 > 5 differenza:45
C 2 < 3 differenza:-1
D 100 < 500 differenza:-400
E 1200 > 900 differenza:300
F 1500 = 1500 differenza:0

Certo sarebbe più comodo e flessibile se i file da confrontare fossero indicati come primo e secondo argomento dello script che lanciamo, anzichè avere il nome di uno dei file scritto all’interno dello script. Per ottenere questo trasformiamo lo script in questo modo lanciandolo da bash anzichè da awk:

$ vim comparing2.sh
#!/bin/bash
pluto=`echo $2`
awk -v secondoarg=$pluto '{
var1=$1;
var2=$2;
}
{
compare[var1]=var2;
getline < secondoarg; compare1[$1]=$2;
}
END{
for (i in compare){
if (compare[i] < compare1[i]){
print i " \t" compare[i] " \t" " < " " \t" compare1[i] \
" \t" "differenza:" (compare[i] - compare1[i]) };
if (compare[i] > compare1[i]){
print i " \t" compare[i] " \t" " > " " \t" compare1[i] \
" \t" "differenza:" (compare[i] - compare1[i])};
if (compare[i] == compare1[i]){
print i " \t" compare[i] " \t" " = " " \t" compare1[i] \
" \t" "differenza:" (compare[i] - compare1[i])};
}
}' $1
echo sono stati elaborati i file: \"$1\" e \"$pluto\"
----------------------------
$ chmod a+x comparing2.sh

$ ./comparing2.sh compare compare1
A 10 < 15 differenza:-5
B 50 > 5 differenza:45
C 2 < 3 differenza:-1
D 100 < 500 differenza:-400
E 1200 > 900 differenza:300
F 1500 = 1500 differenza:0
sono stati elaborati i file: "compare" e "compare1"

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

INDICE

GUIDA AD AWK, 10 – array

Gli array

 

Fonti:

http://ocs.unipa.it/AL/al239.htm

http://www.thegeekstuff.com/2010/03/awk-arrays-explained-with-5-practical-examples/

Awk supporta gli array. Gli array sono una sorta di estensioni delle variabili, delle variabili che memorizzano più di un valore.

La dichiarazione di un array in awk avviene nel momento in cui vi si fa riferimento. In pratica, con l’istruzione

a[2] = “ciao”

si assegna la stringa “ciao” all’elemento (detto anche indice) “2” dell’array “a”. Se l’array non esisteva, viene creato per l’occasione. Nello stesso modo, se l’elemento “2” non esisteva, viene creato all’interno dell’array.

In pratica, l’array di AWK è un insieme di elementi a cui si fa riferimento con un indice libero. Il fare riferimento a un elemento che non esiste, anche solo per leggerne il contenuto, implica la creazione di tale elemento. Come si può intuire, il riferimento a un elemento che non esiste ancora, crea tale elemento assegnandogli la stringa nulla, restituendo pertanto lo stesso valore.

Gli indici di un array sono trattati da awk come stringhe anche se sono numeri, pertanto non hanno un ordine preciso. L’ordine in cui appaiono gli elementi, dipende da AWK, e in generale dovrebbe dipendere dalla sequenza con qui questi sono stati inseriti.

Creazione di un array:

elenco[“italia”] = “roma”
elenco[“francia”] = “parigi”
elenco[3] = 345
elenco[2345] = “pippo”

Scandire gli elementi di un array:

for(i in elenco){print elenco[i]}

Vediamo in uno script come posso creare un array e leggere il contenuto degli elementi: $ vim array.awk

#!/usr/bin/awk -f

BEGIN {

elenco[“italia”] = “roma” elenco[“francia”] = “parigi” elenco[3] = 345 elenco[2345] = “pippo”

for (i in elenco) { print “elenco [“i”] contiene ” elenco[i] } }

$ ./array.awk elenco [italia] contiene roma elenco [2345] contiene pippo elenco [francia] contiene parigi elenco [3] contiene 345

Se vogliamo verificare il valore di un elemento o semplicemente verificare che esista:

if (234 in prova) {
print “L’elemento prova[234] corrisponde a ” prova[234]
}
Vediamo uno script che crea l’array e poi verifica il contenuto di un elemento:

$ vim array2.awk
#!/usr/bin/awk -f
BEGIN {
elenco[italia] = "roma"
elenco[francia] = "parigi"
elenco[3] = 345
elenco[2345] = "pippo"
if (francia in elenco) {
print "L'elemento elenco[francia] corrisponde a " elenco[francia]
}
}
$ ./array2.awk
 L'elemento elenco[francia] corrisponde a parigi

L’eliminazione di un elemento di un array si ottiene con l’istruzione “delete”:

delete <array>[<indice>]

E’ possibile convertire automaticamente una stringa in un array attraverso la funzione interna “split()”. La sintassi è:

split( <stringa>, <array>[, <separatore>])

Esempio:

split( "uno-due-tre", elenco, "-" )

Crea l’array (o lo ricrea se già esistente, eliminando i precedenti elementi) “elenco” con tre elementi “uno”, “due”, “tre” e con indice (automatico) “1”, “2”, “3”. Quindi crea:

elenco[1] = “uno”
elenco[2] = “due”
elenco[3] = “tre”

Esempio array semplice:

$ cat Iplogs.txt
 180607 093423 123.12.23.122 133
 180607 121234 125.25.45.221 153
 190607 084849 202.178.23.4 44
 190607 084859 164.78.22.64 12
 200607 012312 202.188.3.2 13
 210607 084849 202.178.23.4 34
 210607 121435 202.178.23.4 32
 210607 132423 202.188.3.2 167
$ awk '{
 > Ip[$3]++;
 > }
 > END{
 > for (var in Ip)
 > print var, "access", Ip[var]," times"
 > }
 > ' Iplogs.txt
 125.25.45.221 access 1 times
 123.12.23.122 access 1 times
 164.78.22.64 access 1 times
 202.188.3.2 access 2 times
 202.178.23.4 access 3 times

Lo script lista gli ip unici e conta quante volte ricorre nel file:
$3 è l’indirizzo ip ed è usato come indice dell’array chiamato “Ip”;
per ogni linea incrementa (“++”) il valore dell’indice (ip) corrispondente;
alla fine tutti gli indici saranno ip unici e il valore corrispondente saranno le ricorrenze di quegli ip sul file.

Esempio array doppio, con indici “incrociati”:

$ vim ex2.awk
 BEGIN {
 print "IP Address\tAccess Count\tNumber of sites";
 }
 {
 Ip[$3]++;
 count[$3]+=$NF;
 }
 END{
 for (var in Ip)
 print var,"\t",Ip[var],"\t\t",count[var];
 }
$ awk -f ex2.awk Iplogs.txt
 IP Address Access Count Number of sites
 125.25.45.221 1 153
 123.12.23.122 1 133
 164.78.22.64 1 12
 202.188.3.2 2 180
 202.178.23.4 3 110

Questo script lista tutti gli ip e calcola a quanti siti ha avuto accesso:
la sezione BEGIN stampa l’header del file;
la sezione successiva ha due array aventi entrambe lo stesso indice, ovvero $3 corrispondente all’ip;
l’array “Ip” contiene come indici gli indirizzi ip e come valore le relative ricorrenze;
l’array “count” contiene come indici gli stessi ip e come valore l’ultimo campo del file ($NF) che corrisponde al numero di siti a cui i relativi ip hanno avuto accesso;
nella sezione END, alla fine stampa gli ip unici e il numero di siti a cui hanno avuto accesso.

Esempio array doppio + exp + ciclo for + condizione if

$ cat ex3.awk
 {
 date[$1]++;
 }
 END{
 for (count in date)
 {
 if ( max < date[count] ) {
 max = date[count];
 maxdate = count;
 }
}
 print "Maximum access is on", maxdate;
 }
$ awk -f ex3.awk Iplogs.txt
 Maximum access is on 210607

Lista la data in cui c’è stato il massimo numero di accessi:
l’array “date” ha la data come indice e come valore il numero di ricorrenze;
“max” è la variabile che contiene il numero di ricorrenze ed è usata per trovare la data che ha il numero di ricorrenze più alto;
“maxdate” è la variabile che contiene la data con maggior numero di accessi.

Esempio array che inverte l’ordine degli indici e valori

$ cat test9
 prima riga
 seconda riga
 terza riga
 quarta riga
$ awk '{ a[i++] = $0 } END { for (j=i-1; j>=0;) print a[j--] }' test9
 quarta riga
 terza riga
 seconda riga
 prima riga

Lo script stampa le righe (records) del file in ordine inverso (dall’ultima alla prima):
inizialmente registra tutte le righe nell’array “a”;
una volta terminato il processamento, awk esegue l’END dello script;
l’END legge l’array in ordine inverso perchè il valore di “i” arriva col numero dell’ultima riga processata incrementato di 1 (4 nell’esempio). Prova di questo è:

$ awk '{ a[i++] = $0} END { print i }' test9
 4

il ciclo for setta poi “j” come “j=i-1; j>=0″ ovvero setta “j” a “3” e pone un while fino a che “j>=0″. A questo punto l’array “a” viene letto nella forma “a[j–]” ovvero nella successione degli indici: a[3], a[2], a[1], a[0] stampando i valori dell’ultimo al primo.

Esempio array per eliminare record duplicati

$ cat test10
 prima riga
 quarta riga
 seconda riga
 terza riga
 seconda riga
 quarta riga
$ awk '!($0 in array){ array[$0]; print }' test10
 prima riga
 quarta riga
 seconda riga
 terza riga

Lo script elimina i record duplicati:
tramite l’operatore “in” verifica che il record corrente esista nell’array “a”. Essendoci un “not” logico (“!”) -> se NON esiste lo memorizza e lo stampa.

Come abbiamo già detto, gli elementi di un array in awk non sono ordinati. Ci sono funzioni di “gawk” (“asort” e “asorti”) che svolgono tale funzione di ordinamento, ma noi non ce ne occuperemo.
Qualora fosse necessario ordinare gli elementi o i valori di un array possiamo reindirizzare l’output di awk al comando “sort” della shell.
Ad esempio creiamo un array che prende i valori dall’output di “echo” e stampiamo l’array nella forma <indice>-<valore>:

$ echo "5,3,1,8,20" | awk '{split($0,arr,","); for (i in arr) printf ("%s-%d\n", i, arr[i])}'
 1-5
 2-3
 3-1
 4-8
 5-20

Ora lo vogliamo ordinare sulla base degli indici (ovviamente non cambia nulla perchè gli indici rispettano la successione di creazione, quindi sono già ordinati):

$ echo "5,3,1,8,20" | awk '{split($0,arr,","); for (i in arr) printf ("%s-%d\n", i, arr[i]) | "sort -t- -k1 -n"}'
 1-5
 2-3
 3-1
 4-8
 5-20

E ora invece sulla base dei valori:

$ echo "5,3,1,8,20" | awk '{split($0,arr,","); for (i in arr) printf ("%s-%d\n", i, arr[i]) | "sort -t- -k2 -n"}'
 3-1
 2-3
 1-5
 4-8
 5-20

Se vogliamo fare un programmino che ci ordini del valori che diamo come argomento e che li renda disponibili nella variabile esportata “ARRAYSORTED”:

$ vim ordina.awk
#!/bin/bash

export ARRAYSORTED=`echo $1 |

awk '{split($0,arr,","); for (i in arr) print arr[i] | "sort -k1 -n"
}'`

echo $ARRAYSORTED
--------------------

$ ./ordina.awk 3,2,78,32,8,6,100
2 3 6 8 32 78 100

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

INDICE

GUIDA AD AWK, 09 – elaborare campi

Inserire, rimuovere, aggiornare campi in un file csv

$ cat test
 uno,10,roma
 due,20,milano
 tre,30,venezia
 quattro,40,napoli
 cinque,50,verona
 sei,60,vicenza
 sette,70,latina
 otto,80,frascati
 nove,90,albano
# sostituisce la colonna $1 con un intero progressivo (++i)
 $ awk -F, '{$1=++i}1' OFS=, test
 1,10,roma
 2,20,milano
 3,30,venezia
 4,40,napoli
 5,50,verona
 6,60,vicenza
 7,70,latina
 8,80,frascati
 9,90,albano
# Aggiunge una colonna con un intero progressivo alla prima colonna
 $ awk -F, '{$1=++i FS $1}1' OFS=, test
 1,uno,10,roma
 2,due,20,milano
 3,tre,30,venezia
 4,quattro,40,napoli
 5,cinque,50,verona
 6,sei,60,vicenza
 7,sette,70,latina
 8,otto,80,frascati
 9,nove,90,albano
# Stesso risultato:
 $ awk -F, '{x=++i;} {print x, $1, $2, $3}' OFS="," test
 1,uno,10,roma
 2,due,20,milano
 3,tre,30,venezia
 4,quattro,40,napoli
 5,cinque,50,verona
 6,sei,60,vicenza
 7,sette,70,latina
 8,otto,80,frascati
 9,nove,90,albano
# O ancora:
 $ awk -F, '{x=++i;} {print x, $0}' OFS="," test
 1,uno,10,roma
 2,due,20,milano
 3,tre,30,venezia
 4,quattro,40,napoli
 5,cinque,50,verona
 6,sei,60,vicenza
 7,sette,70,latina
 8,otto,80,frascati
 9,nove,90,albano
# La variabile di awk "NF" indica il numero di colonne. Quindi "$(NF+1)" indicherà la colonna da aggiungere come ultima colonna, Dandole il valore "++1" otterremo l'aggiunta di una colonna alla fine contenente un intero progressivo:
 $ awk -F, '{$(NF+1)=++i;}1' OFS=, test
 uno,10,roma,1
 due,20,milano,2
 tre,30,venezia,3
 quattro,40,napoli,4
 cinque,50,verona,5
 sei,60,vicenza,6
 sette,70,latina,7
 otto,80,frascati,8
 nove,90,albano,9
# Inseriamo una colonna prima della penultima colonna
 $ awk -F, '{$(NF-1)=++i FS $(NF-1);}1' OFS=, test
 uno,1,10,roma
 due,2,20,milano
 tre,3,30,venezia
 quattro,4,40,napoli
 cinque,5,50,verona
 sei,6,60,vicenza
 sette,7,70,latina
 otto,8,80,frascati
 nove,9,90,albano
# Facciamo un update della seconda colonna aggiungendo il valore 10
 $ awk -F, '{$2+=10;}1' OFS=, test
 uno,20,roma
 due,30,milano
 tre,40,venezia
 quattro,50,napoli
 cinque,60,verona
 sei,70,vicenza
 sette,80,latina
 otto,90,frascati
 nove,100,albano
# Convertire la prima colonna in maiuscolo:
 $ awk -F, '{$1=toupper($1)}1' OFS=, test
 UNO,10,roma
 DUE,20,milano
 TRE,30,venezia
 QUATTRO,40,napoli
 CINQUE,50,verona
 SEI,60,vicenza
 SETTE,70,latina
 OTTO,80,frascati
 NOVE,90,albano

toupper -> funzione di awk che converte da minuscolo a maiuscolo
tolower -> converte da maiuscolo a minuscolo

# Estraiamo solo i primi 3 caratteri della prima colonna:
 $ awk -F, '{$1=substr($1,1,3)}1' OFS=, test
 uno,10,roma
 due,20,milano
 tre,30,venezia
 qua,40,napoli
 cin,50,verona
 sei,60,vicenza
 set,70,latina
 ott,80,frascati
 nov,90,albano

substr -> funzione di awk che ha 3 argomenti: stringa intera, posizione di partenza, numero di caratteri da estrarre

# "Svuotare" una colonna:
 $ awk -F, '{$2=""}1' OFS=, test
 uno,,roma
 due,,milano
 tre,,venezia
 quattro,,napoli
 cinque,,verona
 sei,,vicenza
 sette,,latina
 otto,,frascati
 nove,,albano
# Rimuovere una colonna:
 $ awk -F, '{x=2; for(i=1;i<=NF;i++)if(i!=x)f=f?f FS $i:$i;print f;f=""}' test
 uno,roma
 due,milano
 tre,venezia
 quattro,napoli
 cinque,verona
 sei,vicenza
 sette,latina
 otto,frascati
 nove,albano
 
# Unire la seconda colonna con la terza e rimuovi la terza colonna:
 $ awk -F, '{$2=$2$x;for(i=1;i<=NF;i++)if(i!=x)f=f?f FS $i:$i;print f;f=""}' x=3 test
 uno,10roma
 due,20milano
 tre,30venezia
 quattro,40napoli
 cinque,50verona
 sei,60vicenza
 sette,70latina
 otto,80frascati
 nove,90albano

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

INDICE

GUIDA AD AWK, 08 – delimitatori multipli

Elaborare dati in file con delimitatori multipli

 

Se vengono utilizzate le quadre per indicare i delimitatore, awk interpreta i delimitatori con una sorta di OR fra i delimitatori stessi; se le graffe non ci sono, awk interpreta i delimitatori come un unico delimitatore:

awk -F ‘[:;]’ -> è la sintassi per impostare delimitatori multipli (“:” OR “;”)
awk -F ‘;;;’ -> il delimitatore è “;;;” (“;;;”)
awk -F ‘[][]’ -> i delimitatori sono le parentesi quadre (“]” OR “[“)

# Il file ha due tipi di delimitatori, ":" e ";":
$ cat file5
primo:2010:10;20;30
secondo:2011:12;29;19
terzo:2012:15;50;61
quarto:2013:10;55;113
# Fai la somma dei campi 3, 4 e 5:
$ awk -F '[:;]' '{x=$3+$4+$5; print $1" anno: " $2" totale delle entrate: " x " EURO"}' file5
primo anno: 2010 totale delle entrate: 60 EURO
secondo anno: 2011 totale delle entrate: 60 EURO
terzo anno: 2012 totale delle entrate: 126 EURO
quarto anno: 2013 totale delle entrate: 178 EURO
# Scompatta ogni record (linea) a seconda dei valori sulle colonne 3,4 e 5
$ awk -F '[;:]' '{for(i=3;i<=5;i++){print $1,$2,$i;}}' OFS=":" file5
primo:2010:10
primo:2010:20
primo:2010:30
secondo:2011:12
secondo:2011:29
secondo:2011:19
terzo:2012:15
terzo:2012:50
terzo:2012:61
quarto:2013:10
quarto:2013:55
quarto:2013:113

Crea un nuovo record per ogni valore di 3, 4 e 5. Il loop:
(i=3;i<=5;i++) -> produce per ogni record i valori 3, 4 e 5 assegnandoli alla variabile “i”, quindi stampa:
{print $1,$2,$i;} -> 1, 2, 3; 1, 2, 4; 1, 2, 5 per ogni record

# Il file ha le parentesi quadre come delimitatori:
$ cat file6
primo;2010[10]20;30
secondo;2011[12]29;19
terzo;2012[15]50;61
quarto;2013[10]55;113
$ awk -F '[][]' '{print $2}' file6
10
12
15
10
# Delimitatori multipli con parentesi quadre
$ awk -F '[][;]' '{print $2, $3, $4, $5}' OFS="-" file6
2010-10-20-30
2011-12-29-19
2012-15-50-61
2013-10-55-113
# Serie di delimitatori:
$ cat file7
123;;;202;;;203
124;;;213;;;203
125;;;222;;;203

ERRORE:
$ awk -F'[;;;]’ ‘{print $2}’ file7
(nessun output)

awk ha interpretato -F'[;;;]’ come “;” OR “;” OR “;” quindi imposta solo un “;” come delimitatore. Restituisce di conseguenza la colonna vuota tra il primo e il secondo “;”. Se si vuol visualizzare la colonna con i valori 202, 213 e 222 occorre quindi richedere la colonna 4:

$ awk -F'[;;;]' '{print $4}' file7
202
213
222
# Per indicare con precisione il delimitatore ";;;" dobbiamo usare -F';;;':
$ awk -F';;;' '{print $2}' file7
202
213
222
# Possiamo naturalmente utilizzare della parole come delimitatori:
$ cat file8
2014Romacapodanno
2013Romanatale
2012Romapasqua
2011Romacarnevale
$ awk -F'Roma' '{print $1, $2}' file8
2014 capodanno
2013 natale
2012 pasqua
2011 carnevale
# Possiamo anche impostare più parole come delimitatori:
$ cat file9
2014Romacapodanno
2013Napolinatale
2012Firenzepasqua
2011Romacarnevale
$ awk -F'Roma|Firenze|Napoli' '{print $2}' file8
capodanno
natale
pasqua
carnevale

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

INDICE

GUIDA AD AWK, 07 – raggruppare dati

Raggruppare dati in un file csv o txt

$ cat file4
 primo,100
 secondo,200
 terzo,300
 quarto,400
 quinto,500
 sesto,600
 settimo,700
 ottavo,800
 terzo,400

Calcolare la somma dei valori di una data colonna:

$ awk -F"," '{x=x+$2} END{print x}' file4
 4000

Il comando è abbastanza autoesplicativo. NB “x=x+$2″ si può indicare anche in modo più sintetico “x+=$2;”:

$ awk -F"," '{x+=$2} END{print x}' file4
 4000
# Variante del precedente:
 $ VAR="terzo"
 $ awk -F, -v inp=$VAR '$1==inp{x+=$2;}END{print x}' file4
 700

Per comprendere come awk gestisce gli array, vedi qui <link a awk array -> awk_9>

# Cercare valori unici nell prima colonna
 $ awk -F, '{a[$1];}END{for (i in a)print i;}' file4

a[$1] -> imposta l’array “a” con $1 come indice. L’array del nostro esempio quindi conterrà: “primo” nella prima ricorrenza; “secondo” nella seconda ricorrenza; terzo nella terza ricorrenza e così via.

END{for (i in a)print i;} -> alla fine della lettura del documento il ciclo for legge tutti gli indici dell’array assegnandoli alla variabile “i” e li stampa a scxhermo ad uno ad uno.
Siccome gli indici devono essere unici, nell’array “a” del nostro esempio il campo “terzo” sarà presente una sola volta. Di fatto il comando cancella i “doppioni”.
Nota: generalmente l’array ha un altro elemento, cioè il valore di quel dato indice. Ad esempio array1[“Dic”]=25 significa che l’array “array” ha un indice “Dic” con valore “25”. Per approfondimento sull’array: <link>

# Calcolare la somma di valori di particolari gruppi di record
 $ awk -F, '{a[$1]=a[$1]+$2;}END{for(i in a)print i", "a[i];}' file4
 sesto, 600
 quarto, 400
 settimo, 700
 primo, 100
 ottavo, 800
 quinto, 500
 terzo, 700
 secondo, 200
# "a[$1]=a[$1]+$2" può essere indicato nella forma contratta "a[$1]+=$2":
 $ awk -F, '{a[$1]+=$2;}END{for(i in a)print i", "a[i];}' file4
 sesto, 600
 quarto, 400
 settimo, 700
 primo, 100
 ottavo, 800
 quinto, 500
 terzo, 700
 secondo, 200
Sviluppo di "{a[$1]+=$2;}":
 a[primo]=100
 a[secondo]=200
 a[terzo]=300
 ...
 a[ottavo]=800
 a[terzo]=700 # in quanto al preesistente 300 viene aggiunto il nuovo valore 400

Essendo gli indici UNICI, otterremo la somma dei valori appartenenti allo stesso indice. Awk, nel nostro esempio, imposta la prima volta l’indice “terzo” con valore 300, poi alla seconda ricorrenza dello stesso indice “terzo” aggiungerà semplicemente il valore di 400.
END{for(i in a)print i”, “a[i];} -> per ogni indice nell’array “a” stampa l’indice (primo, secondo etc) seguito da virgola e spazio, e poi il valore assegnato al relativo indice.
In questo esempio l’array è “completo”: nome array (“a”), indice (valori prima colonna) e relativo valore (valori seconda colonna).

# Appendere all'output la somma di una colonna:
 $ awk -F"," '{x+=$2;print}END{print "Totale, "x}' file4
 primo,100
 secondo,200
 terzo,300
 quarto,400
 quinto,500
 sesto,600
 settimo,700
 ottavo,800
 terzo,400
 Totale, 4000
# Stampare il valore massimo di ogni gruppo di dati:
 awk -F, '{if(a[$1]<$2)a[$1]=$2;}END{for(i in a){print i,a[i];}}' file4

Prima di storare nell’array il valore “$2″ awk compara tale valore del record corrente con il valore già storato. Quest’ultimo viene sostituito se inferiore al primo. Cambiando l’operatore < in > si ottiene ovviamente l’effetto contrario.

# Conta le entries di ciascun gruppo:
 $ awk -F, '{a[$1]++;}END{for(i in a)print i, a[i];}' file4
 sesto 1
 quarto 1
 settimo 1
 primo 1
 ottavo 1
 quinto 1
 terzo 2
 secondo 1

Ogni volta che a[$1] viene matchato, incrementa il valore di quell’indice di 1. Quindi ogni volta che un indice viene matchato, incrementa il relativo valore di 1. Una volta elaborate tutte le righe, stampa l’indice e il relativo valore.

# Stampare solo il primo record di ogni gruppo:
 $ awk -F, '!a[$1]++ {print}' file4
 primo,100
 secondo,200
 terzo,300
 quarto,400
 quinto,500
 sesto,600
 settimo,700
 ottavo,800

Interessante: quando la prima riga di un gruppo viene elaborata a[$1] resta a 0, visto che ++ è conseguente alla variabile, ma il not (!) di 0 è 1 che è vero e quindi la prima ricorrenza viene stampata. Quando la seconda ricorrenza dello stesso gruppo viene elaborata a[$1] va a 1, ma il not (!) di 1 è 0, che è falso e quindi la riga non viene stampata. In tal modo solo la prima ricorrenza viene stampata.
Ovviamente rimuovendo il not (!) vengono stampate tutte le ricorrenze di ogni gruppo, tranne la prima.

# Concatenare i valori di ogni gruppo:
 $ awk -F, '{if(a[$1])a[$1]=a[$1]":"$2; else a[$1]=$2;} END{for (i in a)print i, a[i];}' file4
 sesto 600
 quarto 400
 settimo 700
 primo 100
 ottavo 800
 quinto 500
 terzo 300:400
 secondo 200

Se “a[$1]” contiene un valore concatena il valore corrente utilizzando i “:”, altrimenti assegna semplicemente ad a[$1] il valore del relativo 2, visto che evidentemente è il primo valore della ricorrenza.
Lo stesso comando, in forma di script awk sarà:

 #!/usr/bin/awk -f
 
{
 FS=","
 if (a[$1]){
 a[$1]=a[$1]":"$2;
 }
 else {
 a[$1]=$2;
 }
 }
 END{
 OFS=","
 for (i in a){
 print i, a[i];
 }
 }

Lo stesso risultato si può ottenere con l’operatore ternario di awk, uguale a quello del linguaggio C:

 $ awk -F, '{a[$1]=a[$1]?a[$1]":"$2:$2;}END{for (i in a)print i, a[i];}' OFS=, file4

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

INDICE

GUIDA AD AWK, 06 – ricerca pattern

Cercare un pattern in un file, nell’intera riga o in una colonna

 

Simulazione di grep:

$ awk '$0 ~ /pippo/{print}' file7
pippo 50

~ = contiene

Di default awk cerca sull’intera riga, quindi si può omettere “$0″:

$ awk '/pippo/{print}' file7

E siccome di default stampa l’intera riga, il tutto si riduce in:

$ awk '/pippo/' file7

Se vogliamo che il pattern matchi solo con la prima colonna e stampi l’intera riga:

$ awk '$1 ~ /pippo/' file7

Se in file7 nella prima colonna ci sarà sia “pippo” che “pippos” il risultato sarà:

$ awk '$1 ~ /pippo/' file7
pippo 50
pippos 60

Quindi se vogliamo che il match sia sulla parola esatta, anzichè utilizzare la sintassi “$1 ~ / …” potremo digitare:

$ awk '($1=="pippos") {print}' file7
oppure
$ awk '($1=="pippos")' file7
o ancora meglio:
$ awk '$1=="pippos"' file7

Riassumendo:
$ awk ‘/pippo/’ file7 -> cerca le parole uguali a o che contengano “pippo” nel file file7 e stampa la riga
$ awk ‘$1==”pippo”‘ file7 -> cerca le parole che coincidano perfettamente con pippo in file7 e stampa la riga

Con l’operatore OR (|) possiamo richiedere il match con più parole:

$ awk '/pippo|pluto/' file7
pippo 50
pluto 100
pippos 60
plutos 110

Se cerchiamo solo il match nella prima colonna:

$ awk '($1 ~ /pluto|pippo/)' file7

Se poi il match deve essere esatto uso gli indicatori ^ (inizio riga) e $ di fine riga:

$ awk '($1 ~ /^pluto$|^pippo$/)' file7

Pattern mismatch:

$ awk '!/pippo/' file7
$ awk '$1 !~ /pluto$/' file7

Ora vogliamo stampare i records in cui il secondo campo contiene un valore superiore o uguale a 160:

$ awk '$2>=160' file7

Per stampare il record contenente pippo ma solo se è il primo record:

$ awk 'NR==1 && /pippo/' file7

Per stampare il numero dei record contenenti la stringa “pippo”:

$ awk '/pippo/ {print NR}' file7
1
5

Ora vogliamo stampare i record contenenti il pattern “pluto” e aventi un valore nella seconda colonna superiore a 90:

$ awk '/pluto/ && $2>90' file7
pluto 100
plutos 110

&& = AND -> deve essere soddisfatta sia la prima che la seconda condizione
Diverso l’operatore:
|| = OR -> deve essere soddisfatta o la prima o la seconda condizione

Stampare i records con valore superiore a 90 o contenenti la stringa pippo:

$ awk '/pluto/ || $2>90' file7
pluto 100
paperino 150
minnie 200
plutos 110
paperinos 160
minnies 210

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

INDICE

GUIDA AD AWK, 05 – join righe

Join di righe

 

Per comprendere i prossimi comandi è necessario aprire un inciso su printf
————————————–
Inciso su printf:
è una funzione del linguaggio C, che sostanzialmente stampa a schermo un determinato output in un determinato formato

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
—————————————–

$ cat lista1
 HEADER
 pippo
 pluto
 paperino
 HEADER
 roma
 parigi
 newyork
 HEADER
 acqua
 terra
 fuoco
$ awk '/HEADER/{if (NR!=1)print "";next}{printf $0}END{print "";}' lista1
 pippoplutopaperino
 romapariginewyork
 acquaterrafuoco

Studiamo questo comando.
Vogliamo fare il join di tutte le righe che seguono il pattern “HEADER”
/HEADER/ -> cerca le linee contenenti “HEADER”
{if (NR!=1)print “”;next} -> si attiva solo se la riga contiene “HEADER”. Stampa una linea vuota (print “”) ad eccezione della prima riga (NR!=1). Senza questa condizione una linea vuota verrebbe stampata all’inizio dell’output. Lo statement “next” indica ad awk di stoppare il processamento della riga e di passare alla riga successiva. Fino a qui, quindi, abbiamo detto ad awk di stampare una riga vuota per tutte le ricorrenze “HEADER”, ad eccezione che per la prima riga.
{printf $0} -> sarà vera per tutte le ricorrenze che NON contengono “HEADER”, per effetto di “next”. Stampa la linea senza andare a capo (printf anzichè print). Come risultato avremo tutte le righe dopo il pattern “HEADER” stampate nella stessa riga
END{print “”;} -> stampa una nuova riga alla fine (senza avremmo l’output sulla stessa riga del prompt

Se vogliamo che i campi dell’output siano divisi da uno spazio:

$ awk '/HEADER/{if (NR!=1)print "";next}{printf "%s ", $0}END{print "";}' lista1
 pippo pluto paperino
 roma parigi newyork
 acqua terra fuoco

{printf “%s “, $0} -> il valore di $0 lo stampi con lo specificatore di formato “%s “, ovvero il valore stringa di $0 stesso seguito da uno spazio

Se invece vogliamo che il delimitatore sia la virgola:

$ awk '/HEADER/{if (x)print x;x="";next}{x=(!x)?$0:x","$0;}END{print x;}' lista1
 pippo,pluto,paperino
 roma,parigi,newyork
 acqua,terra,fuoco

Utilizziamo la variabile “x” per storare l’intera stringa e stampiamo il valore di “x” ogni volta che viene matchato il pattern:
/HEADER/{if (x)print x;x=””;next} -> quando viene matchato “HEADER” se x esiste stampane il valore altrimenti impostala a null. next: passa alla riga successiva.
{x=(!x)?$0:x”,”$0;} -> se x è nullo assegna a x la riga corrente, altrimenti fai seguire a x una virgola e la riga corrente.
In tal modo x conterrà le righe unite da una virgola.
Visto che l’istruzione di stampa a schermo si trova all’interno delle graffe del match con “HEADER”, per stampare l’ultimo valore di x, viene aggiunta l’istruzione:
END{print x;} -> stampa il valore di x prima di uscire.

Ovviamente se si vuole che anche HEADER venga stampato, è sufficiente togliere il “next”:

$ awk '/HEADER/{if (x)print x;x="";}{x=(!x)?$0:x","$0;}END{print x;}' lista1
 HEADER,pippo,pluto,paperino
 HEADER,roma,parigi,newyork
 HEADER,acqua,terra,fuoco

In ultima, se vogliamo che HEADER non sia unito al resto della stringa, lo possiamo far stampare separatamente dal valore di x:

$ awk '/HEADER/{if (x)print x;print;x="";next}{x=(!x)?$0:x","$0;}END{print x;}' lista1
 HEADER
 pippo,pluto,paperino
 HEADER
 roma,parigi,newyork
 HEADER
 acqua,terra,fuoco

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

INDICE