Desenvolvimento

Os erros de regex que fazem você perder a tarde

Três erros de regex devolvem algo que parece quase certo, e por isso custam horas em vez de minutos. Como reconhecer cada um em um minuto.

Duas fileiras de caixas: um colchete de ocorrência cobre as cinco da fileira de cima e na de baixo para no fim da linha, com um xis na caixa que ficou de fora.

A maioria das tardes perdidas com regex vem de um entre três erros. Um quantificador é guloso e pega mais do que você queria. Um ponto se recusa a atravessar uma quebra de linha. Ou uma substituição só mexe na primeira ocorrência porque a flag g está desligada. Nenhum deles dá erro. Cada um devolve um resultado que parece certo no único exemplo que você testou, e é isso que os torna caros: você não sabe que algo está errado até o padrão encontrar dados reais.

Por que meu padrão casa com muito mais do que eu queria?

Porque * e + são gulosos. Eles pegam todos os caracteres que conseguem e os devolvem um de cada vez só até o resto do padrão conseguir casar. Rode <.*> sobre <b>hi</b> and <i>yo</i> e você obtém uma única ocorrência: a string inteira, do primeiro < ao último >. O padrão fez o que você escreveu. Você escreveu "qualquer trecho de caracteres entre um colchete e outro colchete", e o trecho mais longo possível é tudo.

Há duas correções e elas não são iguais. Um ponto de interrogação depois do quantificador o torna preguiçoso: <.*?> para no primeiro > e devolve quatro ocorrências (<b>, </b>, <i>, </i>). Uma classe negada diz o que você realmente quer dizer: <[^>]*> é "um colchete, depois qualquer coisa que não seja um colchete de fechamento, depois um colchete de fechamento". Dá as mesmas quatro ocorrências neste texto. Também é o hábito mais seguro, porque um ponto preguiçoso continua atravessando qualquer caractere de que precise, e uma classe negada não consegue.

O sinal é uma quantidade de ocorrências menor do que a esperada, com uma delas suspeitosamente longa. Cole seu texto no testador de regex e olhe o destaque: um único bloco de cor atravessando várias coisas que você queria separadas é casamento guloso, sempre.

Por que o padrão para no fim de uma linha?

Porque o ponto casa com qualquer caractere exceto uma quebra de linha. Pegue três linhas, Name: Ada, Role: Admin, Name: Grace, e o padrão Name:.*Admin. Ele não encontra nada. O texto está ali, mas o ponto não consegue pular a quebra de linha entre a primeira linha e a segunda.

Este é o que mais tempo custa porque o padrão funcionou quando você o testou em uma linha só. Você colou uma linha na caixa, obteve uma ocorrência e levou o padrão para um código que lê um arquivo inteiro. A causa habitual de "funciona no testador e não devolve nada em produção" é esta, ou a flag logo abaixo.

Há duas saídas. A flag s deixa o ponto casar com uma quebra de linha; o testador de regex tem uma caixa de seleção para isso. Ou escreva [\s\S], que significa "espaço em branco ou não espaço em branco" e portanto qualquer caractere. A ferramenta de localizar e substituir não tem uma opção s, então lá o que funciona é [\s\S].

Corrigir isso pode causar o primeiro erro. Ligue s e um Name: (.*) guloso deixa de parar no fim da sua linha: ele devolve uma única ocorrência que vai do primeiro Name: até o fim do texto, levando as outras duas linhas junto. Com o ponto confinado a uma linha, a gulodice era inofensiva. Com o ponto solto, não é, e quantificadores preguiçosos ou classes negadas passam a ser necessários no mesmo instante.

Uma armadilha relacionada são ^ e $. Sem a flag m eles significam o começo e o fim do texto inteiro, então ^Role não casa com nada no meio de um arquivo. As duas ferramentas daqui se comportam de forma diferente: o testador começa com m desligada, enquanto a ferramenta de localizar e substituir começa com "^ e $ casam em cada linha" ligada. Um padrão copiado de uma para a outra pode mudar de significado sem nenhuma edição.

Por que só a primeira ocorrência foi substituída?

Porque a flag g está desligada. Sem ela o motor para depois da primeira ocorrência, e a substituição para junto. Em JavaScript, substituir - por + em a-b-c dá a+b-c sem g e a+b+c com ela.

