GNU/Linux: trovare il testo nei file binari

Questa puntata nasce da una domanda in un commento: è facile usare grep per cercare testo nei file di testo, ma come ci si regola quando bisogna cercare testo in file binari, come i documenti docx o i PDF? La risposta non è complicata, ma ci permette di mettere in pratica alcuni strumenti molto interessanti della linea di comando.

Lo scenario

Mi sono preparato tre PDF, documento1, documento2 e documento3, con un testo semplicissimo: il primo contiene “ciao mondo”, il secondo un testo simile con ciao, il terzo un testo ancora diverso. Quindi cercando ciao dovremmo trovarlo nei documenti 1 e 2, cercando mondo solo nel documento 1. Gli stessi testi, con gli stessi nomi, sono anche in tre file docx (i PDF li ho creati proprio da quelli con LibreOffice). Ogni formato sta in una cartella a parte.

Se proviamo a usare grep direttamente sulla cartella dei PDF, non trova niente:

grep -r mondo .

Il motivo è che i PDF sono file binari e non contengono la stringa mondo in chiaro.

La strategia

Ci sono più strategie per cercare testo in un file binario. Quella che mostro è un po’ grezza, ma dimostra bene le possibilità della shell: per ogni file della cartella lo convertiamo in testo, facciamo il grep sui file di testo generati e infine li cancelliamo, visto che non servono più. E si fa tutto con un one-liner.

I PDF

Cominciamo con un ciclo for che ci dia i documenti uno per riga. Per ora ci limitiamo a stamparli con echo (se dovete lavorare in modo ricorsivo potete usare find, la logica è la stessa):

for i in $(ls *.pdf); do echo $i; done

Per convertire un PDF in testo usiamo pdftotext, che fa parte del pacchetto poppler-utils. Se non è installato:

sudo apt-get install poppler-utils

La sintassi è banale: prima il PDF, poi il file di testo da creare. Sostituiamo echo con pdftotext:

for i in $(ls *.pdf); do pdftotext $i $i.txt; done

Con ls -l vediamo che per ogni PDF ora c’è un equivalente .pdf.txt, il cui contenuto è identico al PDF ma perfettamente leggibile. Adesso possiamo cercare. Cancelliamo prima i txt creati:

rm -f *.txt

E ora tutto in una riga: per ogni PDF si converte in testo, poi (dopo il done, con il punto e virgola) si lancia il grep, e infine si cancellano i txt. Con l’opzione -r si cerca nella cartella, -l mostra solo i nomi dei file che contengono la stringa:

for i in $(ls *.pdf); do pdftotext $i $i.txt; done
grep -rl mondo .
rm -f *.txt

Il risultato è che mondo è nel documento1.pdf.txt, cioè nel documento 1 (basta sostituire mentalmente l’estensione, oppure farlo con una sostituzione di sottostringa). Se cerchiamo ciao, otteniamo i due documenti che lo contengono. Il bello è che la cartella di lavoro alla fine è completamente pulita.

I docx

Con i docx è tutto molto simile, cambia solo il modo in cui il convertitore vuole input e output. Il comando è docx2txt (si installa con apt-get install docx2txt) e vuole il file da convertire in redirezione dall’input, e il file di destinazione in redirezione dell’output:

for i in $(ls *.docx); do docx2txt < $i > $i.txt; done
grep -rl mondo .
rm -f *.txt

Anche qui grep trova il documento 1, e la cartella resta pulita. Cercando ciao otteniamo gli stessi risultati di prima.

La sfida

La mia sfida di oggi: trasformate tutto in forma ricorsiva usando find, in modo da cercare anche nelle sottocartelle. Fatemi sapere nei commenti come l’avete risolta.

La shell è potente, flessibile e anche un po’ divertente: sperimentate con quello che avete visto.

Link e risorse

Sostieni il canale

PER SOSTENERE IL CANALE BASTANO UN LIKE E UN’ISCRIZIONE!
ma se vuoi offrirmi un caffè, mi aiuterà a rimanere sveglio mentre registro i video 😁☕
clicca qui per scoprire come https://buymeacoffee.com/thelinuxnerd

questi link sono affiliati Amazon, acquistando i libri che ti consiglio sostieni il canale!