[inizio] [indice generale] [precedente] [successivo] [indice analitico] [contributi]

157. AWK: funzioni e array

Un programma AWK può contenere la dichiarazione di funzioni definite liberamente. Queste dichiarazioni vanno fatte al di fuori delle regole normali. Il linguaggio AWK può gestire anche gli array, che comunque sono di tipo associativo.

157.1 Dichiarazione di funzioni

La dichiarazione di una funzione avviene in modo simile al linguaggio C, con la differenza che non si dichiara il tipo restituito dalla funzione, e nemmeno quello delle variabili che ricevono i valori della chiamata.

function <nome-della-funzione>( <elenco-parametri-formali> ) {
    <istruzioni>
}

La parentesi tonda aperta che introduce l'elenco dei parametri formali, deve essere attaccata alla fine del nome della funzione che viene dichiarata. L'elenco dei parametri formali è in pratica un elenco di nomi di variabili locali, che ricevono il valore dei parametri corrispondenti nella chiamata. Se una chiamata di funzione utilizza meno parametri di quelli che sono disponibili, le variabili corrispondenti ricevono in pratica la stringa nulla.

È importante osservare che non è possibile dichiarare altre variabili locali, oltre a quelle che appaiono nell'elenco dei parametri formali.

function fattoriale(x) {
    i = x - 1
    while ( i > 0 ) {
	x *= i
	i--
    }
    return x
}

L'esempio mostra la dichiarazione di una funzione ricorsiva, per il calcolo del fattoriale. Si può osservare l'istruzione return, che permette di stabilire il valore che viene restituito dalla funzione. Naturalmente sono ammissibili anche funzioni che non restituiscono un valore: queste non hanno l'istruzione return.

function somma( x, y, z, i ) {
    z = x
    for ( i = 1; i <= y; i++ ) {
        z++
    }
    return z
}

Un altro esempio può servire per comprendere la gestione delle variabili locali in una funzione. In questo caso si tratta di una funzione che calcola la somma dei primi due parametri che gli vengono forniti. I due parametri successivi, z e i, sono dichiarati tra i parametri formali per essere usati come variabili locali, e come si vede, la funzione non tiene in considerazione i valori che potrebbero trasportare.

In effetti, la funzione potrebbe utilizzare ugualmente le variabili z e i, anche se queste non fossero dichiarate tra i parametri formali. In questo modo, però, queste variabili sarebbero globali, e si potrebbero porre dei problemi di conflitti con altre variabili con lo stesso nome usate altrove nel programma.

#!/bin/awk -f
function somma( x, y, z, i ) {
    z = x
    for ( i = 1; i <= y; i++ ) {
        z++
    }
    return z
}
1 { print $1 "+" $2 "=" somma( $1, $2 ) }

Quest'ultimo esempio mostra un programma completo per ottenere la somma dei primi due campi di ogni record fornito in ingresso.

157.2 Array

Gli array di AWK sono simili agli array associativi di Perl. A seconda dell'uso che si vuole fare di questi array, ci si può anche «dimenticare» di questa particolarità di AWK, utilizzando i soliti indici numerici, che però AWK tratta come stringhe.

157.2.1 Dichiarazione e utilizzo di un array

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

a[2] = "ciao"

si assegna la stringa "ciao" all'elemento "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.

L'esempio seguente crea un array un po' strampalato, con una serie di valori senza un significato particolare:

elenco["ciao"] = "Saluti"
elenco["maramao"] = 123
elenco[3] = 345
elenco[2345] = "che bello"

Si intuisce che gli elementi di un array AWK non hanno un ordine preciso.

È importante tenere presente che non è possibile riutilizzare una variabile scalare come array, e nello stesso modo, non si può riutilizzare un array come se fosse una variabile scalare. Se si tenta di fare una cosa del genere, l'interprete dovrebbe bloccarsi con una segnalazione di errore.

157.2.2 Scandire gli elementi di un array

La scansione degli elementi di un array AWK può essere un problema, se si pensa alla sua natura. Per esempio, dal momento che facendo riferimento a un elemento che non esiste, lo si crea implicitamente, si capisce che non si può nemmeno andare per tentativi. Per risolvere il problema, AWK fornisce due strumenti: l'operatore in, e una variante della struttura di controllo for.

Per verificare che un array contenga effettivamente l'elemento corrispondente a un certo indice, si usa l'operatore in, nel modo seguente:

<indice> in <array>

Per esempio, per verificare che esista l'elemento prova[234], si può usare un'istruzione simile a quella seguente:

if (234 in prova) {
    print "L'elemento prova[234] corrisponde a " prova[234]
}

Per scandire tutti gli elementi di un array si usa la struttura di controllo for in un modo particolare:

for (<variabile> in <array>) <istruzione>

In pratica, per ogni elemento contenuto nell'array, viene eseguita l'istruzione (o il blocco di istruzioni) che segue, tenendo conto che alla variabile viene assegnato ogni volta l'indice dell'elemento in corso di elaborazione.

È chiaro che l'ordine in cui appaiono gli elementi, dipende da AWK, e in generale dovrebbe dipendere dalla sequenza con qui questi sono stati inseriti. L'esempio seguente, scandisce un array e mostra il contenuto di ogni elemento:

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

157.2.3 Cancellazione di un elemento

L'eliminazione di un elemento di un array si ottiene con l'istruzione delete:

delete <array>[<indice>]

Alcune realizzazioni di AWK sono in grado di eliminare completamente un array, se non si indica l'indice di un elemento. In alternativa, si ottiene questo risultato con la funzione split(), come si vede sotto. L'uso di questa funzione viene mostrato più avanti.

split("", <array>)

