Compila una volta, poi cerca
Scrivi i pattern come stringhe raw tra backtick. In una normale stringa tra doppi apici ogni backslash va raddoppiato ("\\d+"), e diventa presto illeggibile.
regexp.MustCompile va in panic se il pattern non è valido, ed è quello che vuoi per un pattern scritto nel codice sorgente: un errore di battitura fallisce subito all'avvio, non alla prima richiesta. Per i pattern che arrivano dagli utenti o dalla configurazione usa regexp.Compile e gestisci l'errore:
re, err := regexp.Compile(userPattern)
if err != nil {
return fmt.Errorf("bad pattern: %w", err)
}
Compila una volta sola. Compilare costa molto più che cercare. Un regexp.MustCompile dentro una funzione eseguita a ogni richiesta o a ogni riga ripete quel lavoro ogni volta. Metti il *regexp.Regexp compilato in una var a livello di pacchetto o in un campo di una struct. Una regexp compilata si può usare in sicurezza da molte goroutine contemporaneamente.
regexp.MatchString(pattern, s) compila e cerca in una sola chiamata. Va bene per un controllo occasionale ed è uno spreco dentro un ciclo.
Trovare le corrispondenze
I nomi dei metodi seguono uno schema: Find + All? + String? + Submatch? + Index?.
| Parte | Significato |
|---|---|
All | tutte le corrispondenze non sovrapposte; accetta un limite n (-1 per tutte) |
String | lavora su una string; senza, il metodo accetta e restituisce []byte |
Submatch | restituisce anche i gruppi di cattura |
Index | restituisce offset in byte invece del testo |
FindString restituisce "" quando non c'è corrispondenza, e questo non si distingue dalla corrispondenza con una stringa vuota. Quando il pattern può corrispondere a un testo vuoto, usa FindStringIndex (restituisce nil se non trova nulla) oppure prima MatchString. Le varianti All restituiscono nil quando non trovano nulla.
Gruppi di cattura
Le parentesi catturano. FindStringSubmatch restituisce l'intera corrispondenza all'indice 0, poi un elemento per ogni gruppo:
Controlla sempre nil prima di indicizzare il risultato: se non c'è corrispondenza, m[1] va in panic.
(?:...) raggruppa senza catturare, per alternanze o ripetizioni: (?:ab)+. I gruppi con nome si scrivono (?P<name>...) e, da Go 1.22, anche (?<name>...).
Sostituire
Una trappola classica con i riferimenti ai gruppi: "$1x" viene letto come un gruppo di nome 1x, che non esiste, e si espande in una stringa vuota. Scrivi "${1}x".
Per una stringa fissa, strings.ReplaceAll, strings.Contains e strings.Split sono più semplici e più veloci di una regexp. Usa regexp quando il testo che cerchi ha una forma e non un valore fisso.
Riferimento rapido della sintassi
Go usa la sintassi RE2, che copre le funzionalità familiari in stile Perl:
| Sintassi | Corrisponde a |
|---|---|
. | qualsiasi carattere tranne il ritorno a capo (con (?s), anche il ritorno a capo) |
\d \w \s | cifra, carattere di parola [0-9A-Za-z_], spazio bianco (solo ASCII) |
\D \W \S | le negazioni |
[abc] [^abc] [a-z] | classi di caratteri |
\pL \p{Greek} | classi Unicode: qualsiasi lettera, qualsiasi carattere greco |
* + ? {n,m} | ripetizione, greedy |
*? +? ?? | ripetizione, lazy |
^ $ | inizio e fine del testo (della riga, con (?m)) |
\b | confine di parola (ASCII) |
a|b | alternanza |
(?i) | ignora maiuscole e minuscole da quel punto fino alla fine del gruppo che lo contiene; di solito si scrive all'inizio del pattern |
\d e \w corrispondono solo all'ASCII. Per "qualsiasi lettera in qualsiasi lingua" usa \pL, e per qualsiasi cifra Unicode \p{Nd}. regexp.QuoteMeta(s) fa l'escape di ogni carattere speciale in s, e ti serve quando costruisci un pattern a partire dall'input dell'utente.
Cosa RE2 non sa fare
Le regexp di Go garantiscono che il matching richieda un tempo lineare rispetto alla lunghezza dell'input. Le funzionalità che romperebbero questa garanzia non sono supportate:
- Niente lookahead né lookbehind:
(?=...),(?!...),(?<=...),(?<!...)sono errori di compilazione. Dato che Go 1.22 ha iniziato ad accettare(?<name>...)per i gruppi con nome, ora un lookbehind fallisce con il messaggio poco chiaroinvalid named capture, come mostra il programma qui sotto. - Niente backreference: non si può scrivere
(\w)\1per trovare una lettera doppia. - Niente quantificatori possessivi né gruppi atomici.
Il rimedio è quasi sempre lo stesso: cattura un po' più del necessario, poi filtra o taglia in Go. Il vantaggio è che una regexp di Go non può finire in un backtracking catastrofico. Un pattern come (a+)+$, che può bloccare un motore PCRE con un input breve, qui gira in tempo lineare, e questo conta quando il pattern o l'input arrivano dall'esterno.
Errori comuni
- Compilare dentro un ciclo o un handler. Compila una volta in una variabile a livello di pacchetto.
- Pattern tra doppi apici.
"\d"non è nemmeno una stringa Go valida; usa i backtick. - Dimenticare le ancore nella validazione.
\d{5}corrisponde dentro"abc123456xyz". Usa^\d{5}$. - Indicizzare il risultato di un submatch senza controllare
nil. - Aspettarsi che
\wo\bcapiscano il testo non ASCII. Usa classi Unicode come\pL. - Usare regexp per stringhe fisse. Il pacchetto
stringsè più chiaro e più veloce.
Domande frequenti
Come controllo se una stringa corrisponde a una regex in Go?
Compila il pattern una sola volta con regexp.MustCompile, scrivendolo come stringa raw tra backtick così i backslash non vanno raddoppiati, poi chiama re.MatchString(s). Senza ancore (^ e $), MatchString restituisce true se il pattern corrisponde in un punto qualsiasi della stringa.
Le regexp di Go supportano lookahead e lookbehind?
No. Il pacchetto regexp di Go usa la sintassi RE2, che non ha lookahead, lookbehind né backreference. In cambio il matching richiede un tempo lineare rispetto alla dimensione dell'input, quindi un pattern o un input ostile non può bloccare il programma. Usa invece gruppi di cattura e un po' di codice Go, oppure dividi il controllo in due regex.
Qual è la differenza tra regexp.Compile e regexp.MustCompile?
Compile restituisce il pattern compilato e un errore. MustCompile invece va in panic se il pattern non è valido. Usa MustCompile per i pattern scritti nel codice sorgente, di solito in una variabile a livello di pacchetto, così un errore di battitura fallisce all'avvio. Usa Compile per i pattern che arrivano dagli utenti o dai file di configurazione.
Come ottengo i gruppi di cattura di una regex in Go?
Usa FindStringSubmatch, che restituisce uno slice in cui l'indice 0 è l'intera corrispondenza e gli indici 1, 2, ... sono i gruppi, oppure nil se non c'è corrispondenza. Per tutte le corrispondenze usa FindAllStringSubmatch(s, -1). I gruppi con nome (?P<year>\d{4}) si trovano con re.SubexpIndex("year").