PCRE, il superpotere di -P
Con -P grep parla la lingua delle regex di Perl: lookahead,
lookbehind, non-greedy, classi abbreviate. È il livello che permette di
estrarre testo con precisione chirurgica — quando ti serve, non c'è
sostituto.
Cosa aggiunge PCRE
PCRE sta per Perl Compatible Regular Expressions: la sintassi di regex usata in Perl, in PHP, in Python (con piccole differenze) e in molti altri linguaggi. Rispetto a ERE aggiunge cose che non puoi fare altrimenti:
| simbolo | significato |
|---|---|
| \d \w \s | cifra, carattere "word", spazio (e le maiuscole sono i complementari) |
| \b | confine di parola (come \< e \> insieme) |
| *? +? ?? | versione non-greedy: matcha il meno possibile |
| (?=...) | lookahead positivo: "seguito da..." |
| (?!...) | lookahead negativo: "non seguito da..." |
| (?<=...) | lookbehind positivo: "preceduto da..." |
| (?<!...) | lookbehind negativo: "non preceduto da..." |
-P. Se ci provi, ti risponde "grep: -P not supported". La
soluzione è quella vista nella lezione 1: installa GNU grep con
brew install grep e usa ggrep -P. Su Linux è già
disponibile.
Estrarre con lookahead e lookbehind
La cosa più potente di PCRE è la coppia lookahead/lookbehind. Permettono di
richiedere che attorno al match ci sia qualcosa senza includerlo nel
match stesso. Esempio canonico: estrarre il valore di una variabile
ignorando il prefisso chiave=.
$ cat > .env <<'EOF' DB_HOST=localhost DB_PORT=5432 DB_USER=admin API_KEY=sk-1234-abcd-5678 EOF # solo il valore di API_KEY, senza il prefisso $ grep -oP '(?<=^API_KEY=).*' .env sk-1234-abcd-5678 # con ERE avresti dovuto fare due passaggi (grep + cut o sed): $ grep '^API_KEY=' .env | cut -d= -f2
La sintassi (?<=^API_KEY=).* dice: "matcha .*, ma solo
se preceduto da ^API_KEY=". Il prefisso resta fuori dal match.
Combinato con -o, abbiamo direttamente il valore puro.
Le classi abbreviate \d, \w, \s rendono i
pattern molto più leggibili rispetto alle equivalenti POSIX:
# trovare numeri di telefono italiani (10 cifre, eventuali spazi) $ grep -oP '\b\d{3}[\s-]?\d{3}[\s-]?\d{4}\b' rubrica.txt 340 123 4567 02-1234567 3331234567 # non-greedy: contenuto tra parentesi quadre, ma solo il più corto $ echo "[INFO] [main] avvio" | grep -oP '\[.*?\]' [INFO] [main] # greedy avrebbe restituito tutta [INFO] [main]
Il lookahead (?=...) è altrettanto utile per filtri condizionali:
$ grep -P '^(?=.*ERROR)(?!.*timeout).*$' app.log 10:00:09 [ERROR] utente=bob db locked 10:01:12 [ERROR] utente=alice permission denied
I due lookahead nello stesso pattern fanno un "AND" tra condizioni indipendenti
sulla stessa riga: contiene ERROR AND NON contiene timeout. Senza
PCRE servirebbero due grep concatenati: grep ERROR | grep -v timeout.
Estrarre stringhe tra delimitatori senza i delimitatori
Il pattern più riusabile di tutto PCRE in grep è probabilmente questo: prendere ciò che sta tra due delimitatori, ma senza i delimitatori stessi. Vale per virgolette, parentesi, tag HTML, marker custom… ed è una richiesta che torna di continuo.
$ cat > codice.py <<'EOF' nome = "Alice" saluto = "Ciao mondo" print(f"Benvenuta {nome}") EOF # tutto ciò che sta tra "..." (senza le virgolette) $ grep -oP '(?<=")[^"]+(?=")' codice.py Alice Ciao mondo Benvenuta
Letto a voce: "qualcosa che non sia una virgoletta, ripetuto una o più volte, preceduto e seguito da una virgoletta". Lo stesso schema funziona per qualunque coppia di delimitatori: cambia solo cosa ci metti dentro.
# dentro [parentesi quadre] $ echo "[INFO] avvio [bob] sessione [123]" | grep -oP '(?<=\[)[^\]]+(?=\])' INFO bob 123 # dentro un tag <title> $ echo '<title>La mia pagina</title>' | grep -oP '(?<=<title>).*?(?=</title>)' La mia pagina # dentro backtick di markdown $ grep -oP '(?<=`)[^`]+(?=`)' README.md
(?<=APERTURA)CONTENUTO(?=CHIUSURA) con grep -oP.
Salvalo da qualche parte: lo userai più volte di quanto credi.
Quando i delimitatori sono multi-carattere o le strutture annidate, PCRE inizia a
mostrare i suoi limiti — lì il giusto strumento diventa sed, oppure
un parser vero. Ma per il 90% delle estrazioni quotidiane il lookaround di grep è
più che sufficiente.