Disclaimer :
La versione originale di questo articolo è stata pubblicata da IBM
developerWorks ed è di proprietà di Westtech Information Services. Questo
documento è una versione aggiornata dell'articolo originale, e contiene
numerosi miglioramenti apportati dal Gentoo Linux Documentation team.
Questo documento non è mantenuto attivamente.
|
Sed per esempi, Parte 3
1.
Passando al prossimo livello: La manipolazione dei dati, lo stile sed
sed Muscolare
Nel mio secondo articolo, ho portato un insieme
di esempi per mostrare come lavora sed, ma pochi di essi, in realtà,
eseguivano qualcosa di realmente utile. In questo articolo finale su sed, è
il momento di cambiare rotta e mettere sed veramente all'opera. Si
mostreranno numerosi eccellenti esempi che non solo manifestano la potenza di
sed, ma fanno ad un tempo cose raffinate (e pratiche). Per esempio, nella
seconda parte dell'articolo, farò vedere come ho progettato uno script che
converte un file .QIF prodotto dal programma finanziario Intuit's Quicken in
un testo piacevolmente formattato. Ma prima di fare ciò, diamo uno sguardo ad
alcuni scripts meno complicati ma pur sempre molto utili.
Traduzione di testo
Il nostro primo script pratico converte del testo in formato UNIX-style a testo
in formato DOS/Windows. Come probabilmente sapete, i file di testo
DOS/Windows-based utilizzano un CR (ritorno a carrello) e un LF (linea a capo -
line feed) al termine di ogni riga, mentre UNIX utilizza esclusivamente un line
feed. Succede a volte di dover trasferire file di testo da sistemi UNIX a
sistemi WINDOWS, e questo script realizzerà la conversione di formato per voi.
Codice 1.1: Conversione di formato tra UNIX and Windows |
$ sed -e 's/$/\r/' myunix.txt > mydos.txt
|
In questo script, l'espressione regolare '$' designa i fine linea e la '\r'
comanderà a sed di inserirvi davanti un ritorno a carrello. Inserisce un
ritorno a carrello davanti a ciascun line feed e, immediatamente, ognilinea
sarà terminata da un CR/LF. Si noti che il '\r' sarà sostituito con un CR
solo usando la versione GNU sed 3.02.80 o seguente. Se non avete installato
ancora GNU sed 3.02.80, leggete il mio primo
articolo per istruzioni su come fare.
Non saprei dirvi quante volte io abbia scaricato script di esempio o codice
C, scoprendo che si trattava di documenti in formato DOS/Windows. Mentre molti
programmi non temono i file formattati con i caratteri CR/LF in formato
DOS/Windows, ve ne sono parecchi, invece, che lo fanno -- primo tra essi bash,
che si arresta appena incontra un ritorno a carrello. La seguente invoncazione
convertirà testo in formato DOS/Windows ad uno pulito Formato UNIX:
Codice 1.2: Convertire codice C dal formato Windows al formato UNIX |
$ sed -e 's/.$//' mydos.txt > myunix.txt
|
Il principio di funzionamento di questo script è semplice: la nostra
sostituzione di espressione regolare individua l'ultimo carattere di ciascuna
linea, che risulta essere un ritorno a carrello. Noi la sostituiamo con
nulla, provocando la cancellatura completa dall'output. Se utilizzi questo
script e osservi che l'ultimo carattere di ciascuna linea è stato cancellato,
significa che hai specificato in input un file di testo già formattato in
stile UNIX. Non avresti dovuto far nulla!
Inversione di linee
Questo è un altro piccolo, comodissimo script. Produce un'inversione delle
linee di un file, similmente al comando tac, incluso in molte distribuzioni
Linux. La denominazione "tac" può apparire un po' fuorviante, perché "tac"
non inverte la posizione dei caratteri sulla linea, ma esclusivamente la
posizione delle linee nei file (da su a giù). Usiamo "tac" sul seguente file:
Codice 1.3: File esempio |
foo
bar
oni
|
Produrremo l'output seguente:
Codice 1.4: File di output |
oni
bar
foo
|
Possiamo ottenere lo stesso risultato con l'uso del seguente script di sed:
Codice 1.5: Fare lo stesso con uno script |
$ sed -e '1!G;h;$!d' forward.txt > backward.txt
|
Questo script risulterà utilissimo se si opera in un sistema FreeBSD, che non
possiede un comando "tac". Ma lasciatemi discutere un po' su questo.
Spiegazione dell'inversione
In primis, questo script contiene tre comandi distinti, separati da punti e
virgola: '1!G', 'h' and '$!d'. Ora, è opportuno acquisire una buona
comprensione degli indirizzi utilizzati per il primo e il terzo comando. Se
il primo comando fosse '1G', il comando 'G' sarebbe applicato esclusivamente
alla prima linea. Però, avendo inserito un carattere aggiuntivo '!' -- questo
'!' nega l'indirizzo, facendo applicare il comando 'G' a tutte le linee
tranne la prima. Analogamente accade per il comando '$!d'. Se il comando
fosse '$d', si applicherebbe il comando 'd' alla sola ultima linea del file
(il '$' è una modalità semplice di indicare l'ultima linea). Invece, con il
'!', '$!d' il comando 'd' sarà applicato a tutte le linee, tranne l'ultima.
Così, abbiamo tutti compreso come funziona funziona ciascun singolo comando.
Quando eseguiamo il nostro script di inversione sul file di testo precedente,
il primo comando eseguito è 'h'. Questo comando copia il contenuto del
pattern space (un registro di memoria temporanea). Dopodichè, viene eseguito
il comando 'd', che cancella "foo" dal pattern space, in modo che non venga
stampato dopo che tutti i comandi sono eseguiti per questa linea.
Ora, linea due. Dopo che "bar" viene letto nel pattern space, il comando 'G' è
eseguito, e accoda il contenuto del hold space ("foo\n") al pattern space
("bar\n"), restituendo "bar\n\foo\n" nel pattern space attuale. Il comando 'h'
riporta il tutto in hold space per salvarlo, mentre 'd' cancella la linea dal
pattern space affinchè non venga stampata.
Per l'ultima riga "oni", viene rieseguita la stessa procedura, tranne che i
contenuti del pattern space (le tre linee in successione) sono stampati sullo
standard output.
Ora, è il momento per eseguire una qualche potente conversione di dati con
sed.
QIF: una magia di sed
Per le prossime settimane, stavo meditando di acquistare una copia di Quicken
per controllare i miei conti in banca.
Quicken è un programma finanziario molto carino, che permette di corredare il
lavoro con colori sfarzosi. Ma, dopo averci pensato, ho creduto che avrei
potuto facilmente scrivere io del software per mantenere il mio checkbook.
Dopo tutto, ho pensato, sono un programmatore di software!
Così, Ho sviluppato un progamma di controllo del conto corrente (con
awk) che determina lo stato del mio conto analizzando un file di testo
contenente tutte le mie transazioni.
Dopo qualche riflessione, compresi che potevo tener traccia di due liste
indipendenti di crediti e debiti proprio come veniva fatto da Quicken. Ma vi
era ancora una nuova caratteristica che volevo aggiungere.
Recentemente, avevo spostato i miei conti in una banca che offriva una
interfaccia online. Un giorno, notai che il sito web della mia banca mi
consentiva di scaricare le informazioni sul mio account nel formato .QIF di
Quicken.In pochissimo tempo, decisi che avrei realmente provato a convertire le
informazioni in formato testo.
Una tavola dei due formati
Prima di concentrarci al formato QIF, vediamo qui come appare il mio
file checkbook.txt:
Codice 1.6: Esempio di formato QIF |
28 Aug 2000 food - - Y Supermarket 30.94
25 Aug 2000 watr - 103 Y Check 103 52.86
|
Nel mio file, tutti i campi sono separati da uno o più caratteri di
tabulazione, con una transazione per linea. Dopo la data, il campo successivo
indica il tipo di spesa (oppure un "-" se si tratta di una voce in entrata) Il
terzo
campo indica il tipo di entrata (o "-" se si tratta di una voce in uscita).
Quindi, c'è un campo di controllo (o "-" se vuoto), un campo di riconoscimento
delle transazioni eliminate,
("Y" or "N"), un commento e un importo in dollari. Ora possiamo dare un occhio
al formato QIF. Quando osservai il mio file QIF, dopo averlo scaricato, per
mezzo di un visualizzatore di testo, questo è ciò che lessi:
Codice 1.7: File di output malformattato |
!Type:Bank
D08/28/2000
T-8.15
N
PCHECKCARD SUPERMARKET
^
D08/28/2000
T-8.25
N
PCHECKCARD PUNJAB RESTAURANT
^
D08/28/2000
T-17.17
N
PCHECKCARD SUPERMARKET
|
Dopo aver analizzato il file, non fu molto difficile ricostruire il formato --
ignorando la prima linea, il formato è come segue:
Codice 1.8: File format |
D<date>
T<transaction amount>
N<check number>
P<description>
^
|
Iniziare il processo
Quando si affronta un progetto complesso come questo con sed, non bisogna
scoraggiarsi -- sed consente di manipolare gradualmente i dati fino alla loro
forma finale. Mentre si procede, è possibile continuare a rifinire lo script
di sed affinchè l'output appaia esattamente come si desidera. Non è
necessario ottenere il comportamento corretto immediatamente al primo
tentativo.
Per cominciare, creai un file chiamato qiftrans.sed, e iniziai a
manipolare i dati:
Codice 1.9: qiftrans.sed |
1d
/^^/d
s/[[:cntrl:]]//g
|
Il primo comando '1d' cancella la prima linea, e il secondo rimouve quegli
antipatici caratteri '^' dall'output. L'ultima linea rimuove ogni carattere
di controllo che possa comparire nel file. Siccome sto per trattare un file
in un formato estraneo, voglio evitare il rischio di incontrare qualunque
carattere di controllo strada facendo. E per ora, questo è buono. Adesso, è
il momento di aggiungere qualche istruzione di elaborazione a questo script
di base:
Codice 1.10: Improved basic script |
1d
/^^/d
s/[[:cntrl:]]//g
/^D/ {
s/^D\(.*\)/\1\tOUTY\tINNY\t/
s/^01/Jan/
s/^02/Feb/
s/^03/Mar/
s/^04/Apr/
s/^05/May/
s/^06/Jun/
s/^07/Jul/
s/^08/Aug/
s/^09/Sep/
s/^10/Oct/
s/^11/Nov/
s/^12/Dec/
s:^\(.*\)/\(.*\)/\(.*\):\2 \1 \3:
}
|
Dapprima, ho aggiunto un indirizzo '/^D/ affinchè sed inizi a processare solo
quando incontri il primo carattere del campo di dati QIF, 'D'. Tutti i comandi
tra parentesi graffe saranno eseguiti sequenzialmente non appena sia letta una
tale linea nel pattern space.
La prima linea tra parentesi graffe trasforma una linea che appare in questo
modo:
Codice 1.11: Linea iniziale prima della modifica |
D08/28/2000
|
In una siffatta:
Codice 1.12: Linea iniziale dopo la modifica |
08/28/2000 OUTY INNY
|
Naturalmente questo non è un formato perfetto, ma può andare. Noi rifiniremo
gradualmente il contenuto del pattern space via facendo. Le successive 12 linee
hanno l'effetto netto di trasformare la data in un formato a tre lettere, con
l'ultima linea che rimuove i tre slash dalla data. Alla fine, otteniamo questo
risultato:
Codice 1.13: Aspetto finale della linea |
Aug 28 2000 OUTY INNY
|
I capi OUTY e INNY sono usati come fermaposto e saranno rimpiazzati in
seguito. Io non sono in grado di specificarli fin d'ora, perché se l'importo
in dollari sarà negativo, io sostituirò OUTY e INNY con "misc" e "-", ma se
l'importo sarà positivo, li dovrò sostiuire con "-" e "inco"
rispettivamente. Fno a che l'importo non sia stato determinato, sarà
necessario mantenere i fermaposti per quanto segue.
Rifinitura
Ora, è necessaria un'ulteriore rifinitura:
Codice 1.14: Ulteriore rifinitura |
1d
/^^/d
s/[[:cntrl:]]//g
/^D/ {
s/^D\(.*\)/\1\tOUTY\tINNY\t/
s/^01/Jan/
s/^02/Feb/
s/^03/Mar/
s/^04/Apr/
s/^05/May/
s/^06/Jun/
s/^07/Jul/
s/^08/Aug/
s/^09/Sep/
s/^10/Oct/
s/^11/Nov/
s/^12/Dec/
s:^\(.*\)/\(.*\)/\(.*\):\2 \1 \3:
N
N
N
s/\nT\(.*\)\nN\(.*\)\nP\(.*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/
s/NUMNUM/-/
s/NUM\([0-9]*\)NUM/\1/
s/\([0-9]\),/\1/
}
|
Le successive sei nuove linee sono un po' complicate, e perciò saranno
descritte in dettaglio. Prima, noi abbiamo tre comandi 'N' in una riga. Il
comando 'N' indica a sed di leggere la successiva linea di output e di
accodarla al pattern space corrente. I tre 'N' consentono di concatenare le
successive tre linee al registro del pattern space attuale, in modo che ora la
nostra linea appaia in questo modo:
Codice 1.15: New look of our lines |
28 Aug 2000 OUTY INNY \nT-8.15\nN\nPCHECKCARD SUPERMARKET
|
Il pattern space di sed è diventato un po' strambo -- noi dobbiamo rimuovere
i newlinee eccedenti ed eseguire qualche formattazione aggiuntiva.Per far
questo,
useremo il coamndo di sostituzione: Il pattern da riconoscere è:
Codice 1.16: Rimuovere i fine linea eccedenti e applicarea alcune formattazioni aggiuntive |
'\nT.*\nN.*\nP.*'
|
Così viene individuato un newline, seguito da una 'T', seguito da zero o più
caratteri, seguito da un newline, seguito da una 'N', seguito da un numero
arbitrario di caratteri e un newline, seguito da una 'P', seguita da un numero
arbitrario di caratteri. Phew! Questa espressione regolare selezionerà l'intero
contenuto delle tre linee che noi accoderemo semplicemente al pattern space. Ma
noi vogliamo riformattare questa regione, non rimpiazzarla
interamente.L'ammontare in dollari, il numero di check (se presente) e la
descrizione dovranno ricomaparire nella nostra stringa di rimpiazzamento.Per
ottenere ciò, abbiamo racchiuso quelle "parti interessanti" tra parentesi con
uno backslash, in modo da poterci riferire ad esse nella stringa di
rimpiazzamento
(usando '\1', '\2', '\3' per dire a sed dove inserirle). Questo è il comando
finale:
Codice 1.17: The final command |
s/\nT\(.*\)\nN\(.*\)\nP\(.*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/
|
Con questo comando, la nostra linea è così trasformata:
This command transforms our line into:
Codice 1.18: Result of the above command |
28 Aug 2000 OUTY INNY NUMNUM Y CHECKCARD SUPERMARKET AMT-8.15AMT
|
Sebbene questa linea comincia ad apparire migliore, ci sono un paio di cosette
che a prima vista appaiono un pochinino ... meno interessanti. La prima è
quella sciocca stringa "NUMNUM" -- a che serve? Si scoprirà, appena saranno
osservate le successive due linee dello script di sed, che "NUMNUM" sarà
rimpiazzato da un "-", mentre "NUM"<number>"NUM" sarà rimpiazzato da
<number>. Come si vede, rinchiudendo il numero di controllo con un banale
tag consente di inserire convenientemente un "-" se il campo è vuoto.
Ritocchi finali
L'ultima linea rimuove una virgola che segue un numero. Questo trasforma
importi come "3,231.00" in "3231.00", che è il formato da me usato.
Ora è possibile dare uno sguardo fino allo script di produzione:
Codice 1.19: Lo script finale di produzione |
1d
/^^/d
s/[[:cntrl:]]//g
/^D/ {
s/^D\(.*\)/\1\tOUTY\tINNY\t/
s/^01/Jan/
s/^02/Feb/
s/^03/Mar/
s/^04/Apr/
s/^05/May/
s/^06/Jun/
s/^07/Jul/
s/^08/Aug/
s/^09/Sep/
s/^10/Oct/
s/^11/Nov/
s/^12/Dec/
s:^\(.*\)/\(.*\)/\(.*\):\2 \1 \3:
N
N
N
s/\nT\(.*\)\nN\(.*\)\nP\(.*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/
s/NUMNUM/-/
s/NUM\([0-9]*\)NUM/\1/
s/\([0-9]\),/\1/
/AMT-[0-9]*.[0-9]*AMT/b fixnegs
s/AMT\(.*\)AMT/\1/
s/OUTY/-/
s/INNY/inco/
b done
:fixnegs
s/AMT-\(.*\)AMT/\1/
s/OUTY/misc/
s/INNY/-/
:done
}
|
Le undici righe aggiuntive usano la sostituzione e alcune istruzioni di salto
(branch) per perfezionare l'output. Rivediamo un attimo la prima di esse:
Codice 1.20: Controllo attento della prima linea |
/AMT-[0-9]*.[0-9]*AMT/b fixnegs
|
La linea contiene un comando di branch, che è nel formato "/regexp/b
etichetta" Se il patern space soddisfa l'espressione regolare, sed eseguirà
un salto all'etichetta ":fixnegs" del listato. Se la regexp non coincide, il
processo proseguirà normalmente con il comando successivo.
Ora che abbiamo compreso il funzionamento del comando in sè, diamo uno
sguardo ai salti. Se osserviamo le espressioni regolari di salto, si vedrà
che ricercano la stringa 'AMT', seguita da un '-', seguito da un numero
arbitrario di cifre, un '.', una successione di cifre e 'AMT'. Come
sicuramente avrete intuito, questa regexp agisce specificamente sugli importi
negativi. In precedenza, il nostro importo era stato racchiuso in una stringa
'AMT', in modo da poterlo facilmente ritrovare in seguito. Poiché
l'espressione regolare riconosce esclusivamente importi in denaro preceduti
da un '-', il nostro salto avverrà solamente se stiamo trattando con un
debito. Ma se stiamo manipolando un debito, OUTY dovrà essere impostato a
'misc', INNY dovrà essere '-' e il segno negativo davanti all'importo in
denaro dovrà essere rimosso. Controllando il codice, si verifica che è
esattamente ciò che accade. Se il salto non è eseguito, OUTY sarà rimpiazzato
con un '-', e INNY sostituito da 'inco'. Abbiamo finito! La nostra linea di
output ora è perfetta.
Codice 1.21: The perfect output line |
28 Aug 2000 misc - - Y CHECKCARD SUPERMARKET -8.15
|
Non lasciatevi confondere
Come potete vedere, manipolare dati con l'uso di sed non è così difficile,
purché si affronti il problema in progressione incrementale. Non si cerchi di
realizzare ogni cosa con un singolo comando, o tutto in un colpo. Invece,si
ricerchi gradualmente la propria strada verso l'obiettivo, lavorando
continuamente a migliorare lo script di sed fino a quando l'output non assuma
l'aspetto desiderato. Sed sistema un sacco di cose, e io spero che vi divenga
familiare nei suoi segreti meccanismi e che possiate continuare a sviluppare la
vostra abilità nell'uso di sed!
-
Altri articoli di Daniel da developerWorks: Con titolo comune: Sed by
example, Part 1 and Part 2.
-
Le eccellenti sed
FAQ di Eric Pement's.
-
I sorgenti di sed 3.02 si possono trovare su
ftp://ftp.gnu.org/pub/gnu/sed.
-
Il nuovo sed 3.02.80, invece,si può trovare su
ftp://alpha.gnu.org.
-
Eric Pement inoltre ha scritto sed
one-liners che chi aspira a diventare un guru di sed deve
assolutamente conoscere.
-
Dare una ripassata a using regular
expressions per trovare e modificare i pattern in un testo in
questo libero, dW-exclusive tutorial.
-
In ultimo. Questo link, in origine, aveva un interesse storico notevole
(7th edition
UNIX's sed man page (circa 1978!). Attualmente non funziona più ed
è stato rimosso dalla documentazione ufficiale. Ma siccome gli autori lo
hanno sostituito con una meravigliosa dichiarazione d'amore, il
traduttore si è preso licenza di mantenerlo ugualmente.
|