Successivo: Programma wc, Precedente: Programma tee, Su: Cloni [Contenuti][Indice]
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 la sezione Elaborare opzioni specificate sulla riga di comando)
e la funzione di libreria join()
(vedi la sezione Trasformare un vettore in una sola stringa).
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 la sezione Funzioni di manipolazione di 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) }
Incidentalmente, questo programma non segue la convenzione, raccomandabile, di assegnare alle variabili globali un nome con la lettera iniziale maiuscola. Se lo si fosse fatto, il programma sarebbe stato un po’ più semplice da comprendere.
Successivo: Programma wc, Precedente: Programma tee, Su: Cloni [Contenuti][Indice]