Gentoo Logo

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>
^
(questo è il separatore di campo)

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 per esempi, Parte 1 e Parte 2.
  • Le eccellenti sed FAQ di Eric Pement.
  • I sorgenti più recenti di sed si possono trovare su ftp://ftp.gnu.org/pub/gnu/sed.
  • Eric Pement inoltre ha scritto sed one-liners che chi aspira a diventare un guru di sed deve assolutamente conoscere.


Stampa

Aggiornato il 14 febbraio 2008

Oggetto: In questa parte conclusiva della serie di articoli su sed, Daniel Robbins ci dà un vero assaggio della potenza di sed. Dopo aver introdotto una serie di utili script essenziali basati su sed, ci dimostrerà alcune modalità di scripting sed radicali convertendo un file Quicken .QIF in un formato testo leggibile. Questo script di conversione non è solamente funzionale, serve anche come esempio eccellente della potenza dello scripting con sed.

Daniel Robbins
Autore

Marco Clocchiatti
Traduttore

Donate to support our development efforts.

Copyright 2001-2014 Gentoo Foundation, Inc. Questions, Comments? Contact us.