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 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: Programma wc, Precedente: Programma tee, Su: Cloni [Contenuti][Indice]