FormatFlow

Passo a passo · 6 min de leitura

Expressões regulares: um guia prático de início

Uma expressão regular é um padrão que descreve um conjunto de strings. Ela pode verificar se um texto corresponde, encontrar todas as ocorrências, capturar partes de uma correspondência ou localizar e substituir com referências a essas capturas. A maioria das linguagens de programação e muitas ferramentas de linha de comando implementa a mesma sintaxe básica com pequenas diferenças.

A dificuldade não está em escrever um padrão uma vez, mas em entender por que um padrão corresponde mais ou menos do que você pretendia. Este guia cobre os blocos de construção, o modelo de correspondência que explica resultados surpreendentes e os limites de segurança que vale conhecer antes de rodar um padrão sobre entrada não confiável.

Uma expressão regular é uma descrição, não uma string de busca

Uma busca literal encontra os caracteres exatos que você digitou. Uma expressão regular interpreta alguns caracteres como instruções, então o padrão abc corresponde aos caracteres abc, enquanto a.c corresponde a qualquer string de três caracteres que comece com a e termine com c. Essa interpretação é o que torna o regex poderoso e o que faz um ponto sem escape virar um bug comum.

Expressões regulares costumam ser aplicadas a uma linha ou a uma string inteira dependendo das flags, e esse contexto muda o resultado. O mesmo padrão pode corresponder a uma substring em uma única linha e a algo completamente diferente quando as quebras de linha são incluídas. Confirme sempre se o mecanismo trata a entrada como uma única string ou como linhas separadas antes de depurar o padrão em si.

Os blocos de construção que vale aprender primeiro

Classes de caracteres correspondem a um caractere de um conjunto: [abc] corresponde a a, b ou c; [a-z] corresponde a qualquer letra ASCII minúscula; \d é um dígito; \w é um caractere de palavra; \s é espaço em branco. Classes negadas invertem o significado, então [^0-9] corresponde a qualquer coisa que não seja um dígito. Âncoras não consomem caracteres: ^ corresponde ao início da string ou da linha e $ ao final.

Quantificadores dizem quantas vezes o elemento anterior se repete: * significa zero ou mais, + significa um ou mais, ? significa zero ou um, e {2,5} significa entre dois e cinco. Por padrão são gananciosos e pegam o máximo possível, devolvendo caracteres só quando o resto do padrão precisa deles; adicionar ? os torna preguiçosos e pegam o mínimo possível. Grupos entre parênteses capturam o que correspondeu, e uma barra vertical cria alternativas como cat|dog.

  • \d+ corresponde a um ou mais dígitos; ^\d{5}$ corresponde exatamente a cinco dígitos sozinho.
  • \bword\b corresponde à palavra inteira, não à palavra dentro de outra palavra.
  • ([A-Za-z]+)\s+([A-Za-z]+) captura duas palavras separadas por espaço em branco.
  • a.*?b corresponde do primeiro a até o b mais próximo, enquanto a.*b chega até o último b.
  • Flags mudam o comportamento: i ignora maiúsculas e minúsculas, g encontra todas as correspondências, m trata ^ e $ como âncoras de linha, s permite que o ponto corresponda a quebras de linha e u ativa a semântica Unicode completa.

Como a correspondência realmente avança

Os mecanismos testam o padrão em posições sucessivas da entrada (a menos que esteja ancorado), e em cada posição exploram alternativas. Quando um quantificador ganancioso consumiu caracteres e o resto do padrão falha, o mecanismo retrocede e tenta uma repetição mais curta. Esse retrocesso é a razão pela qual um padrão pode corresponder corretamente e ainda assim ser lento: o número de caminhos explorados pode crescer exponencialmente com a entrada.

A correspondência global é um laço em torno desse processo. Um padrão com a flag g devolve correspondências sucessivas, e correspondências de comprimento zero avançam a posição de forma especial para evitar um laço infinito. Grupos de captura são numerados pela ordem em que o parêntese de abertura aparece, e grupos nomeados tornam padrões complexos muito mais fáceis de ler e manter.

