7 manipolazione testo bash con awk, sed e regex

In questa lezione affronteremo la manipolazione delle stringhe in bash, partiamo con l’ottenere la lunghezza di una stringa. Le stringhe possono essere considerate come un array dove ogni carattere corrisponde ad un indice.

my_var="0123456789"
echo "La lunghezza di ${my_var} è: ${#my_var} caratteri"

Per ottenere la lunghezza è stato sufficiente aggiungere un cancelletto ( # ).

Se vogliamo estrarre una parte della stringa dalla posizione 4 seguiranno il nome i due punti e il numero dell’indice

echo "Substring di $my_var partendo dalla posizione 4: ${my_var:4}"

Se voglio estrarre un certo numero di caratteri dopo una certa posizione aggiungo nuovamente i due punti e il numero di quanti caratteri voglio la stringa

echo "Substring di $my_var di lunghezza 4 partendo dalla posizione 2: ${my_var:2:4}"

Per rimuovere caratteri all’inizio della stringa aggiungiamo il cancelletto dopo il nome seguito dai caratteri che si vuole rimuovere, mentre per rimuoverli dalla fine uso il simbolo percentuale ( % )

echo "Rimuovo i caratteri 0 1 2 dall'inizio ${my_var#012}"
echo "Rimuovo i caratteri 6 7 8 9 dalla fine ${my_var%6789}"

Possiamo sostituire i caratteri di una stringa aggiungendo uno slash seguito dal pattern identificatore e la stringa nuova con cui vogliamo sostituire quel caratteri

my_name="Dan"
echo "Il mio nome è ${my_name/Dan/Faghy}"

Possiamo usare anche le espressioni regolari

echo "Il mio nome è ${my_name/[0-9][0-9][0-9]/Pippo}"

Sostituisco la stringa se composta da 3 numeri, il pattern [0-9] corrisponde a un numero, quindi nel precedente codice non viene applicata nessuna modifica.

echo "Il mio nome è ${my_name/[A-Z][a-z][a-z]/Pippo}"

In questo caso la sostituzione viene applicata perché [A-Z] corrisponde ad un carattere maiuscolo, mentre [a-z] ad uno minuscolo.

Queste sostituzioni sostituirebbero solo la prima occorrenza trovata nella variabile, se noi cogliamo eliminare un pezzo di stringa basta racchiudere il pattern da eliminare tra slah ( / )

longstring="Questa questa questa è una variabile con stringa lunga"
echo "Stringa lunga: $longstring"
echo "Stringa lunga quasi corretta: ${longstring/questa /}"
echo "Stringa lunga corretta: ${longstring//questa /}"

Nella stringa lunga quasi corretta elimino la prima stringa “questa” che incontro, in quella corretta elimino ogni “questa” che incontro grazie al doppio slash ( // ).

Ora cancello un pezzo di stringa all’inizio col cancelletto e alla fine con la percentuale

echo "Rimpiazzo l'inizio della stringa con niente: ${longstring/#Questa questa questa è /}"
echo "Rimpiazzo la fine della stringa con niente: ${longstring/%stringa lunga}"

sed

sed è un programma che permette la manipolazione del testo, viene usato solitamente per utilizzare sostituzioni tramite espressioni regolari sui files. Grazie a pochi comandi si possono effettuare una marea di sostituzioni nei files, anche in un intera installazione di WordPress, per esempio.

creiamo questo file using_sed.txt

a@test
b@test
c@test
d@test
e@test
f@test
h@test
i@test

Per utilizzare sed in maniera non distruttiva occorre utilizzare dopo il comando sed la lettera s che sta per sostitution, seguita dal pattern e infine il nome del file

sed s/^[a-z]/test/ using_sed.txt

Il simbolo esponenziale ( ^ ) identifica l’inizio di una stringa poi si identifica un carattere alfabetico minuscolo ( [a-z] ) che poi andremo a sostituire con la parola test. Nel nostro esempio ogni riga diventerà test@test.

Con il simbolo del dollaro identifichiamo invece la fine di ogni riga, aggiungiamo .com alla fine di ogni riga

sed s/$/\.com/ using_sed.txt

il punto essendo un carattere speciale nelle regex, deve essere preceduto da back slash per indicare che vogliamo utilizzarlo come stringa.

In questi esempi, il file non viene modificato, per applicare le modifiche dobbiamo utilizzare l’opzione -i ( in place ).

sed -i s/$/\.com/ using_sed.txt

per altri sistemi come in macOS aggiungere anche -e execute per toglier l’errore di invalid command

sed -i -e s/$/\.com/ using_sed.txt

L’intervallo tra parentesi quadre che abbiamo definito è un range di caratteri. Se volessimo sostituire questo intervallo di mail a@test.com con nulla potremmo definire tramite le espressioni regolari

/^[a-z]@[a-z]

Il primo carattere all’inizio seguito dal simbolo chiocciola, seguito da un carattere

\+

con \+ identifico una o più istanze della definizione del precedente carattere

/^[a-z]@[a-z]\+

aggiungo .com al mio pattern e aggiungo l’opzione -r ( oppure -E in macOS ) per usare la forma estesa delle espressioni regolari

sed -r s/^[a-z]@[a-z]+.com// using_sed.txt

Possiamo usare le back reference per preservare una porzione della stringa identificata che vogliamo sostituire, facendo uso dei gruppi ovvero racchiudendo la stringa in parentesi tonde precedute dallo slash ( \( \) ). Nel nostro esempio vogliamo riportare le stringhe alla versione originale rimuovendo il .com dalla fine di ognuna

sed -E s/^\([a-z]@[a-z]+\).com// using_sed.txt

Questo \([a-z]@[a-z]+\) è il nostro gruppo 1, quindi richiamiamo con doppio back slash il gruppo 1 ( \\1 ) se avessimo un altro gruppo dopo sarebbe il 2 richiamato con \\2, un eventuale successivo con \\3 e così via

sed -E s/^\([a-z]@[a-z]+\).com/\\1/ using_sed.txt

Per salvare il file aggiungere l’azione -ri o -Eie su macOS

sed -Eie s/^([a-z]@[a-z]+).com/\1/ using_sed.txt

Per ottenere una lista completa dei caratteri di escape, quantificatori, range e altri simboli e sintassi usate dalle espressioni regolari e sed, si può visitare vim regex

awk

Possiamo utilizzare sed nei nostri script per automatizzare la sostituzione di testo su più files in maniera metodica, ma per aumentare il numero di opzioni di manipolazione testuale a nostra disposizione, possiamo utilizzare il comando awk.

Awk ci permette di default di stampare nella console ogni stringa separata da uno spazio .

cp using_sed.txt using_awk.txt
sed -ie s/$/.com/ using_awk.txt
sed -Eie s/^/email\ address:\ / using_awk.txt

abbiamo copiato il contenuto nel nuovo file using_awk.txt, abbiamo aggiunto .com alla fine di ogni rigo e aggiunto all’iniziane email address:, possiamo stampare con il comando cat il contenuto del file

cat using_awk.txt

oppure utilizzando anche awk stampare solo la prima stringa che precede il primo spazio

cat using_awk.txt | awk '{print $1}'

verrebbero stampate le righe con solo la stringa email

cat using_awk.txt | awk '{print $2}'

verranno stampate le righe con address e con awk ‘{print $3}’ solo la terza stringa che precede uno spazio, quindi gli indirizzi email.

Con awk ‘{print $0}’ stampo tutte le righe, quindi il file completo nel nostro caso.

Tramite awk possiamo utilizzare il matching dei pattern, per esempio se una riga contiene a@test.com come terza stringa precedente gli spazi, allora stampo l’intera riga

cat using_awk.txt | awk '$3 == "a@test.com" {print $0}'

Il separatore dei campi non deve essere necessariamente uno spazio, possiamo definire un altro carattere con l’opzione -F per cambiare separatore delle stringhe

cat using_awk.txt | awk -F ':' '{print $2}'

Cambiare il separatore è particolarmente utile per i files separati da virgole come i comma-separated values ( .CSV) o in tutte quelle necessità in cui bisogna estrarre dati che sono metodicamente separati da un preciso carattere. Awk è un programma a tutti gli effetti e se approfondito può venirci in aiuto in varie situazioni senza dover imparare perl .

È anche molto potente il comando cat che molto immediato nell’uso, permette di definire un carattere di delimitazione, quindi suddividere il testo usando tale carattere permettendo di suddividere l’output con caratteri numerici per esempio. In certe occasioni è più veloce utilizzare cat piuttosto che awk per fare la medesima operazione