Compile uma vez, depois case
Escreva os padrões como raw strings entre crases. Em uma string normal entre aspas duplas, toda barra invertida precisa ser duplicada ("\\d+"), o que logo fica ilegível.
regexp.MustCompile causa panic se o padrão for inválido, que é o que você quer para um padrão no seu código-fonte: um erro de digitação falha na hora, na inicialização, e não na primeira requisição. Para padrões que vêm de usuários ou de configuração, use regexp.Compile e trate o erro:
re, err := regexp.Compile(userPattern)
if err != nil {
return fmt.Errorf("bad pattern: %w", err)
}
Compile uma vez. Compilar é muito mais caro que casar. Um regexp.MustCompile dentro de uma função que executa a cada requisição ou a cada linha repete esse trabalho toda vez. Coloque o *regexp.Regexp compilado em um var de nível de pacote ou em um campo de struct. Uma regexp compilada pode ser usada por muitas goroutines ao mesmo tempo com segurança.
regexp.MatchString(pattern, s) compila e casa em uma chamada só. Serve para uma verificação avulsa e é desperdício dentro de um laço.
Encontrando casamentos
Os nomes dos métodos seguem um padrão: Find + All? + String? + Submatch? + Index?.
| Parte | Significado |
|---|---|
All | todos os casamentos sem sobreposição; recebe um limite n (-1 para todos) |
String | trabalha com uma string; sem ela, o método recebe e devolve []byte |
Submatch | devolve também os grupos de captura |
Index | devolve offsets em bytes em vez do texto |
FindString devolve "" quando não há casamento, o que não se distingue de casar uma string vazia. Quando o padrão pode casar um texto vazio, use FindStringIndex (devolve nil quando não há casamento) ou MatchString antes. As variantes All devolvem nil quando nada casa.
Grupos de captura
Parênteses capturam. FindStringSubmatch devolve o casamento inteiro no índice 0 e depois uma entrada por grupo:
Sempre verifique se é nil antes de indexar o resultado; sem casamento, m[1] causa panic.
(?:...) agrupa sem capturar, para alternância ou repetição: (?:ab)+. Grupos nomeados se escrevem (?P<nome>...) e, desde o Go 1.22, também (?<nome>...).
Substituindo
Uma armadilha clássica com referências a grupos: "$1x" é lido como um grupo chamado 1x, que não existe, e vira uma string vazia. Escreva "${1}x".
Para uma string fixa, strings.ReplaceAll, strings.Contains e strings.Split são mais simples e mais rápidos que uma regexp. Recorra ao regexp quando o texto que você procura tem um formato, e não um valor fixo.
Referência rápida da sintaxe
O Go usa a sintaxe RE2, que cobre os recursos conhecidos do estilo Perl:
| Sintaxe | Casa com |
|---|---|
. | qualquer caractere exceto quebra de linha (com (?s), a quebra também) |
\d \w \s | dígito, caractere de palavra [0-9A-Za-z_], espaço em branco (só ASCII) |
\D \W \S | as negações |
[abc] [^abc] [a-z] | classes de caracteres |
\pL \p{Greek} | classes Unicode: qualquer letra, qualquer caractere grego |
* + ? {n,m} | repetição, gulosa |
*? +? ?? | repetição, preguiçosa |
^ $ | início e fim do texto (da linha, com (?m)) |
\b | fronteira de palavra (ASCII) |
a|b | alternância |
(?i) | sem diferenciar maiúsculas e minúsculas daquele ponto até o fim do grupo que o contém; normalmente escrito no início do padrão |
\d e \w só casam com ASCII. Para "qualquer letra em qualquer idioma", use \pL, e para qualquer dígito Unicode, \p{Nd}. regexp.QuoteMeta(s) escapa todos os caracteres especiais de s, o que você precisa ao montar um padrão a partir de entrada do usuário.
O que o RE2 não consegue fazer
A regexp do Go garante que o casamento leva tempo linear no tamanho da entrada. Recursos que quebrariam essa garantia não são suportados:
- Sem lookahead nem lookbehind:
(?=...),(?!...),(?<=...),(?<!...)são erros de compilação. Como o Go 1.22 passou a aceitar(?<nome>...)para grupos nomeados, um lookbehind agora falha com a mensagem confusainvalid named capture, como mostra o programa abaixo. - Sem backreferences:
(\w)\1para casar uma letra repetida não é possível. - Sem quantificadores possessivos nem grupos atômicos.
A solução alternativa é quase sempre a mesma: capture um pouco mais do que precisa e depois filtre ou fatie em Go. A recompensa é que uma regexp do Go não pode ser levada a um backtracking catastrófico. Um padrão como (a+)+$, que pode congelar um motor PCRE com uma entrada curta, executa em tempo linear aqui, o que importa quando o padrão ou a entrada vêm de fora.
Erros comuns
- Compilar dentro de um laço ou de um handler. Compile uma vez em uma variável de nível de pacote.
- Padrões entre aspas duplas.
"\d"nem é uma string Go válida; use crases. - Esquecer as âncoras na validação.
\d{5}casa dentro de"abc123456xyz". Use^\d{5}$. - Indexar o resultado de um submatch sem verificar se é
nil. - Esperar que
\wou\bentendam texto não ASCII. Use classes Unicode como\pL. - Usar regexp para strings fixas. O pacote
stringsé mais claro e mais rápido.
Perguntas frequentes
Como verificar se uma string casa com uma regex em Go?
Compile o padrão uma vez com regexp.MustCompile, escrevendo-o como raw string entre crases para que as barras invertidas não precisem de escape, e chame re.MatchString(s). Sem âncoras (^ e $), MatchString devolve true se o padrão casar em qualquer lugar da string.
O regexp do Go suporta lookahead e lookbehind?
Não. O regexp do Go usa a sintaxe RE2, que não tem lookahead, lookbehind nem backreferences. Em troca, o casamento executa em tempo linear no tamanho da entrada, então um padrão ou uma entrada maliciosos não conseguem travar o seu programa. Use grupos de captura e um pouco de código Go, ou divida a verificação em duas regexes.
Qual a diferença entre regexp.Compile e regexp.MustCompile?
Compile devolve o padrão compilado e um erro. MustCompile causa panic com um padrão inválido. Use MustCompile para padrões escritos no seu código-fonte, normalmente em uma variável de nível de pacote, para que um erro de digitação falhe na inicialização. Use Compile para padrões que vêm de usuários ou de arquivos de configuração.
Como pegar os grupos de captura de uma regex em Go?
Use FindStringSubmatch, que devolve um slice em que o índice 0 é o casamento inteiro e os índices 1, 2, ... são os grupos, ou nil se não houver casamento. Para todos os casamentos, use FindAllStringSubmatch(s, -1). Grupos nomeados (?P<year>\d{4}) podem ser encontrados com re.SubexpIndex("year").