Fastway
Voltar ao blog

Por que seu regex não casa: os 7 erros mais comuns

Rodrigo Krohling
·8 min de leitura

Uma expressão regular que não casa nada parece bug no motor de regex. Quase nunca é. O motor fez exatamente o que o padrão disse; o padrão é que disse outra coisa diferente do que você quis dizer.

Aqui estão os sete jeitos de isso acontecer, mais ou menos na ordem de frequência. Cada um tem um padrão quebrado concreto, o motivo e a correção. Teste enquanto lê — o testador de regex destaca as correspondências ao vivo, o que é mais rápido do que raciocinar sobre qualquer um destes casos de cabeça.

1. Quantificadores gulosos engolem demais

O padrão:

<.+>

Contra <b>um</b> e <b>dois</b> você espera quatro correspondências. Recebe uma: a linha inteira.

O + é guloso. Ele consome tudo o que consegue e depois devolve caractere por caractere só até o resto do padrão conseguir ter sucesso. Como o último > da linha satisfaz >, ele para ali e nunca retrocede mais.

A correção é um quantificador preguiçoso — acrescente ?:

<.+?>

Agora ele devolve assim que o padrão consegue casar, parando no primeiro >. O mesmo vale para *?, {2,}? e companhia. Se uma correspondência ficou maior do que você esperava, gula é a primeira suspeita.

2. Um ponto sem escape casa qualquer coisa

O padrão:

3.14

casa 3.14, e também 3x14, 3914 e 3 14. O ponto é curinga, não ponto final.

3\.14

A mesma armadilha é armada por + * ? ( ) [ ] { } ^ $ | e pela própria barra invertida. Dentro de uma classe de caracteres a maioria perde o significado especial, e é por isso que [.] também funciona e às vezes é mais legível que o escape.

Fique atento a isso em extensões de arquivo e domínios: relatorio.pdf como padrão casa relatorioXpdf, e exemplo.com casa exemploZcom.

3. ^ e $ estão ancorados na entrada inteira

O padrão:

^ERRO

rodado contra um log de várias linhas não acha nada, a menos que a primeiríssima linha comece com ERRO. Por padrão ^ significa início da entrada e $ significa fim da entrada — não início e fim de linha.

Ligue a flag m e as duas âncoras passam a valer por linha:

/^ERRO/m

Essa é a flag mais útil para qualquer coisa em formato de log, e a ausência dela é o motivo de tantos relatos de "funciona no testador mas não no meu código" acabarem sendo uma flag faltando, não um recurso faltando.

4. O . não atravessa quebra de linha

O padrão:

<title>(.*)</title>

funciona num documento de uma linha e falha assim que o título ocupa duas. O ponto casa qualquer caractere exceto quebra de linha.

Duas correções. A flag s (dotAll) faz o ponto incluir quebras:

/<title>(.*?)<\/title>/s

Ou, se você não quer o ponto tão permissivo em todo lugar, case com uma classe que inclua tudo explicitamente: [\s\S]*?. Esse truque é anterior à flag s e ainda aparece em muito código.

5. \d e \w são mais estreitos do que você pensa

Em JavaScript, sem a flag u, \w é exatamente [A-Za-z0-9_]. Ele não casa letra acentuada. Um validador de nome construído sobre ^\w+$ rejeita silenciosamente José, Müller e Łukasz — e vai passar em todos os testes que você escrever em inglês.

Se você precisa de letras de qualquer alfabeto, use escapes de propriedade Unicode:

/^\p{L}+$/u

A flag u é obrigatória para \p{...} funcionar. Mas seja deliberado: \p{L} é literalmente toda letra do Unicode, o que pode ser mais amplo do que um validador deveria aceitar. Raramente existe um regex correto para nome de pessoa; costuma existir uma decisão correta de validar menos.

6. A flag g deixa o regex com estado

Esse gera os relatos de bug mais estranhos, porque o padrão alterna entre funcionar e não funcionar:

const re = /\d+/g;
re.test('42');   // true
re.test('42');   // false

Um regex com g carrega uma propriedade lastIndex. test e exec começam a partir dela e a avançam, então chamar duas vezes na mesma string retoma depois da correspondência anterior, chega ao fim e reseta. Se você reaproveita um regex global entre chamadas — uma constante no nível do módulo é o culpado de sempre — você recebe resultados alternados.

Correções, em ordem de preferência: não use g quando só quer um booleano; construa o regex dentro da função para cada chamada receber um novo; ou zere re.lastIndex = 0 antes de cada uso.

7. O padrão está certo, mas a entrada não era o que você achava

Antes de reescrever um padrão pela quinta vez, olhe a string. Texto colado de PDF, planilha ou aplicativo de chat costuma carregar coisas que parecem caracteres comuns e não são: espaços não separáveis no lugar de espaços, aspas curvas no lugar de retas, um espaço de largura zero, um \r sobrando de quebra de linha do Windows.

O \s casa espaço não separável em JavaScript, mas um " " literal no seu padrão não casa. Do mesmo jeito, " nunca vai casar ". Se um padrão funciona no testador e falha com dado real, desconfie do dado.

Um fluxo que economiza tempo

Construa padrões de dentro para fora, a partir do menor pedaço que funciona.

  1. Comece com um trecho literal que você sabe que aparece no texto. Confirme que casa.
  2. Troque um literal por vez por uma classe ou um quantificador, conferindo a cada mudança.
  3. Coloque as âncoras por último. Elas são a causa mais comum de um padrão que de repente não casa nada, e deixá-las para o fim significa saber exatamente qual mudança quebrou.

Trabalhando assim, um regex que falha te diz qual dos sete você acertou, porque você só mudou uma coisa. Cole a sua entrada real — não uma versão simplificada — no testador e a resposta costuma aparecer em menos de um minuto. Se o texto vem de uma resposta de API, formate o JSON antes para enxergar o valor real da string, escapes e tudo.

Expressões regulares não são difíceis porque a sintaxe é densa. São difíceis porque fazem exatamente o que você diz, e dizer com precisão o que se quer dizer é a parte difícil de programar em geral.