[GNU/Linux] Comando awk

Fab996
Non ho ben chiaro cosa faccia questo comando
awk 'BEGIN{FS="_"} ($1 == "CIAO") {print $5,$2}'

Il comando awk ha la forma pattern {azione}, non mi è ben chiaro cosa faccia il BEGIN e perchè FS="_". FS non è la variabile che contiene il divisore dei campi? Diciamo che non mi è chiaro in generale la sintassi :')

Risposte
anonymous_be1147
Ciao, il comando stampa il 5° e il 2° campo di una riga in input, quando il primo campo è uguale a "CIAO". Inoltre si presume che i campi siano separati da un carattere di sottolineatura.

[inline]BEGIN[/inline] è un "contrassegno" speciale che segnala ad [inline]awk[/inline] un blocco di codice da eseguire prima di iniziare a leggere l'input. Nel tuo caso l'istruzione da eseguire è [inline]FS="_"[/inline], cioè impostare il separatore dei campi (FS: field separator) uguale al carattere _.

Esempio:

awk 'BEGIN{FS="_"} ($1 == "CIAO") {print $5,$2}' <<EOF
CIAO_COSA_STAMPERÀ_MAI_STA_RIGA?
CIAO NON NE HO PROPRIO_IDEA.
EOF

stampa:

STA COSA

Fab996
Grazie, ma perchè ($1=="CIAO") è messo tra parentesi tonde? Invece
awk '{if ($3>max){max=$3}} END {print max}'
come si interpreta ? :?

anonymous_be1147
"Fab996":
Grazie, ma perchè ($1=="CIAO") è messo tra parentesi tonde?

Perché un pattern può essere tra parentesi tonde, anche se qui non erano strettamente necessarie. In questo caso, il pattern è di tipo relazionale e usa l'operatore di uguaglianza ==.

Invece
awk '{if ( $3>max){max=$3}} END {print max}'
come si interpreta ? :?

La prima azione, [inline]{if ( $3>max){max=$3}}[/inline], è priva di pattern, quindi si applicherà a tutte le righe in input; la seconda, [inline]{print max}[/inline], verrà eseguita invece in corrispondenza del pattern [inline]END[/inline], cioè dopo che tutte le righe in input sono state elaborate.

Quindi il programma funziona più o meno così: legge una riga di input e la scompone nei vari campi: \$1, \$2 etc. Esegue quindi la prima azione: se il terzo campo $3 è maggiore di quello della variabile max, allora assegna a quest'ultima il valor di \$3. Siccome in awk le variabili sono dinamiche, vengono cioè create la prima volta che compaiono nel programma, alla prima esecuzione max contiene la stringa vuota, e quindi le viene assegnata il valore del terzo campo (se esiste). E così via, fino alla lettura dell'ultima riga in input. Alla fine viene eseguita la seconda azione, che stamperà il valore finale della variabile max.

Per tutti i dettagli della sintassi awk, puoi eseguire il comando [inline]man awk[/inline] o [inline]info awk[/inline], ma c'è anche il manuale online https://www.gnu.org/software/gawk/manual/gawk.html , per l'implementazione di GNU. Se ci sono dubbi, chiedi pure. :)

Fab996
Ciao mi è venuto un dubbio con questo esercizio, avendo il testo:
5
2 5
10 3 4

e il seguente codice
#!/bin/awk –f

	{
		for (i=1; i <= NF; i++ ) { somma[i] += $i;
		}
	}
		END {
			for (i=1; i<=NF; i++) printf("%d ",somma[i]);
			printf("\n");
		}


col primo for ottengo un vettore somma[1] = 17, somma[2] = 8, somma[3] = 4, però perchè poi per stampare il vettore nell'END mette <=NF, cioè del numero di campi di ogni riga ?