Um fluxo de trabalho de depuração que funciona

Comece pela menor entrada que deveria corresponder e pela entrada mais próxima que não deveria. Teste as duas e depois adicione complexidade um elemento de cada vez em vez de escrever o padrão completo e ficar olhando o resultado. Quando uma correspondência está errada, faça três perguntas: onde começou, onde terminou e qual alternativa ou repetição consumiu os caracteres do meio.

Escape os caracteres que o mecanismo trata de forma especial. Esta é a causa mais comum de confusão ao testar URLs, caminhos de arquivo, endereços de e-mail e trechos de código. Lembre que literais de string de JSON e de linguagens de programação também processam barras invertidas, então um padrão escrito para um mecanismo regex pode precisar da barra invertida duplicada no código-fonte. Use um testador que mostre tanto o padrão que você digitou quanto o padrão que o mecanismo recebeu.

Segurança: backtracking catastrófico e entrada não confiável

Alguns padrões são corretos mas catastroficamente lentos em entradas específicas. Quantificadores aninhados como (a+)+$ são o exemplo clássico: para uma sequência longa de caracteres seguida de um que não pode corresponder à âncora final, o mecanismo tenta um número exponencial de formas de dividir a sequência. As defesas práticas são simplificar a repetição aninhada, ancorar padrões sempre que possível, preferir classes de caracteres explícitas e aplicar um timeout ou limite de comprimento de entrada ao casar texto não confiável.

Nunca use uma expressão regular como única defesa para análise sensível à segurança. Endereços de e-mail, HTML, URLs e linguagens de programação ultrapassam o que um padrão compacto consegue validar corretamente. Use regex para uma checagem rápida de sanidade e depois analise com uma biblioteca dedicada ou valide estruturalmente. E quando um padrão roda em um servidor, limite o tamanho da entrada e o número de correspondências para uma única requisição não conseguir consumir o processo.

Perguntas frequentes

Por que meu regex só corresponde à primeira ocorrência?
A maioria dos métodos de string devolve apenas a primeira correspondência. Adicione a flag g quando usar um mecanismo que a suporta, ou use uma função de busca global como matchAll. O testador mostra todas as correspondências por padrão, então se ele encontra várias e o seu código encontra uma, a flag costuma ser a diferença.
Qual a diferença entre quantificadores gananciosos e preguiçosos?
Quantificadores gananciosos consomem o máximo possível e depois retrocedem; preguiçosos consomem o mínimo possível e se expandem só quando necessário. Adicionar um ponto de interrogação depois do quantificador o torna preguiçoso: a.*b chega até o último b da linha, enquanto a.*?b para no primeiro b.
Por que meu padrão funciona em uma linguagem e falha em outra?
Dialetos de expressões regulares diferem nos recursos suportados e nos padrões. Lookbehind, sintaxe de captura nomeada, escapes de propriedades Unicode e o significado de \w variam por mecanismo. Confira também como o padrão é passado: escape de JSON ou de string pode mudar as barras invertidas antes de o mecanismo vê-las.
Como faço para corresponder através de várias linhas?
Por padrão, o ponto não corresponde a uma quebra de linha. Ative a flag s para o ponto corresponder a quebras de linha, e use a flag m se quiser que ^ e $ correspondam em cada limite de linha em vez de só no início e no fim da string inteira. As duas flags estão disponíveis em JavaScript e em muitos outros mecanismos.
Um regex basta para validar um endereço de e-mail ou uma URL?
Não. Um regex pode detectar erros óbvios, mas a validação completa exige analisar com uma biblioteca que implemente a especificação real. Use um padrão permissivo para recusar entradas claramente inválidas e deixe o analisador real decidir se o valor é utilizável.

Ferramentas para esta tarefa

Todas as ferramentas Desenvolvimento