Successivo: Programma egrep, Su: Cloni [Contenuti][Indice]
Il programma di utilità cut
seleziona, o “taglia” (cut),
caratteri o campi dal suo standard input e li
spedisce al suo standard output.
I campi sono separati da caratteri TAB per default,
ma è possibile fornire un’opzione dalla riga di comando per cambiare il campo
delimitatore (cioè, il carattere che separa i campi). La definizione di
campo di cut
è meno generale di quella di awk
.
Un uso comune del comando cut
potrebbe essere quello di estrarre
i nomi degli utenti correntemente collegati al sistema, a partire
dall’output del comando who
. Per esempio, la seguente
pipeline genera una lista in ordine alfabetico, senza doppioni, degli utenti
correntemente collegati al sistema:
who | cut -c1-8 | sort | uniq
Le opzioni per cut
sono:
-c lista
Usare lista come lista di caratteri da ritagliare. Elementi all’interno della lista possono essere separati da virgole, e intervalli di caratteri possono essere separated da trattini. La lista ‘1-8,15,22-35’ specifica i caratteri da 1 a 8, 15, e da 22 a 35.
-f lista
Usare lista come lista di campi da ritagliare.
-d delimitatore
Usare delimitatore come carattere che separa i campi invece del carattere TAB.
-s
Evita la stampa di righe che non contengono il delimitatore di campo.
L’implementazione awk
del comando cut
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 un commento che descrive le opzioni, le funzioni
di libreria necessarie, e una funzione sintassi()
che stampa un
messaggio ed esce. sintassi()
è chiamato se si specificano degli
argomenti non validi:
# cut.awk --- implementa cut in awk # Opzioni: # -f lista Ritagliare campi # -d c Carattere di delimitazione di campo # -c lista Ritagliare caratteri # # -s Sopprimere righe che non contengono il delimitatore # # Richiede le funzioni di libreria getopt() e join()
function sintassi() { print("sintassi: cut [-f lista] [-d c] [-s] [file...]") > "/dev/stderr" print("sintassi: cut [-c lista] [file...]") > "/dev/stderr" exit 1 }
Subito dopo c’è una regola BEGIN
che analizza le opzioni della riga
di comando.
Questa regola imposta FS
a un solo carattere TAB, perché quello è
il separatore di campo di cut
per default.
La regola poi imposta il separatore di campo in output allo stesso valore
del separatore di campo in input. Un ciclo che usa getopt()
esamina
le opzioni della riga di comando. Una e una sola delle variabili
per_campi
o per_caratteri
è impostata a "vero", per indicare
che l’elaborazione sarà fatta per campi o per caratteri, rispettivamente.
Quando si ritaglia per caratteri, il separatore di campo in output è
impostato alla stringa nulla:
BEGIN { FS = "\t" # default OFS = FS while ((c = getopt(ARGC, ARGV, "sf:c:d:")) != -1) { if (c == "f") { per_campi = 1 lista_campi = Optarg } else if (c == "c") { per_caratteri = 1 lista_campi = Optarg OFS = "" } else if (c == "d") { if (length(Optarg) > 1) { printf("cut: usa il primo carattere di %s" \ " come delimitatore\n", Optarg) > "/dev/stderr" Optarg = substr(Optarg, 1, 1) } fs = FS = Optarg OFS = FS if (FS == " ") # mette specifica in formato awk FS = "[ ]" } else if (c == "s") sopprimi = 1 else sintassi() } # Toglie opzioni da riga di comando for (i = 1; i < Optind; i++) ARGV[i] = ""
Nella scrittura del codice si deve porre particolare attenzione quando il
delimitatore di campo è uno spazio. Usare
un semplice spazio (" "
) come valore per FS
è
sbagliato: awk
separerebbe i campi con serie di spazi,
TAB, e/o ritorni a capo, mentre devono essere separati solo da uno spazio.
Per far questo, salviamo il carattere di spazio originale nella variabile
fs
per un uso futuro; dopo aver impostato FS
a "[ ]"
non
è possibile usarlo direttamente per vedere se il carattere delimitatore di
campo è nella stringa.
Si ricordi anche che dopo che si è finito di usare getopt()
(come descritto nella Elaborare opzioni specificate sulla riga di comando),
è necessario
eliminare tutti gli elementi del vettore ARGV
da 1 a Optind
,
in modo che awk
non tenti di elaborare le opzioni della riga di comando
come nomi-file.
Dopo aver elaborato le opzioni della riga di comando, il programma verifica
che le opzioni siano coerenti. Solo una tra le opzioni -c
e -f dovrebbe essere presente, ed entrambe richiedono una lista di
campi. Poi il programma chiama
prepara_lista_campi()
oppure prepara_lista_caratteri()
per
preparare la lista dei campi o dei caratteri:
if (per_campi && per_caratteri) sintassi() if (per_campi == 0 && per_caratteri == 0) per_campi = 1 # default
if (lista_campi == "") { print "cut: specificare lista per -c o -f" > "/dev/stderr" exit 1 }
if (per_campi) prepara_lista_campi() else prepara_lista_caratteri() }
prepara_lista_campi()
pone la lista campi, usando la virgola come
separatore, in un vettore. Poi, per
ogni elemento del vettore, controlla che esso non sia un intervallo. Se è
un intervallo, lo fa diventare un elenco. La funzione controlla l’intervallo
specificato, per assicurarsi che il primo numero sia minore del secondo.
Ogni numero nella lista è aggiunto al vettore lista_c
, che
semplicemente elenca i campi che saranno stampati. Viene usata la normale
separazione in campi di awk
. Il programma lascia ad awk
il compito di separare i campi:
function prepara_lista_campi( n, m, i, j, k, f, g) { n = split(lista_campi, f, ",") j = 1 # indice in lista_c for (i = 1; i <= n; i++) { if (index(f[i], "-") != 0) { # un intervallo m = split(f[i], g, "-")
if (m != 2 || g[1] >= g[2]) { printf("cut: lista campi errata: %s\n", f[i]) > "/dev/stderr" exit 1 }
for (k = g[1]; k <= g[2]; k++) lista_c[j++] = k } else lista_c[j++] = f[i] } ncampi = j - 1 }
La funzione prepara_lista_caratteri()
è più complicata di
prepara_lista_campi()
.
L’idea qui è di usare la variabile di gawk
FIELDWIDTHS
(vedi la sezione Leggere campi di larghezza costante),
che descrive input a larghezza costante. Quando si usa una lista di
caratteri questo è proprio il nostro caso.
Impostare FIELDWIDTHS
è più complicato che semplicemente elencare
i campi da stampare. Si deve tener traccia dei campi da
stampare e anche dei caratteri che li separano, che vanno saltati.
Per esempio, supponiamo che si vogliano i caratteri da 1 a 8, 15,
e da 22 a 35. Per questo si specifica ‘-c 1-8,15,22-35’. Il valore che
corrisponde a questo nella variabile FIELDWIDTHS
è
"8 6 1 6 14"
. Questi sono cinque campi, e quelli da stampare
sono $1
, $3
, e $5
.
I campi intermedi sono riempitivo (filler),
ossia è ciò che separa i dati che si desidera estrarre.
lista_c
lista i campi da stampare, e t
traccia l’elenco
completo dei campi, inclusi i riempitivi:
function prepara_lista_caratteri( campo, i, j, f, g, n, m, t, filler, ultimo, lungo) { campo = 1 # contatore totale campi n = split(lista_campi, f, ",") j = 1 # indice in lista_c for (i = 1; i <= n; i++) { if (index(f[i], "-") != 0) { # intervallo m = split(f[i], g, "-") if (m != 2 || g[1] >= g[2]) { printf("cut: lista caratteri errata: %s\n", f[i]) > "/dev/stderr" exit 1 } lungo = g[2] - g[1] + 1 if (g[1] > 1) # calcola lunghezza del riempitivo filler = g[1] - ultimo - 1 else filler = 0
if (filler) t[campo++] = filler
t[campo++] = lungo # lunghezza del campo ultimo = g[2] lista_c[j++] = campo - 1 } else { if (f[i] > 1) filler = f[i] - ultimo - 1 else filler = 0 if (filler) t[campo++] = filler t[campo++] = 1 ultimo = f[i] lista_c[j++] = campo - 1 } } FIELDWIDTHS = join(t, 1, campo - 1) ncampi = j - 1 }
Poi viene la regola che elabora i dati. Se l’opzione -s è stata
specificata, il flag sopprimi
è vero. La prima istruzione
if
accerta che il record in input abbia il separatore di
campo. Se cut
sta elaborando dei campi, e sopprimi
è vero,
e il carattere di separazione dei campi non è presente nel record, il
record è ignorato.
Se il record è valido, gawk
ha già separato i dati in campi,
usando il carattere in FS
o usando campi a lunghezza fissa
e FIELDWIDTHS
. Il ciclo scorre attraverso la lista di campi che
si dovrebbero stampare. Il campo corrispondente è stampato se contiene dati.
Se il campo successivo contiene pure dei dati, il carattere di separazione è
scritto tra i due campi:
{ if (per_campi && sopprimi && index($0, fs) == 0) next for (i = 1; i <= ncampi; i++) { if ($lista_c[i] != "") { printf "%s", $lista_c[i] if (i < ncampi && $lista_c[i+1] != "") printf "%s", OFS } } print "" }
Questa versione di cut
utilizza la variabile FIELDWIDTHS
di
gawk
per ritagliare in base alla posizione dei caratteri. È
possibile, in altre implementazioni di awk
usare substr()
(vedi la sezione Funzioni di manipolazione di stringhe), ma
la cosa è molto più complessa.
La variabile FIELDWIDTHS
fornisce una soluzione elegante al problema
di suddividere la riga in input in singoli caratteri.
Successivo: Programma egrep, Su: Cloni [Contenuti][Indice]