Considerato che per AWK l'eliminazione di un array è precisamente l'eliminazione di tutti i suoi elementi, si potrebbe fare anche come viene mostrato nello schema seguente:

for (<variabile> in <array>) {
    delete <array>[<variabile>]
}

157.2.4 Indici numerici e indici «nulli»

Gli indici di un array AWK sono delle stringhe, quindi, se si usano dei numeri, questi vengono convertiti in stringa, utilizzando la stringa di formato contenuta nella variabile CONVFMT. Finché si usano indici numerici interi, non sorgono problemi; nel momento in cui si utilizzano valori non interi, la conversione può risentire di un troncamento, o di un'approssimazione derivata dalla conversione. In altri termini, due indici numerici differenti potrebbero puntare di fatto allo stesso elemento, perché la trasformazione in stringa li rende uguali.

L'indice di un array potrebbe essere anche una variabile mai usata prima. In tal caso, la variabile contiene la stringa nulla. Nel caso in cui questa variabile venga poi trattata in modo numerico, incrementando o decrementando il suo valore, per creare e fare riferimento a elementi dell'array che si vogliono raggiungere con indici pseudo-numerici, bisogna tenere presente che esiste anche l'elemento con indice "". Se si tenta di raggiungerlo con l'indice "0", si fallisce nell'intento.

1 {
    riga[n] = $0
    n++
}
END {
    for ( i=n-1; i >= 0; i-- ) {
	print riga[i]
    }
}

Si intuisce che il programma AWK che si vede nell'esempio serva ad accumulare tutte le righe lette nell'array riga, e quindi a scandire lo stesso array per emettere il testo di queste righe. Se si osserva con attenzione, di capisce che la prima riga non può essere ottenuta. Infatti, la variabile n viene utilizzata subito la prima volta, quando il suo contenuto iniziale è la stringa nulla, ""; successivamente viene incrementata, e questo fa sì che quella stringa nulla venga intesa come uno zero, ma intanto, è stato creato l'elemento riga[""]. Alla fine della lettura di tutti i record, viene scandito nuovamente l'array, trattandolo come se contenesse elementi da zero a n-1. Tuttavia, dal momento che l'elemento riga[0] non esiste, perché al suo posto c'è invece riga[""] che non viene raggiunto, si perde la prima riga.

157.2.5 Trasformare una stringa delimitata in un array

È molto importante considerare la possibilità di convertire automaticamente una stringa in un array attraverso la funzione interna split().

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

In pratica, il primo parametro è la stringa da suddividere; il secondo è l'array da creare (nel caso esista già, vengono eliminati tutti i suoi elementi); il terzo, è il carattere, o l'espressione regolare, utilizzato per separare gli elementi all'interno della lista. Se non viene indicato l'ultimo argomento, viene utilizzato il contenuto della variabile FS (come si può intuire). Dal momento che questo tipo di operazione è analoga alla separazione in campi di un record, anche in questo caso, se il carattere di separazione è uno spazio (<SP>), gli elementi vengono individuati tra delimitatori composti da sequenze indefinite di spazi e tabulazioni.

Il primo elemento dell'array creato in questo modo ha indice "1", il secondo ha indice "2", e così di seguito, fino all'elemento n-esimo.

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

L'esempio che si vede crea (o ricrea) l'array elenco, con tre elementi contenenti le stringhe uno, due e tre. In pratica, è come se si facesse quanto segue:

elenco[1] = "uno"
elenco[2] = "due"
elenco[3] = "tre"

Se non c'è alcuna corrispondenza tra il carattere, o l'espressione regolare, che si utilizzano come ultimo argomento, viene creato solo l'elemento con indice "1", nel quale viene inserita tutta la stringa di partenza.

157.2.6 Array pseudo-multidimensionali

Gli array di AWK sono associativi, e sotto questo aspetto non ha senso parlare di dimensioni, in quanto è disponibile un solo indice. Tuttavia, gestendo opportunamente le stringhe, si possono gestire idealmente più dimensioni, anche se ciò non è vero nella realtà. Supponendo di voler gestire un array a due dimensioni, con indici numerici, si potrebbero indicare gli indici come nell'esempio seguente, dove si assegna un valore all'elemento ideale «1,10»:

elenco[1 "s" 10] = 123

La lettera «s» che si vede, è solo una stringa, scelta opportunamente, in modo che l'indice che si ottiene non si possa confondere con qualcosa che non si vuole. In questo caso, l'indice reale è la stringa 1s10.

AWK offre un supporto a questo tipo di finzione multidimensionale. Per farlo, esiste la variabile SUBSEP, che viene usata per definire il carattere di separazione. Questo carattere è generalmente <FS>, che si esprime in esadecimale come 0x1c, e per AWK si può usare la sequenza di escape \034.

Quando si fa riferimento a un elemento di un array, in cui l'indice sia composto da una serie di valori separati con una virgola, AWK intende che questi valori debbano essere concatenati con il contenuto della variabile SUBSEP. Per esempio,

elenco[1, 10] = 123

è come se fosse stato scritto:

elenco[1 SUBSEP 10] = 123

In generale, non è opportuno modificare il valore di questa variabile, dal momento che si tratta di un carattere decisamente inusuale, e in questo senso garantisce che non si possano formare degli indici uguali per elementi che dovrebbero essere differenti.

Per verificare se un elemento di un array del genere esiste, si può utilizzare lo stesso trucco:

(<indice-1>, <indice-2>, ...) in <array>

---------------------------

Appunti Linux 1999.09.21 --- Copyright © 1997-1999 Daniele Giacomini --  daniele @ pluto.linux.it


[inizio] [indice generale] [precedente] [successivo] [indice analitico] [contributi]