Successivo: , Precedente: , Su: Cloni   [Contenuti][Indice]


11.2.6 Stampare righe di testo non duplicate

Il programma di utilità uniq legge righe di dati ordinati sul suo standard input, e per default rimuove righe duplicate. In altre parole, stampa solo righe uniche; da cui il nome. uniq ha diverse opzioni. La sintassi è la seguente:

uniq [-udc [-n]] [+n] [file_input [file_output]]

Le opzioni per uniq sono:

-d

Stampa solo righe ripetute (duplicate).

-u

Stampa solo righe non ripetute (uniche).

-c

Contatore righe. Quest’opzione annulla le opzioni -d e -u. Sia le righe ripetute che quelle non ripetute vengono contate.

-n

Salta n campi prima di confrontare le righe. La definizione di campo è simile al default di awk: caratteri non bianchi, separati da sequenze di spazi e/o TAB.

+n

Salta n caratteri prima di confrontare le righe. Eventuali campi specificati con ‘-n’ sono saltati prima.

file_input

I dati sono letti dal file in input specificato sulla riga di comando, invece che dallo standard input.

file_output

L’output generato è scritto sul file di output specificato, invece che sullo standard output.

Normalmente uniq si comporta come se siano state specificate entrambe le opzioni -d e -u.

uniq usa la funzione di libreria getopt() (vedi Funzione getopt) e la funzione di libreria join() (vedi Funzione join).

Il programma inizia con una funzione sintassi() e poi con una breve spiegazione delle opzioni e del loro significato, sotto forma di commenti. La regola BEGIN elabora gli argomenti della riga di comando e le opzioni. Viene usato un artificio per poter impiegare getopt() con opzioni della forma ‘-25’, trattando quest’opzione come la lettera di opzione ‘2’ con l’argomento ‘5’. Se si specificano due o più cifre (Optarg sembra essere numerico), Optarg è concatenato con la cifra che costituisce l’opzione e poi al risultato è addizionato zero, per trasformarlo in un numero. Se c’è solo una cifra nell’opzione, Optarg non è necessario. In tal caso, Optind dev’essere decrementata, in modo che getopt() la elabori quando viene nuovamente richiamato. Questo codice è sicuramente un po’ intricato.

Se non sono specificate opzioni, per default si stampano sia le righe ripetute che quelle non ripetute. Il file di output, se specificato, è assegnato a file_output. In precedenza, file_output è inizializzato allo standard output, /dev/stdout:

# uniq.awk --- implementa uniq in awk
#
# Richiede le funzioni di libreria getopt() e join()

function sintassi()
{
    print("sintassi: uniq [-udc [-n]] [+n] [ in [ out ]]") > "/dev/stderr"
    exit 1
}

# -c    contatore di righe. prevale su -d e -u
# -d    solo righe ripetute
# -u    solo righe non ripetute
# -n    salta n campi
# +n    salta n caratteri, salta prima eventuali campi

BEGIN {
    contatore = 1
    file_output = "/dev/stdout"
    opts = "udc0:1:2:3:4:5:6:7:8:9:"
    while ((c = getopt(ARGC, ARGV, opts)) != -1) {
        if (c == "u")
            solo_non_ripetute++
        else if (c == "d")
            solo_ripetute++
        else if (c == "c")
            conta_record++
        else if (index("0123456789", c) != 0) {
            # getopt() richiede argomenti per le opzioni
            # questo consente di gestire cose come -5
            if (Optarg ~ /^[[:digit:]]+$/)
                contatore_file = (c Optarg) + 0
            else {
                contatore_file = c + 0
                Optind--
            }
        } else
            sintassi()
    }

    if (ARGV[Optind] ~ /^\+[[:digit:]]+$/) {
        conta_caratteri = substr(ARGV[Optind], 2) + 0
        Optind++
    }

    for (i = 1; i < Optind; i++)
        ARGV[i] = ""

    if (solo_ripetute == 0 && solo_non_ripetute == 0)
        solo_ripetute = solo_non_ripetute = 1

    if (ARGC - Optind == 2) {
        file_output = ARGV[ARGC - 1]
        ARGV[ARGC - 1] = ""
    }
}

