Menu

Regexp in Golang: match, ricerca, gruppi e sostituzione

Come usare le espressioni regolari in Go con il pacchetto regexp: MustCompile, MatchString, FindString e FindAllString, gruppi di cattura e gruppi con nome, ReplaceAllString e i limiti della sintassi RE2, come l'assenza del lookbehind.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

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?.

ParteSignificato
Alltutte le corrispondenze non sovrapposte; accetta un limite n (-1 per tutte)
Stringlavora su una string; senza, il metodo accetta e restituisce []byte
Submatchrestituisce anche i gruppi di cattura
Indexrestituisce 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:

SintassiCorrisponde a
.qualsiasi carattere tranne il ritorno a capo (con (?s), anche il ritorno a capo)
\d \w \scifra, carattere di parola [0-9A-Za-z_], spazio bianco (solo ASCII)
\D \W \Sle 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))
\bconfine di parola (ASCII)
a|balternanza
(?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 chiaro invalid named capture, come mostra il programma qui sotto.
  • Niente backreference: non si può scrivere (\w)\1 per 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 \w o \b capiscano 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").

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA