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. |
1. Come ottenere ulteriori vantaggi dall'editor di testo UNIX
Diamo un'occhiata a uno dei più utili comandi di sed, il comando di sostituzione. Con esso, possiamo sostituire una particolare stringa o un pattern specificato da una espressione regolare con un'altra stringa. Ecco un esempio di un uso base di questo comando:
Codice 1.1: Il più semplice uso del comando di sostituzione |
$ sed -e 's/foo/bar/' myfile.txt
|
Il comando sopra avrà come uscita nello stdout il contenuto del file myfile.txt, con la prima ricorrenza di 'foo' (se viene trovata) di ogni linea sostituita con la stringa 'bar'. Notare che abbiamo detto "prima ricorrenza di ogni linea", che normalmente non è ciò che si vuole. Infatti, quando si vuole fare una sostituzione, normalmente si decide di effettuarla in maniera globale. Ecco come sostituire tutte le ricorrenze in ogni linea:
Codice 1.2: Sostituire tutte le ricorrenze in ogni linea |
$ sed -e 's/foo/bar/g' myfile.txt
|
L' opzione 'g' posta dopo l'ultimo slash indica a sed di eseguire la sostituzione in maniera globale.
Ci sono alcune cose che bisogna sapere del comando di sostituzione s///. Primo, è solo un comando: non ci sono espressioni specificate in nessuno degli esempi sopra. Ciò significa che il comando s/// può essere usato con il controllo tramite espressioni in modo da regolare in quali linee deve essere applicato, esempio:
Codice 1.3: Specificare le linee dove deve essere applicato il comando |
$ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt
|
L'esempio sopra farà in modo che tutte le ricorrenze di 'enchantment' verranno sostituite con 'entrapment', ma soltanto nelle linee da 1 a 10, incluse.
Codice 1.4: Specificare più opzioni |
$ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt
|
Questo esempio cambierà 'hills' con 'mountains', ma solo nei blocchi di testo che cominciano con una linea bianca e terminano con una linea che inizia con i tre caratteri 'END', inclusivamente.
Un'altra cosa interessante del comando s/// è che ci sono molte opzioni riguardandi i separatori /. Se si sta eseguendo una sostituzione e l'espressione regolare o la stringa di sostituzione ha troppi slash, è possibile specificare un altro separatore inserendo un differente carattere dopo la 's'. Per esempio, questo sostituirà tutte le ricorrenze di /usr/local con /usr:
Codice 1.5: Replacing all the occurences of one string with another one |
$ sed -e 's:/usr/local:/usr:g' mylist.txt
|
Nota: In questo esempio, è stato utilizzato ':' come separatore. Se si deve utilizzare lo stesso carattere del separatore nelle espressioni regolari, si deve farlo precedere da un backslash. |
Fino ad ora, abbiamo soltanto seguito una semplice sostituzione. Anche se è comodo, è possibile anche usare le espressioni regolari. Per esempio, il seguente comando sed verificherà tutte le stringe che cominciano per '<', terminano per '>' e contengono un numero qualsiasi di caratteri tra di essi e le eliminerà (sostituisce con una stringa vuota):
Codice 1.6: Eliminare una frase specifica |
$ sed -e 's/<.*>//g' myfile.html
|
Questo è un buon tentativo per creare uno script sed che rimuova i tag html da un file, ma non funziona correttamente, a causa di una stranezza delle espressioni regolari. Il motivo? Quando sed prova a confrontare le espressioni regolari su una linea, ricerca le corrispondenze più lunghe su quella linea.Ciò non produceva alcun comportamento indesiderato nel mio precedente articolo su sed, perché allora venivano utilizzati i comandi d e p, che erano proprio intesi a cancellare o stampare interamente la riga. Ma, utilizzando i comando s///, si manifesta una differenza enorme, perché l'intera porzione dell'espressione regolare riconosciuta deve essere sostituita con la stringa assegnata, o, nel nostro caso, cancellata. Ciò vuol dire che, nell'esempio precedente, si cambierà la linea qui sotto:
Codice 1.7: Sample HTML code |
<b>This</b> is what <b>I</b> meant. |
In quest'altra:
Codice 1.8: Not desired effect |
meant. |
Al contrario, questo è ciò che vorremmo:
Codice 1.9: Desired effect |
This is what I meant. |
Fortunamente, esiste una tecnica semplice per risolvere ciò. Anziché inserire una espressione regolare che indica "un carattere '<' seguiti da un numero arbitrario di di caratteri, e terminante con i carattere '>', basta produrre una espressione regolare che ricerchi un numero indefinito di caratteri non-'>', conclusa da un carattere '>'. In questo modo, si ottiene il risultato di riconoscere la ricorrenza più breve dell'espressione, anziché la più estesa. Il nuovo comando apparirà in questo modo:
Codice 1.10: |
$ sed -e 's/<[^>]*>//g' myfile.html
|
Nell'esempio sopra esposto, il [^>]' specifica un carattere "non-'>'" e il '*' che segue completa l'espressione richiedendo "zero o più caratteri non-'>'.Verifica questo comando su un piccolo file html di esempio, assegna l'output in pipe a more e verifica i risultati.
Ricerca su gruppi di caratteri
La sintassi dell'espressione regolare '[ ]' presenta alcune altre opzioni.Per specificare un intervallo di caratteri si può usare un '-' esteso dalla prima all'ultima posizione, come segue:
Codice 1.11: Specificare un intervallo di caratteri |
'[a-x]*' |
In questo modo saranno riconosciuti zero o più caratteri, compresi tra 'a','b','c'...'v','w','x'. In aggiunta, la classe di caratteri '[:space:]' è utilizzabile per riconoscere gli spazi bianchi. Questa è una lista pressochè completa delle classi di caratteri disponibili:
| Classi di caratteri | Descrizione |
| [:alnum:] | Alfanumerici [a-z A-Z 0-9] |
| [:alpha:] | Alfabetici [a-z A-Z] |
| [:blank:] | Spazi o caratteri di tabulazione |
| [:cntrl:] | Caratteri di controllo generici |
| [:digit:] | Cifre decimali [0-9] |
| [:graph:] | Caratteri visibili (esclude gli spazi bianchi) |
| [:lower:] | Lettere minuscole [a-z] |
| [:print:] | Caratteri stampabili (esclude i caratteri di controllo) |
| [:punct:] | Caratteri di punteggiatura |
| [:space:] | Spazio bianco |
| [:upper:] | Lettere maiuscole [A-Z] |
| [:xdigit:] | Cifre esadecimali [0-9 a-f A-F] |
E' vantaggioso utilizzare classi di caratteri ovunque possibile, perché si adattano meglio ai linguaggi di localizzazione nonInglesi (compresi i caratteri accentati, ove necessario).
Operazioni di sostituzione avanzata
Abbiamo visto fin qui come eseguire sia sostituzioni semplici che ragionevolmente complesse, ma sed può fare anche dell'altro. Ora, ci interessemo ad espressioni regolari riconosciute interamente o in parte, e le utilizzeremo per costruire la stringa di sostituzione. Ad esempio, suppiamo di rispondere ad un messaggio. Il seguente esempio aggiungerà a ciascuna linea il prefisso "ralph said: "
Codice 1.12: Preporre a ciscuna linea una data stringa |
$ sed -e 's/.*/ralph said: &/' origmsg.txt
|
L'output apparirà come segue:
Codice 1.13: Output of the above command |
ralph said: Hiya Jim, ralph said: ralph said: I sure like this sed stuff! ralph said: |
In questo esempio, si è usato il carattere '&' nella stringa di sostituzione, che ordina a sed di inserire l'intera espressione regolare selezionata. Così, tutto ciò che è stato selezionato con '.*' (ovvero l'insieme più esteso di zero o più caratteri contenuti sulla linea - che vuol dire l'intera linea) può essere inserito ovunque nella linea di sostituzione, e ciò può essere fatto anche contemporaneamente in più punti. Questo è grande, ma sed è anche più potente.
Delle meravigliose parentesi fatte con i backslash
Ancor meglio che '&', il comando /// ci permette di definire regioni nella nostra espressione regolare, a cui possiamo fare rifermento nella stringa di sostituzione. Come esempio, supponiamo di avere un file che contenga il seguente testo.
Codice 1.14: Sample text |
foo bar oni eeny meeny miny larry curly moe jimmy the weasel |
Ora, supponiamo di voler scrivere uno script di sed che sostituisca "eeny meeny miny" con "Victor eeny-meeny Von miny", etc. Per fare ciò, scriveremo una espressione regolare che selezioni queste tre stringhe, separate da spazi:
Codice 1.15: Ricerca di espressioni regolari |
'.* .* .*' |
Dunque. Ora definiamo delle regioni inserendo parentesi di backslash per ciascuna regione di interesse:
Codice 1.16: Definire regioni |
'\(.*\) \(.*\) \(.*\)' |
Questa espressione regolare lavorerà esattamente come la nostra prima, ma riuscirà anche a definire tre regioni logiche nella nostra stringa di sostituzione. Questo è lo script finale:
Codice 1.17: Script finale |
$ sed -e 's/\(.*\) \(.*\) \(.*\)/Victor \1-\2 Von \3/' myfile.txt
|
come si vede, stiamo facendo riferimento a ciascuna regione delimitata dalle parentesi, a partire da uno. L'output sarà il seguente:
Codice 1.18: Output del comando precedente |
Victor foo-bar Von oni Victor eeny-meeny Von miny Victor larry-curly Von moe Victor jimmy-the Von weasel |
Ora che stiamo oramai acquisendo familiarità con sed, diventiamo capacità di eseguire operazioni di elaborazione di testi decisamente complesse con una discreta comodità. Riflettendo su come si potrebbe affrontare questo problema in un qualunque altro linguaggio di scripting di vostra preferenza, sapreste indicare forse uno strumento che realizzi la soluzione in una linea soltanto?
Nella creazione di script più complessi, sorge la necessità di eseguire più di un comando in succesione. Ci sono diversi modi per realizzarla. Primo, si può utilizzare il punto e virgola tra i comandi. Per esempio, questa procedura esegue il comando '=', che indica a sed di stampare il numero di linea, e successivamente il comando p, che richiede esplicitamente di stampare ls linea (poichè siamo in modalità -n):
Codice 1.19: Primo metodo, il punto e virgola |
$ sed -n -e '=;p' myfile.txt
|
Se vengono specificati due o più comandi, ciascuno sarà applicato (nell'ordine) a ciascuna linea del file. Nell'esempio sopra, prima viene applicato il comando '=' alla linea due, e poi il comando p. Quindi, sed procede alla seconda linea e ripete il processo. Sebbene il punto e virgola sia comodo, vi sono istruzioni in cui non funziona. Una seconda alternativa è l'uso di due opzioni -e per specificare i due comandi separati:
Codice 1.20: Secondo metodo, l'opzione multipla -e |
$ sed -n -e '=' -e 'p' myfile.txt
|
Anche qui, con comandi di inserimento e concatenazione complessi, anche l'opzione '-e' potrebbe non essere d'aiuto. Per complessi script multilinea, la soluzione migliore è quella di inserire i comandi necessari in un file separato. In questo caso, si fa riferimento al file separato in questo modo:
Codice 1.21: Terzo metodo, file di comandi esterno |
$ sed -n -f mycommands.sed myfile.txt
|
Questo sistema, sebbene decisamente meno conveniente, funziona sempre.
Comandi multipli per un singolo indirizzo
Talora, si potrebbe aver desiderio di specificare comandi multipli da applicare ad un singolo indirizzo. Questo accade con particolare frequenza nell'eseguire numerose trasformazioni di parole o sintassi nei file sorgente. Per eseguire comandi multipli su un indirizzo, introdurre i comandi di sed in un file, costruendo il gruppo di comandi '{ }' come segue:
Codice 1.22: Eseguire comandi multipli su di un unico indirizzo |
1,20{
s/[Ll]inux/GNU\/Linux/g
s/samba/Samba/g
s/posix/POSIX/g
}
|
L'esempio precedente applicherà tre comandi di sostituzione dalla linea 1 alla 20, estremi inclusi. È possibile anche fare uso di espressioni regolari indirizzate, o una combinazione dei due:
Codice 1.23: Combinazione di entrambi i metodi |
1,/^END/{
s/[Ll]inux/GNU\/Linux/g
s/samba/Samba/g
s/posix/POSIX/g
p
}
|
Questo esempio si applica a tutti i comandi tra '{ }' le linee inizianti per 1 fino alla linea che inizia con le lettere "END", oppure una terminazione di file se la stringa non viene rinvenuta nel file sorgente.
Accodare, inserire e modificare linee
Preparato che sia lo script di sed in un file separato, possiamo avvantaggiarci dei comandi per accodare, inserire e modificare linee. Questi comandi inseriranno la linea corrente, o la sostituiranno nel cosidetto pattern space, che è l'area attiva di memoria in cui è contenuta la riga su cui sed sta attualmente lavorando. Gli stessi possono essere anche usati per inserire linee multiple in output. Il comando di inserimento di linea è utilizzato come segue:
Codice 1.24: Uso del comando di inserimento di line |
i\ Questa linea sarà inserita prima di ciascuna linea |
Se non viene specificato un indirizzo per quest comando, sarà applicato a ciascuna linea e produrrà un output che appare come il seguente:
Codice 1.25: Output of the above command |
Questa linea sarà inserita prima di ciascuna linea linea 1 qui Questa linea sarà inserita prima di ciascuna linea linea 2 qui Questa linea sarà inserita prima di ciascuna linea linea 3 qui Questa linea sarà inserita prima di ciascuna linea linea 4 qui |
Desiderando inserire linee multiple prima della linea corrente, potrete aggiungere linee addizionali concatenando un backslash alla linea precedente, come qui:
Codice 1.26: Inserire linee multiple prima di quella corrente |
i\ inserisci questa linea\ e questa\ e questa\ e, uh, anche questa, poi. |
Il comando di concatenazione lavora analogamente, ma inserirà la linea o le linee, nel pattern space. E' usato come segue:
Codice 1.27: Concatenare linee dopo quella corrente |
a\ Inserisci questa linea dopo ogni linea., Grazie! :) |
D'altra parte, il comando di sostituzione di linea rimpiazzerà la linea corrente nel pattern space, ed è usato come segue:
Siccome i comandi di concatenazione, inserimento e modifica devono essere eseguiti su linee multiple, avrai desiderio di inserirli in appositi script di testo per sed e chiamare l'esecuzione con sed attraverso l'opzione '-f'. Utilizzare metodi alternativi per passare i comandi a sed può generare problemi.
La prossima volta, nell'articolo finale di questa serie su sed, vi mostrerò molti ottimi esempi di uso di sed in numerose situazioni operative. E non solo mostrerò cosa faccia lo script, ma perchè lo fa. Ciò fatto, sarete voi a trovare nuove splendide idee per usare sed in vari progetti. Io starò a guardare voi, allora!