Gli atomi: caratteri, ancore, parentesi quadre

Un’espressione regolare è una sequenza di atomi. La maggior parte dei caratteri vale per sé stessa; alcuni hanno un significato speciale. Il punto . corrisponde a un carattere qualsiasi, quindi .c si trova sia in abcd sia in a1cd, mentre bc solo nella prima. Il circonflesso ^ àncora l’inizio della riga e il dollaro $ la fine: cd si trova in abcd ma ^cd no, e ab$ non si trova in abcd. Altrove sono caratteri ordinari.

Le parentesi quadre delimitano un unico atomo che corrisponde a uno qualunque dei caratteri elencati: [1b] si trova in entrambe le stringhe di prima. Con ^ subito dopo la quadra aperta il senso si rovescia, e [^1b] corrisponde a qualsiasi carattere tranne quei due. Dentro le quadre puoi scrivere intervalli come [0-9] o [a-z], ma gli intervalli dipendono dal locale, quindi su un sistema configurato in modo insolito [a-z] può comportarsi in modo inatteso.

Più solide sono le classi POSIX: [:alpha:], [:digit:], [:alnum:], [:upper:], [:lower:], [:space:], [:blank:], [:punct:], [:xdigit:]. Esistono solo dentro una bracket expression, quindi le quadre sono sempre due:

grep '[[:digit:]]' file.txt     corretto
grep '[:digit:]' file.txt       sbagliato: cerca i caratteri : d g i t

È l’errore più frequente dell’obiettivo, e in una domanda a completamento non c’è nessuno a correggerti.

BRE ed ERE: la stessa cosa scritta in due modi

POSIX definisce due dialetti: espressioni regolari di base (BRE) ed estese (ERE). Non cambiano le funzioni, cambia chi ha bisogno del backslash: in BRE i metacaratteri avanzati sono letterali e vanno protetti, in ERE sono attivi di default.

                    ERE          BRE
zero o più          a*           a*
uno o più           a+           a\+
zero o uno          a?           a\?
esattamente 3       a{3}         a\{3\}
almeno 2            a{2,}        a\{2,\}
da 2 a 4            a{2,4}       a\{2,4\}
alternativa         he|him       he\|him
raggruppamento      (ab)+        \(ab\)\+
back reference      \1           \1

L’asterisco è l’unico quantificatore identico nei due dialetti. Quantificatori e limiti si applicano solo all’atomo che li precede: un limite da 2 a 4 scritto dopo xyz riguarda la sola z, non l’intera sequenza. E quando due corrispondenze partono dallo stesso punto vince la più lunga.

Le forme BRE con backslash per il più, il punto interrogativo e l’alternativa sono estensioni GNU: su Linux funzionano ovunque, ma il codice portabile usa ERE.

Chi parla quale dialetto: grep e sed di default usano BRE; grep -E (e il vecchio egrep) e sed -E usano ERE; grep -F (e il vecchio fgrep) non interpreta nulla e cerca la stringa letterale, comodo quando il testo contiene punti o asterischi veri. egrep e fgrep sono ancora nel programma d’esame ma da GNU grep 3.8 stampano un avviso di obsolescenza. La pagina di riferimento è man 7 regex.

grep: le opzioni che vengono chieste

$ grep -v '^#' /etc/services            righe che NON iniziano con #
$ grep -c '^options' alsa-base.conf     conta le righe corrispondenti
$ grep -n -i errore /var/log/messages   numero di riga, senza distinguere maiuscole
$ grep -r -l 'Listen' /etc              elenca solo i file che contengono il testo
$ grep -E -H -C 1 '3d (modeling|printing)' guida.md

Il file di log dell’esempio cambia famiglia: su Red Hat e Fedora è /var/log/messages, su Debian e Ubuntu è /var/log/syslog.

Da tenere a mente: -i ignora maiuscole e minuscole, -v inverte la selezione, -c conta, -n numera, -l mostra solo i nomi dei file, -o solo la porzione corrispondente, -r scende ricorsivamente, -w impone la parola intera, -f FILE legge i pattern da un file, -A, -B e -C aggiungono righe di contesto. L’opzione -H merita un avviso: il nome del file compare da solo quando i file sono più di uno, ma non con un file singolo, quindi con find -exec grep va aggiunta a mano. Con find -print0 serve invece -z.

sed: indirizzi, cancellazioni, sostituzioni

sed è un editor di flusso: non mostra nulla sullo schermo, applica istruzioni riga per riga. Un’istruzione è una lettera, eventualmente preceduta da un indirizzo, che può essere un numero di riga, un intervallo separato da virgola, il dollaro per l’ultima riga o un’espressione regolare fra due barre.

$ sed '1d' elenco.txt                cancella la prima riga
$ sed '1,7d' elenco.txt              cancella dalla prima alla settima
$ sed '/^#/d' /etc/services          cancella le righe di commento
$ sed -n '/^options/p' modulo.conf   stampa SOLO le righe corrispondenti
$ sed -e '1,7d' -e '11d' elenco.txt  due istruzioni, due -e
$ sed -f script.sed elenco.txt       istruzioni lette da file

L’opzione -n spegne la stampa automatica di ogni riga; da lì in poi stampa solo ciò che chiedi con p. È la coppia -n più p che rende sed un sostituto di grep.

L’istruzione più usata resta la sostituzione:

$ sed 's/hda/sda/' fstab.bak       solo la PRIMA occorrenza di ogni riga
$ sed 's/hda/sda/g' fstab.bak      tutte le occorrenze
$ sed -i.bak 's/hda/sda/g' fstab   modifica il file sul posto, con backup
$ sed 's|/usr/local|/opt|g' lista  separatore diverso dalla barra
$ sed -e 's/.* \(.*\)$/\1/' lista  tiene solo l'ultimo campo della riga

Nel testo di sostituzione il carattere & rappresenta tutto ciò che ha corrisposto, mentre \1 e \2 richiamano i gruppi catturati, che in BRE si scrivono con \( e \). Ricorda che -i e -E sono estensioni GNU e che -i riscrive il file senza possibilità di annullare: provalo prima senza, poi passagli un suffisso di backup.