La funzione seguente, se_sono_uguali(), confronta la riga corrente, $0, con la riga precedente, ultima. Gestisce il salto di campi e caratteri. Se non sono stati richiesti né contatori di campo né contatori di carattere, se_sono_uguali() restituisce uno o zero a seconda del risultato di un semplice confronto tra le stringhe ultima e $0.

In caso contrario, le cose si complicano. Se devono essere saltati dei campi, ogni riga viene suddivisa in un vettore, usando split() (vedi Funzioni per stringhe); i campi desiderati sono poi nuovamente uniti in un’unica riga usando join(). Le righe ricongiunte vengono immagazzinate in campi_ultima e campi_corrente. Se non ci sono campi da saltare, campi_ultima e campi_corrente sono impostati a ultima e $0, rispettivamente. Infine, se occorre saltare dei caratteri, si usa substr() per eliminare i primi conta_caratteri caratteri in campi_ultima e campi_corrente. Le due stringhe sono poi confrontare e se_sono_uguali() restituisce il risultato del confronto:

function se_sono_uguali(    n, m, campi_ultima, campi_corrente,\
vettore_ultima, vettore_corrente)
{
    if (contatore_file == 0 && conta_caratteri == 0)
        return (ultima == $0)

    if (contatore_file > 0) {
        n = split(ultima, vettore_ultima)
        m = split($0, vettore_corrente)
        campi_ultima = join(vettore_ultima, contatore_file+1, n)
        campi_corrente = join(vettore_corrente, contatore_file+1, m)
    } else {
        campi_ultima = ultima
        campi_corrente = $0
    }
    if (conta_caratteri) {
        campi_ultima = substr(campi_ultima, conta_caratteri + 1)
        campi_corrente = substr(campi_corrente, conta_caratteri + 1)
    }

    return (campi_ultima == campi_corrente)
}

Le due regole seguenti sono il corpo del programma. La prima è eseguita solo per la prima riga dei dati. Imposta ultima al record corrente $0, in modo che le righe di testo successive abbiano qualcosa con cui essere confrontate.

La seconda regola fa il lavoro. La variabile uguale vale uno o zero, a seconda del risultato del confronto effettuato in se_sono_uguali(). Se uniq sta contando le righe ripetute, e le righe sono uguali, viene incrementata la variabile contatore. Altrimenti, viene stampata la riga e azzerato contatore, perché le due righe non sono uguali.

Se uniq non sta contando, e se le righe sono uguali, contatore è incrementato. Non viene stampato niente, perché l’obiettivo è quello di rimuovere i duplicati. Altrimenti, se uniq sta contando le righe ripetute e viene trovata più di una riga, o se uniq sta contando le righe non ripetute e viene trovata solo una riga, questa riga viene stampata, e contatore è azzerato.

Infine, una logica simile è usata nella regola END per stampare l’ultima riga di dati in input:

NR == 1 {
    ultima = $0
    next
}

{
    uguale = se_sono_uguali()

    if (conta_record) {    # prevale su -d e -u
        if (uguale)
            contatore++
        else {
            printf("%4d %s\n", contatore, ultima) > file_output
            ultima = $0
            contatore = 1    # reset
        }
        next
    }

    if (uguale)
        contatore++
    else {
        if ((solo_ripetute && contatore > 1) ||
            (solo_non_ripetute && contatore == 1))
                print ultima > file_output
        ultima = $0
        contatore = 1
    }
}

END {
    if (conta_record)
        printf("%4d %s\n", contatore, ultima) > file_output
    else if ((solo_ripetute && contatore > 1) ||
            (solo_non_ripetute && contatore == 1))
        print ultima > file_output
    close(file_output)
}

Successivo: , Precedente: , Su: Cloni   [Contenuti][Indice]