A armadilha é que nada parece quebrado. Em um teste de uma linha o resultado está certo. Em um documento de duas mil linhas a primeira ocorrência é corrigida e as outras 1.999 ficam intactas, e a menos que você procure depois o texto antigo, não vai ver. Quando a substituição está errada por exatamente "todas menos uma", olhe primeiro as flags.

A mesma flag tem um segundo efeito no código. Um objeto regex criado com g lembra onde terminou sua última ocorrência, em uma propriedade chamada lastIndex. Chamar .test() duas vezes com a mesma string devolve true e depois false, porque a segunda busca começa depois da primeira ocorrência. Se uma verificação passa com algumas entradas e falha com as mesmas entradas dentro de um laço, uma regex /g compartilhada é uma causa provável. Tire a flag quando você só precisa de um sim ou não. O testador deste site constrói um padrão novo a cada tecla digitada, então não consegue reproduzir isso; só aparece em código que reutiliza um.

Como eu descubro qual dos erros estou vendo?

Conte antes de ler. Calcule quantas ocorrências você espera e compare com o número que a ferramenta informa. A diferença aponta para a causa:

Depois reduza o problema. Corte o texto até a menor amostra que ainda dê errado, e corte o padrão até a parte que está errada, acrescentando peças de uma em uma. Uma regex que falha inteira costuma ter uma peça que casou com algo inesperado, e você só vê qual construindo a partir do zero. Ler um padrão longo procurando o erro é mais lento do que reconstruí-lo.

Quando há uma substituição, confira também a saída e não só as ocorrências. Os erros ao localizar e substituir explica como confirmar o que de fato mudou, porque uma lista de ocorrências correta ainda pode produzir um resultado errado se a string de substituição contiver um $.

O que o testador não pega?

Alguns limites, para você não confiar em um resultado verde além do que ele alcança.

O padrão, o texto e a substituição são tratados pelo seu navegador. Nada é enviado, e isso importa aqui porque as pessoas testam padrões em trechos de log e registros de clientes.

Para ver os três erros com os próprios olhos, cole os exemplos acima no testador de regex e alterne as caixas g, s e m. O destaque e a contagem de ocorrências mudam a cada clique, e isso ensina a diferença mais rápido do que uma descrição.

Se o padrão está certo e o resultado continua errado, o texto provavelmente tem caracteres que não são os que você vê. Os caracteres invisíveis em um texto explica como encontrá-los.

Perguntas frequentes

Por que minha regex casa com mais texto do que eu esperava?

Quantificadores como * e + são gulosos por padrão: pegam todos os caracteres que conseguem e os devolvem só até o resto do padrão casar. Acrescentar um ponto de interrogação torna o quantificador preguiçoso, então <.*?> para no primeiro colchete de fechamento. Uma classe negada como <[^>]*> costuma ser mais segura porque não consegue atravessar o caractere que você procura.

Por que o ponto da minha regex não casa com uma quebra de linha?

Em JavaScript o ponto casa com qualquer caractere exceto uma quebra de linha, então um padrão que funciona em uma linha pode não devolver nada em um parágrafo. Ligue a flag s para o ponto atravessar linhas, ou escreva [\s\S] no lugar do ponto. A flag m é outro ajuste: ela muda o que ^ e $ significam e não mexe no ponto.

Por que minha regex substitui só a primeira ocorrência?

Porque a flag g está desligada. Sem ela o motor para na primeira ocorrência e a substituição para junto, então em um texto longo uma ocorrência é trocada e as outras ficam intactas. Ligue a flag g para substituir todas as ocorrências.

Por que minha regex devolve true e depois false com a mesma string?

Um objeto regex com a flag g guarda em lastIndex onde terminou sua última ocorrência, e a chamada seguinte começa dali. Por isso chamar test() duas vezes com a mesma string devolve true e depois false. Tire a flag g quando você só precisa de uma resposta de sim ou não, ou crie uma regex nova para cada verificação.

Por que meu padrão funciona no testador mas não no meu código?

As causas habituais são um motor de regex diferente, uma flag faltando ou um texto diferente do que você colou. Lookbehind, grupos nomeados e escapes de propriedade variam entre JavaScript, Python, PCRE e Go. Dados reais também costumam ter quebras de linha, espaços não separáveis e aspas curvas que um teste de uma linha só não tinha.

Última atualização 19 de outubro de 2026