GUIDA A SED, 00 – fondamenti

Fondamenti

 

Fonte:
http://www.pluto.it/files/ildp/guide/abs/sedawk.html

Mentre awk è un vero e proprio linguaggio di programmazione per l’elaborazione di file di testo strutturati orientato ai campi e con una sintassi simile a quella del C, sed è un editor non interattivo di file di testo.
Nonostante le loro differenze, le due utility condividono una sintassi d’invocazione simile, entrambe fanno uso delle Espressioni Regolari, entrambe leggono l’input, in modo predefinito, dallo stdin ed entrambe inviano i risultati allo stdout. Sono strumenti UNIX ben collaudati e funzionano bene insieme. L’output dell’una può essere collegato, per mezzo di una pipe, all’altra e le loro capacità combinate danno agli script di shell parte della potenza di Perl.

Sed è un editor di riga non interattivo. Riceve un testo come input, o dallo stdin o da un file, esegue alcune operazioni sulle righe specificate, una alla volta, quindi invia il risultato allo stdout o in un file. Negli script di shell, sed è, di solito, una delle molte componenti di una pipe.

Sed determina le righe dell’input, su cui deve operare, tramite un indirizzo che gli è stato passato. Questo indirizzo può essere rappresentato sia da un numero di riga sia da una verifica d’occorrenza.
Ad esempio, “3d” indica a sed di cancellare la terza riga dell’input, mentre “/windows/d” segnala a sed che si vogliono cancellare tutte le righe dell’input contenenti l’occorrenza “windows”.

Sintassi:

sed [opzioni] comando_di_editing [file]

Il formato per i comandi di editing è:

[indirizzo1[,indirizzo2]] [funzione] [argomenti]
Operatori di base di sed:

[indirizzo]/p -> print Visualizza [l’indirizzo specificato]

[indirizzo]/d -> delete Cancella [l’indirizzo specificato]

s/modello1/modello2/ -> substitute Sostituisce in ogni riga la prima occorrenza della stringa modello1 con la stringa modello2

[indirizzo]/s/modello1/modello2/-> substitute Sostituisce, in tutte le righe specificate in indirizzo, la prima occorrenza della stringa modello1 con la stringa modello2

[indirizzo]/y/modello1/modello2/-> transform sostituisce tutti i caratteri della stringa modello1 con i corrispondenti caratteri della stringa modello2, in tutte le righe specificate da indirizzo (equivalente di tr)

g -> global Agisce su tutte le verifiche d’occorrenza di ogni riga di input controllata

q -> quit Esci quanto viene matchato un pattern

Esempi di operatori sed:

8d -> Cancella l’ottava riga dell’input.
/^$/d -> Cancella tutte le righe vuote.
1,/^$/d -> Cancella dall’inizio dell’input fino alla prima riga vuota compresa.
/Jones/p -> Visualizza solo le righe in cui è presente “Jones” (con l’opzione -n).
s/Windows/Linux/ -> Sostituisce con “Linux” la prima occorrenza di “Windows” trovata in ogni riga dell’input.
s/BSOD/stabilità/g -> Sostituisce con “stabilità” tutte le occorrenze di “BSOD” trovate in ogni riga dell’input.
s/ *$// -> Cancella tutti gli spazi che si trovano alla fine di ogni riga.
s/00*/0/g -> Riduce ogni sequenza consecutiva di zeri ad un unico zero.
/GUI/d -> Cancella tutte le righe in cui è presente “GUI”.
s/GUI//g -> Cancella tutte le occorrenze di “GUI”, lasciando inalterata la parte restante di ciascuna riga.

Alcune valide flag per le funzioni sostitutive sono:

Le opzioni:
-e -> (edit) indica che la stringa successiva deve essere interpretata come un’istruzione di editing, quindi se viene passata un’unica istruzione il “-e” può essere omesso
-e script script di editing

-n -> indica a sed di visualizzare solo quelle righe che verificano il modello, altrimenti verrebbero visualizzate tutte le righe dell’input. In pratica non stampa l’output di default, ma solamente quelle linee specificate dalle funzioni p o s///p

-i -> indica a sed di operare direttamente sul file sorgente (edit the file “in-place”)
Il quoting forte (”) protegge i caratteri speciali delle espressioni regolari presenti nell’istruzione, dalla reinterpretazione da parte dello script. Questo riserva solo a sed l’espansione delle espressioni regolari.

La barra inversa costringe il comando di sostituzione sed a continuare sulla riga successiva. L’effetto è quello di usare il carattere di a capo alla fine della prima riga come stringa di sostituzione.

s/^ */\
/g

In questo modo, tutti gli spazi che si trovano all’inizio della riga vengono sostituiti con un carattere di a capo. Il risultato finale è la sostituzione di tutte le indentazioni dei paragrafi con righe vuote poste tra gli stessi paragrafi.

Un indirizzo seguito da una, o più, operazioni può richiedere l’impiego della parentesi graffa aperta e chiusa, con un uso appropriato dei caratteri di a capo.

/[0-9A-Za-z]/,/^$/{
/^$/d
}
Questo cancella solo la prima di ogni serie di righe vuote. Potrebbe essere utile per effettuare la spaziatura singola di un file di testo mantenendo, però, la/e riga/he vuota/e tra i paragrafi.

Esempi:

Questo esempio modifica tutte le accidentali virgole (,) in una virgola seguita da uno spazio (, ) quindi crea l’output:

% cat filey | sed s/,/,\ /g

Il seguente esempio rimuove tutte le accidentali Jr precedute da uno spazio (Jr) all’interno del file filey:

% cat filey | sed s/\ Jr//g

Per realizzare operazioni multiple sull’input, si precede ogni operazione con l’opzione -e (edit) e si quota la stringa. Ad esempio, per filtrare le linee contenenti «Date: » e «From: » e rimpiazzarle senza i due punti (:):

% sed -e ‘s/Date: /Date /’ -e ‘s/From: /From /’

Per visualizzare solamente le linee del file che iniziano con «Date:» e includerne una che inizia con «Name:»:

% sed -n ‘/^Date:/,/^Name:/p’

Per stampare solamente le prime 10 linee dell’input (un rimpiazzo di “head”):

% sed -n 1,10p

———————————————-

 INDICE

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