Oggi vedremo come manipolare un testo, e in particolare un file di sottotitoli… utilizzerò questo punto di partenza per introdurvi a due potentissimi strumenti tipici del mondo GNU/Linux (e Unix più in generale).
L’idea iniziale era di scrivere un unico articolo che trattasse regex, sed e awk tutti assieme! L’articolo stava però diventando mastodontico! Quindi qui dentro vi linkero gli articoli che introducono regexp, sed e awk!
Al momento in cui scrivo l’articolo sulle regular expression e su sed sono finiti e saranno quindi già linkati! Sto ancora lavorando sull’articolo di awk (più lungo e complesso) che arriverà presto, impegni permettendo…
Qui vi proporrò un esempio pratico che funge da “antipasto” per le prossime guide: un file dei sottotitoli “srt” non conforme allo standard “srt”, e quindi con dei difetti da sistemare…
Questi difetti sono 2(+1):
- ad ogni riga del file ne segue una vuota che vogliamo eliminare (il che significa voler eliminare tutte le righe pari del file)
- il formato tempo non è corretto, manca una virgola tra i secondi e i millesimi di secondo! Vogliamo aggiungerla automaticamente
- risincroniziamo il file dei sottotitoli con il video: questo non è un problema di conformità allo standard srt ne un problema che ho risolto con sed o awk; l’ho aggiungo per completezza al fine di mostrare come risincronizzare un file di sottotitoli desincronizzato con l’audio…
Partiamo da un testo fatto così:
e ne vogliamo uno così:
Ovviamente ha poco senso per file così corti: immaginate di avere un file con migliaia di righe da sistemare! A mano diventa improponibile… ecco dove entrano in scena awk e sed.
Per il punto uno useremo “awk”, per il punto due “sed” e per il punto 3 “gaupol” (uno dei software per manipolare i sottotitoli che potete trovare in Linux) anche se non ci siano particolari vincoli (avremmo potuto fare tutto sia con sed che con awk)
Supponiamo che il file iniziale si chiami “sottotitoli.srt”: aprite un terminale nella cartella dove c’è il vostro file
Nota: mi scuso per aver utilizzato delle immagini ma con wordpress non riuscivo a mantenere le corrette spaziature e righe vuote!! (venivano automaticamente eliminate) se volete provare potete trovare il primo file (copia-incollabile) qui, e il risultato qui.
Rimuovere tutte le righe pari:
cat sottotitoli.srt | awk 'NR%2==1' > sottotitoli-dispari.srt
Spiegazione:
Così avrete creato il file “sottotitoli-dispari.srt” che, come potete verificare, manterrà le sole righe dispari, o se preferite sarà privo di tutte le righe pari.
Il comando “cat” non fa altro che leggere e stampare il file (provate)
con il simbolo ‘|’ mandiamo il file al comando dopo, in questo caso awk
la stringa tra apici ‘NR%2==1
‘ significa “(se) resto di (numero-di-riga / 2) è uguale a 1″: come sapete se il resto della divisione per 2 è 1 allora il numero che è stato diviso è dispari.
per ‘awk’ questo significa: quando la condizione è verificata allora stampa il risultato, altrimenti scartalo: quindi scarteremo tutte le righe pari e manterremo le dispari! Se aveste messo uno zero invece dell’uno potevate stampare le sole righe pari e con lo stesso metodo si può scartare una riga ogni 4 o ogni 7 a piacimento.
Infine con ‘> sottotitoli-dispari.srt
‘ diciamo semplicemente di mettere il risultato in un nuovo file chiamato ‘sottotitoli-dispari.srt’ anziché stamparlo a video.
Inseriamo la virgola dove necessario
sed -e 's/[0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}/&,/g' sottotitoli-dispari.srt > sottotitoli-convirgola.srt
Spiegazione:
il comando sed elabora dei testi: quel che facciamo qui è eseguire uno script (opzione -e) che definiamo tra gli apici.
Nel nostro caso lo script applica una sostituzione sfruttando le espressioni regolari, per comprendere facciamo qualche esempio…
Questo istruzione qui sotto sostituisce tutte le occorrenze di “itaglia” con “Italia”, o comunque nel file di ingresso
s/itaglia/Italia/g
Senza la “g” finale avrebbe sostituito solamente la prima occorrenza di “itaglia” per ogni riga. Niente che non potete fare con qualunque editor, per ora 🙂
Invece questo sostituisce ogni occorrenza di “Linux” o “linux” con “GNU/Linux” o “GNU/linux” a seconda di come sia il primo 🙂
sed -e 's/[Ll]inux/GNU\/&/g' esempio.txt
Qui sono state introdotte un po’ ci cose:
prima di tutto la barra “/” di GNU/Linux è un carattere speciale quindi perché venga proprio considerato come “/” e stampato dobbiamo anteporgli quest’altra barra “\”: “\/
” verrà stampato come “/”. Visto che il comando si aspetta quacosa con 3 slash tipo ‘s/testo/altro testo/’ se noi inseriamo un altro slash (/) lì dentro non saprebbe più che fare: se però inseriamo ‘\/’ sà esattamente che quella va considerata come testo
Stavolta la stringa ricercata è un po’ strana: “[Ll]inux”. Questo significa semplicemente: cerca le parole “Linux” e “linux”, le parentesi quadre dicono che il primo carattere può essere sia L maiuscolo che l minuscolo. Questa è un’espressione regolare di base, anche chiamata regex, verranno introdotte meglio qui sotto.
L’ultima stranezza di questo esempio è la stringa (testo) di sostituzione che presenta una e-commerciale (&): essa significa: metti al posto della & il testo trovato, così se il testo trovatè è “Linux” verrà mantenuto “Linux” così come se il testo trovato è “linux” resterà “linux”.
eccone un esempio…
prima:
questo è un esempio di cosa si può fare con i tool di Linux il famoso software free! Avete visto la potenza del terminale in linux? E questo è niente!!
dopo:
questo è un esempio di cosa si può fare con i tool di GNU/Linux il famoso software free! Avete visto la potenza del terminale in GNU/linux? E questo è niente!!
Avrete intuito che “s/[0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}/&,/g
” va in qualche modo a cercare le stringhe fatte così: “12:34:56
” e vi aggiunge una virgola alla fine.
Per comprendere com’è costruita quella stringa e per permettervi di scrivere voi stessi un espressione di regex vi serve una piccola introduzione alle espressioni regolari
Qualche nota in più su awk e sed
Se avete un testo da monipolare questi due comandi sono i più potenti strumenti a vostra disposizione! In particolare abbinati al linguaggio della shell di Unix/Linux (sh, bash, altro) vi permettono di fare davvero di tutto!
Qui vi introdurrò al loro utilizzo, io stesso non sono un guru ne dell’uno ne dell’altro! Quando ho bisogno di qualcosa leggo il manuale (man sed, man awk), cerco su internet e, solo successivamente, chiedo in qualche chat. Una volta introdotti dovreste essere in grado di proseguire da soli lo studio se sarete interessati o avrete bisogno, non chiedetemi supporto perché non sarei probabilmente in grado di rispondervi 🙂 [mi riservo di modificare questa frase in futuro se dovessi diventare un guru di awk/sed]. E comunque leggetevi gli articoli linkati qui e sarete il vostro supporto! Questi articoli verranno mantenuti aggiornati mano a mano che imparo nuove cose su questi strumenti…
Leggendo il contenuto di questi Link vi sarà facilitato cominciare ad utilizzare questi strumenti…
- (prerequisito ai 2 successivi) Introduzione alle espressioni regolari
- Introduzione all’uso di sed
- Introduzione all’uso di awk (in preparazione)
Operazioni sui sottotitoli srt
Per completezza, visto che l’esempio tratta un file dei sottotitoli…
Forse vi sarete chiesti perché talvolta i sottotitoli si sfalsano rispetto all’audio e le immagini sempre più proseguendo nella visione di un video!
In generale il problema è che i sottotitoli sono stati creati per un video con un certo “frame rate per second” (fps) mentre il video che voi avete davanti ne ha un altro… Questo può accadere se ad esempio ricodificate il video!
L’altra cosa che può accadere è uno sfalsamento (offset) di qualche secondo o minuto, anche qui può dipendere da diversi fattori ma in sostanza significa che il vostro video comincia prima o dopo rispetto ai sottotitoli…
In genere gli fps di un video sono 3: 23.976 fps, 25 fps, 29.97 fps.
sul mio esempio ho ipotizzato che quei sottotitoli fossero stati sincronizzati a 23.976 fps e che il video fosse a 25 fps. Ho aperto con gaupol il file e dal menù strumenti ho convertito gli fps dei sottotitoli usando “convert framerate” selezionando come fps di partenza 23.976 e di arrivo 25 🙂
Se i sottotitoli “vanno troppo in fretta” rispetto al video significa che dovete aumentare gli fps dei sottotitoli, se “vanno sempre più lenti” dovete ridurli. Gli fps di arrivo devono sempre essere quelli del vostro video, cosa che potete scoprire con il vostro software per visualizzare i video (io uso mplayer da linea di comando e me lo dice :P)
Poi ho ipotizzato che i sottotitoli anticipassero di 1 secondo il momento esatto in cui mi aspettavo comparissero, per correggerlo sempre in gaupol da menù strumenti con “shift position” ho impostato 1 secondo di offset per shiftarli tutti in avanti di un secondo!
Se avete un sottotitolo di cui non conoscete questi dati lavorate su delle copie e fate delle prove per gli fps. Quando lo sfalsamento dei sottotitoli resta costante (quindi non aumenta/diminuisce con l’avanzare del video o film) potete misurare a occhio l’offset e sistemarlo…
La cosa più difficile è capire se dovete prima sistemare l’offset e poi shiftare o meno…. in genere prima vanno sistemati gli fps e poi l’offset comunque!
21 aprile, 2011 at 10:03
E’ passato un po’ di tempo e proprio oggi guardando qui e la ho letto questo sito dove avevo postato qualche domanda, probabilmente con il metodo indicato li per li, dal gentile autore, non avevo capito a fondo il comando di esecuzione, che non era dettagliatamente spiegato.
Poi studiando in giro ora ho acquisito il metodo ottimo per questo tipo di problemi.Rispondo adesso che ne ho l’occasione per i tanti che mi leggeranno, il metodo trova le sue radici su quanto suggerito nel precedente post, che cerchero’ di spiegare esaustivamente.
Apropos di questo:
-023-01-caricabasso
_508-02+bitta
=987-03_vanga
+045_04?scotta
-123+06=bugna
La stringa risolutiva per cambiare solo il carattere prima dei nomi e’:
%s/\([0-9]\{3\}.[0-9]\{2\}\)./\1-/g
Risultato:
-023-01-caricabasso
_508-02-bitta
=987-03-vanga
+045_04-scotta
-123+06-bugna
Cerchero’ di darne una breve spiegazione.
1) Devo trovare la parte di stringa che rimane fissa finche’ non arrivo al carattere da sostituire la parte fissa la creo tra le due parentesi qui indicate \( \) quello che sta in parentesi costituisce la parte fissa
Il \ toglie significato alla ( che altrimenti costituirebbe parte dell’occorrenza da cercare.
2) Nella fattispecie con [0-9] identifico la serie di caratteri numerali che associata a \{3\} indica che il qualunque carattere numerico e’ compreso tra 0 e 9 ripetuto 3 volte, infatti con \{\} e il numero all’interno si intende quante volte i caratteri precedenti devono essere trovati aggiungo il . che identifica qualunque carattere a seguire che una volta e’ un – una volta un _ o qualunque altro esso sia. A seguire ho [0-9]\{2\} cioe’ qualunque numero ripetuto 2 volte, ho quindi indicato la parte fissa terminandola con \) a seguire abbiamo il . che identifica qualunque carattere a seguire la stringa fissa precedente, e che essendo fuori dalle parentesi \(\) e’ propio quello che intendo sostituire lo sostituiro’ con:
\1 cioe’ la stessa precisa occorrenza individuata fra \( \) ovvero l’occorrenza protetta dalle parentesi che voglio tenere fissa,a cui segue un – nella parte variabile prima rappresentata nella ricerca con il . fuori dalle parentesi ()
Il gioco e’ fatto.
Si nota che anche le parentesi graffe per essere viste come carattere speciale hanno bisogno di essere precedute dal \ che ne toglie il significato di carattere normale
2 febbraio, 2011 at 12:22
Grazie. Un articolo chiaro e da tenere a parte per una lettura maggiormente dettagliata.
Lo farò a breve.
Un grazie a parte per gli ot nei commenti che, pur non essendo incentrati su sed, sono stati interessanti quanto l’articolo.
Complimenti vivissimi.
Buon proseguo!
12 aprile, 2010 at 16:43
ciao a tutti
volevo sapere per favore
dato un file x
come faccio a sapere che questi non contiene
nessun carattere che non sia numerico??
sarei grato
grazie
17 aprile, 2010 at 1:42
grazie per il plurale
allora..
prima di tutto devo capire la tua domanda:
NON contiene Nessun Carattere … = Contiene qualche carattere …
quindi stai chiedendo come fai a sapere se un file X contiene qualche carattere non numerico
suppongo però che tu intendessi “come faccio a sapere se un file non contiene numeri?
e…
la regex è questa:
/^[^0-9]*$/
però ha poco senso fare così
conviene valutare la cosa contraria, ovvero: contiene dei numeri?
/[0-9]/
quindi:
grep ‘[0-9]’ X
se ritorna qualcosa contiene dei numeri
oppure:
sed -n ‘/[0-9]/p’
se ritorna qualcosa contiene dei numeri…
bay
20 luglio, 2009 at 23:13
[…] fa parte di una serie di articoli sui tool GNU (e quindi Linux) per manipolare […]
15 novembre, 2008 at 13:43
@Maui
grazie 🙂
in realtà qui siamo OT, dovremmo parlarne nell’articolo sulle regex
ma non importa
prendendo questo
-023-01-caricabasso
_508-02+bitta
=987-03_vanga
+045_04″scotta
-123+06=bugna
un modo per fare ciò che vuoi è il seguente:
:% s/\(.\d\{3\}\).\(\d\{2\}\).\(.*\)/\1-\2-\3/
i 3 gruppi identificano
.\d\{3\}
cioè: 1 carattere qualunque (il . significa 1 carattere qualunque) seguito da 3 cifre
\d\{2\}
2 cifre
.*
qualunque seguenza di caratteri di qualunque lunghezza
in mezzo ai gruppi, ho messo dei “.” cioè “1 carattere qualunque” e sono quelli che poi andrò a sostituire con un bel trattino
volendo si può raffinare un po’ ma il concetto è quello
poi:
#EXTINF,178 01 Superficial
#EXTINF,128 05 Silence
#EXTINF,143 07 Making
se ho ben capito qui vuoi beccare la prima sequenza numero{spazio}lettera di ogni riga
vediamo:
:% s/\(\d\) \([A-Za-z\)/\1,\2/
questo fa ciò che ho detto prima 🙂 (nota lo spazio tra i 2 gruppi)
oppure il secondo gruppo può essere
[^\d] o \a invece di [A-Za-z]
così da prendere qualunque carattere non sia un numero o qualunque carattere “lettera”
guarda qui:
http://www.softpanorama.org/Editors/Vimorama/vim_regular_expressions.shtml
si possono fare altre cose interessanti con le regex in vim (come rendere tutti minuscoli i caratteri durante una sostituzione)
😉
cosa volevi dire al vi? 😛
15 novembre, 2008 at 2:49
Bello il concetto delle Regex Grouping e finalmente uno che spiega la generazione di macro in modo semplice, elementare e efficace senza troppi casini.
Cio’ detto l’espressione :% s/\( \d\{2\}\) /\1,/g in effetti non fa altro che cancellarmi lo spazio, la seconda coppia di caratteri, e lo spazio che segue per sostituirla con un 1, cosi’ facendo perderei la numerazione progressiva del testo che e’ determinata appunto dalla coppia di caratteri della stringa su cui opero le sostituzioni.
Sono italiano ma il concetto e’ un po difficile da spiegare.
provo a esemplificare in questo modo:
diciamo che ho la sequenza seguente nel testo
-023-01-caricabasso
_508-02+bitta
=987-03_vanga
+045_04″scotta
-123+06=bugna
io voglio dire al VI con unico comando, se fosse possibile di prendere la seconda coppia di numeri mantenere quei numeri, ma i soli singoli caratteri prima e dopo la coppia, li vorrei modificare e uniformare in –
ottenendo percio’
-023-01-caricabasso
_508-02-bitta
=987-03-vanga
+045-04-scotta
-123-06-bugna
Cioe’ vorrei poter dire preso un pattern o sequenza di 4 caratteri da sostituire in cui all’interno e’ presente anche una sequenza di due caratteri numerali; vorrei che il primo e l’ultimo carattere li modifichi, i due centrali (ovvero i numeri) no
oppure sempre partendo dalla sequenza di 4 caratteri, dire mi modifichi i due centrali, ovvero i numeri e mi lasci come sono i valori di cio’ che viene prima della coppia dei numeri e di cio’ che viene dopo la coppia dei numeri.
Per capirci, nel DOS O/S il significato di lasciare i valori come sono viene fatta con il carattere ? .
Spero di non essere stato troppo complicato
tornando all’esempio di cui sopra partendo da
#EXTINF,178 01 Superficial
#EXTINF,128 05 Silence
#EXTINF,143 07 Making
vorrei dire prendi la sequenza formata da numero spazio lettera che per l’ultima riga sarebbe:
:::::7 M:::::
mi metti la ,(virgola) al posto dello spazio e il carattere prima e dopo li lasci invariati
quindi otterrei:
::::::7,M::::::
e questo per tutte le righe.
Comunque grazie per le dritte di prima, sono davvero utili.
Spesso ho visto usare il VI ma la gente lo usa senza finezze, come fosse la zappa.
Grazie ancora
io voglio dire al vi
27 settembre, 2008 at 13:38
@Maui
ma sei italiano? te lo chiedo perché ho dovuto sforzarmi per comprendere alcune frasi che mi hai scritto….
comunque penso che questo comando Vim possa far per te:
:% s/\( \d\{2\}\) /\1,/g
ho usato il grouping, un’implementazione delle regex che non ho spiegato qui…. (devo sempre completare questa guida.. un giorno ne avrò il tempo…)
allora:
:%
vuol dire “su tutto il file”
s/regex/nuovovalore/g
sostituisce quel che trova nella regex con il nuovo valore per tutte le occorrenze su ogni riga (g)
bene
ora la regex è questa:
” \d\{2\} ”
spazio, 2 cifre, spazio
ho creato un gruppo in quella regex usando \( e \) (aperta e chiusa tonda con una \ davanti perché non venga presa come carattere
diventa
“\( \d{2}\{2\}\) ”
fuori dal gruppo c’è solo l’ultimo spazio…
sostituisci quella stringa trovata con
\1,
cioè “primo gruppo” (\1) seguito da una virgola…
se servisse puoi creare più gruppi anche uno dentro l’altro, fai un po’ di prove (\1, \2, \3 e così via)
nel caso i gruppi siano uno dentro l’altro l’ordine è l’ordine con cui trovi la prima parentesi aperta (quindi la più esterna ha numero più basso)
esempio
\(esterno \(interno\)\)
\1 = esterno interno
\2 = interno
altre info utili…
le regex, in una specifica più ampia, prevedono anche condizioni ( http://www.regular-expressions.info/conditional.html ) ma vim non supporta le regex condizionali
però con vim puoi registrare macro ( http://www.pluto.it/files/ildp/traduzioni/vimhelp-it/vim70/html/usr_10.html )
il che ti permette di definire una sequenza di comandi e regex lunga quanto ti pare e riprodurla come vuoi (puoi anche cercare con una regex, modificare a mano usando l’editor)
praticamente basta se sei in Vim “modalità normale”
– premi “qr”: inizia la registrazione di una macro sulla lettera “r”
– fai quello che vuoi registrare
– premi “q!: termina la registrazione della macro
– premi “@r”. ripete la macro
– premi “200@r”: ripete 200 volte la macro
suppongo adesso avrai abbastanza carne al fuoco 🙂
Ciao,
Daniele
27 settembre, 2008 at 12:51
Pongo una domanda a cui non ho dato soluzione, con Vi ovvero l’avrei data ma non in un solo comando.
#EXTINF,178 01 Superficial
#EXTINF,128 05 Silence
vorrei ottenere il seguente risultato:
#EXTINF,178 01,Superficial
#EXTINF,128 05,Silence
Ovvero voglio porre la sostituzione in unico comando, dopo la coppia di numeri di una , al posto dello spazio
a poco vale :
:%s/[0-9]\{2\} /&,/g
perche otterrei
178 01 ,Superficial
certo poi da li potrei togliere \ , con :%s/\ ,/,/g quindi avrei il risultato
#EXTINF,178 01,Superficial
che e’ quello che cercavo.
addirittura vorrei poter partire dalla coppia di numeri [0-9] in modo che potrei ottenere lo stesso risultato anche se ci trovassimo in questo caso:
# EXTINF,178 01 Superficial
o in questo
2 #EXTINF,178 01 Superficial
o in questo
#EXTINF,178,01 superficial
Vorerei sapere se ponendo una & che rappresenta l’occorrenza trovata, posso manipolarmi di quella & i singoli caratteri del pattern prima che sia restituito
Ho anche provato a partire meno elegantemente dalle lettere con
:%s/\ [A-z]/,\[A-z\]/g
ma vedo che il pattern delle sostituzioni nella seconda parte sostituisce il testo con [A-z] non riuscendo a capire come dare o togliere significato ai caratteri spaciali quando questi si trovano nella seconda parte delle sostituzioni e non nella prima parte relativa alla ricerca.
Grazie per l’aiuto e per l’eventuale domanda esaustiva che vorrai darmi.
Maui
10 marzo, 2008 at 17:34
@per chi aspetta l’articolo su awk
chiedo scusa ma non ho più avuto tempo.. lo scriverò.. prometto spero il prossimo mese o quello dopo ancora..
intanto..
visto che qualcuno mi ha chiesto
“NR%2==1” significa
NR = numero di record letti fin ora (leggi: numero di righe)
%2 = resto della divisione per 2
A==B –> A è uguale a B ?
quindi appunto “NR%2==1”
il resto della divisione per due del numero di riga è uguale a 1 ? se si allora fai quanto segue su questa riga…
appunto.. lavora solo sulle righe dispari
2 dicembre, 2007 at 22:41
[…] sed è tipico degli ambienti GNU/Linux e Unix in generale. Prerequisito di questa guida è la conoscienza almeno basilare delle espressioni regolari, se ancora non lo avete letto o non le conoscete leggetevi prima questo articolo. Un esempio semplice di utilizzo di sed lo trovate invece qui. […]
2 dicembre, 2007 at 20:07
[…] Questo articolo sulle regexp fa parte di una serie di guide sul mio blog ed è un prerequisito per comprendere a fondo l’uso di sed e awk. Se invece volete un piccolo esempio di cosa si può fare con le regex date un occhiata a questo articolo. […]