anonymous_be1147
Infatti è sbagliato, se lo scopo è quello di stampare tutti gli elementi del vettore somma. Così com'è il programma stampa alla fine solo tanti elementi quanti sono i campi dell'ultima riga in input (prova ad esempio ad aggiungere una riga che contenga uno o due numeri). Ovviamente potrebbe essere corretto se si sapesse in anticipo e con certezza che l'input conterrà righe con un numero sempre crescente di campi.

Fab996
Ti ringrazio, scusa se ti disturbo ancora, in questo esercizio:

Scrivere uno script in awk che produca in output una tabella in cui, per i soli alunni che abbiano
la media dei voti strettamente superiore a 24, si mostri voto minimo e voto massimo.

La soluzione proposta è questa
{
	s = 0; min=9999; max=0; 
    for(i=2; i<= NF; i++) {
        s+=$i
        if ($i<min) min=$i
        if ($i>max) max=$i
     } 
     MIN[$1] = min
     MAX[$1] = max
     AVG[$1] = s/(NF-1)
}
END {
    for( a in AVG ) 
        if (AVG[a]>24)
            print a, MIN[a], MAX[a]
    }


Avendo per esempio questo input:
Alunno1 22 25 18 27 19

Non riesco a capire la parte in cui crea il vettore MIN/MAX/AVG, perchè fa MIN[$1] ? Se ho capito bene come funziona il comando, si scorre una riga per volta partendo dal secondo campo e si salva il minimo, il massimo e il valore totale. Poi per ogni riga si creano tre vettori, uno con il minimo, l'altro col massimo e l'altro ancora con la media.

anonymous_be1147
Ciao, basta ricordarsi che in AWK gli array sono associativi. Quindi, nel tuo caso, viene usato il primo campo della riga come chiave, cioè il "nome" dell'alunno (es. Alunno1).
In pratica, con quell'unica riga di input, le tre istruzioni d'assegnamento degli array sono equivalenti a queste:

MIN["Alunno1"] = 18
MAX["Alunno1"] = 27
AVG["Alunno1"] = 22,2


Alla fine non ti stamperà niente, perché la media di questo alunno è minore di 24.

Fab996
Capito, grazie. Ma i vettori vengono ordinati automaticamente? Inoltre un'altra cosa, come posso scrivere un'espressione regolare che controlli che il carattere in posizione n-esima sia per esempio una lettera maiuscola?

anonymous_be1147
"Fab996":
Ma i vettori vengono ordinati automaticamente?

No, non sono ordinati, a meno di non usare una variabile numerica per gli indici (da incrementare a ogni passaggio) per caricare i valori e il ciclo [inline]for (i; i< ..., i++)[/inline] per scansionarlo. Alcune versioni di AWK, come gawk, hanno però delle funzioni apposite per ordinare gli arrary.

come posso scrivere un'espressione regolare che controlli che il carattere in posizione n-esima sia per esempio una lettera maiuscola?

Dipende da cosa intendi per posizione, carattere e lettera (maiuscola). :)

Se per esempio con posizione n-sima intendi quella in $0 e un carattere può essere anche uno spazio, allora potresti usare un'espressione regolare come [inline]/^.{n-1}[A-Z]/[/inline], oppure (se gli spazi non vanno considerati) [inline]/^\S{n-1}[A-Z]/[/inline] (dove $n-1$ è chiaramente un numero positivo indicante la posizione) oppure anche

BEGIN {
    FS=""
}
$n ~ /[A-Z]/

(al posto di $n$ va messo il numero di posizione).

Ma il risultato potrebbe dipendere, come detto, anche dal tipo di caratteri in input e dall'implementazione di AWK che si usa.

Fab996
Grazie, però intendevo il carattere in posizione N utilizzando il comando grep, quindi senza aver a disposizione i campi.

anonymous_be1147
Ah, non avevo capito, scusa. Non cambia molto, puoi usare un'espressione regolare[nota]estesa[/nota] come [inline]^.{n}[[:upper:]][/inline], sostituendo a $n$ il numero di caratteri che precedono quello da controllare.

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.