Introdução
Expressões regulares — comumente chamadas de regex ou regexp — são sequências de caracteres que definem um padrão de busca. Elas são uma das ferramentas mais poderosas no arsenal de qualquer desenvolvedor, permitindo pesquisar, validar, extrair e transformar texto com uma única expressão compacta.
Seja validando um endereço de e-mail em um formulário web, extraindo dados de logs, ou realizando uma complexa operação de busca e substituição em milhares de arquivos, as expressões regulares permitem que você expresse exatamente o que procura em uma notação concisa e portátil que praticamente todas as linguagens de programação e a maioria dos editores de texto entendem.
Breve História das Expressões Regulares
A história das expressões regulares remonta aos fundamentos da ciência da computação teórica:
- 1951 — O matemático Stephen Kleene formaliza o conceito de linguagens regulares e introduz a notação de estrela de Kleene (
*) como parte de seu trabalho em teoria de autômatos. - 1968 — Ken Thompson implementa expressões regulares no editor de texto QED e posteriormente em ferramentas Unix como
grep,sedeawk. - 1986 — POSIX padroniza duas variantes: BRE (Basic Regular Expressions) e ERE (Extended Regular Expressions).
- 1997 — Philip Hazel cria a biblioteca PCRE (Perl Compatible Regular Expressions), que se torna o padrão de fato para regex avançado.
- 1999 — ECMAScript 3 padroniza o objeto
RegExpdo JavaScript. - 2015 — ES6 adiciona as flags
u(Unicode) ey(sticky). - 2018 — ES2018 adiciona grupos de captura nomeados (
(?<nome>...)) e asserções lookbehind.
POSIX vs PCRE vs JavaScript RegExp
| Recurso | BRE/ERE (POSIX) | PCRE | JavaScript RegExp |
|---|---|---|---|
| Lookahead | ✗ | ✓ | ✓ |
| Lookbehind | ✗ | ✓ | ✓ (ES2018+) |
| Grupos nomeados | ✗ | ✓ | ✓ (ES2018+) |
| Correspondência não-gulosa | ✗ | ✓ | ✓ |
| Unicode | Limitado | ✓ | ✓ (com flag u) |
| Retroreferências | ✓ | ✓ | ✓ |
Referência de Sintaxe Principal
Tabela de Referência Rápida
| Padrão | Significado |
|---|---|
. |
Qualquer caractere exceto quebra de linha |
^ |
Início da string / início de linha (com flag m) |
$ |
Fim da string / fim de linha (com flag m) |
\d |
Qualquer dígito [0-9] |
\D |
Qualquer não-dígito |
\w |
Caractere de palavra [a-zA-Z0-9_] |
\W |
Caractere de não-palavra |
\s |
Espaço em branco (espaço, tabulação, quebra de linha…) |
\S |
Caractere que não é espaço em branco |
[abc] |
Classe de caracteres — corresponde a a, b ou c |
[^abc] |
Classe negada — corresponde a tudo exceto a, b, c |
[a-z] |
Intervalo de caracteres |
* |
0 ou mais vezes (guloso) |
+ |
1 ou mais vezes (guloso) |
? |
0 ou 1 vez (guloso) |
{n} |
Exatamente n vezes |
{n,m} |
Entre n e m vezes (guloso) |
*? +? ?? |
Equivalentes preguiçosos (não-gulosos) |
(abc) |
Grupo de captura |
(?:abc) |
Grupo de não-captura |
(?<nome>abc) |
Grupo de captura nomeado |
| |
Alternância — corresponde à esquerda OU direita |
(?=...) |
Lookahead positivo |
(?!...) |
Lookahead negativo |
(?<=...) |
Lookbehind positivo |
(?<!...) |
Lookbehind negativo |
Classes de Caracteres
Classes de caracteres permitem corresponder a um caractere de um conjunto. [aeiou] corresponde a qualquer vogal. [a-zA-Z] corresponde a qualquer letra. [^0-9] corresponde a qualquer caractere que não seja um dígito.
Classes abreviadas comuns:
\dequivale a[0-9]\wequivale a[a-zA-Z0-9_]\scorresponde a espaço, tabulação (\t), quebra de linha (\n), retorno de carro (\r) e outros espaços em branco
Quantificadores: Gulosos vs Preguiçosos
Por padrão, os quantificadores são gulosos — correspondem ao máximo possível. Considere a string HTML <b>negrito</b> e <i>itálico</i>:
<.*> → corresponde à string inteira de <b> a </i> (guloso)
<.*?> → corresponde a <b>, depois </b>, depois <i>, depois </i> (preguiçoso)
Adicionar ? após um quantificador o torna preguiçoso (não-guloso): ele corresponde ao mínimo possível enquanto o padrão geral ainda for bem-sucedido.
Âncoras
^corresponde ao início da string (ou início de linha com a flagm).$corresponde ao fim da string (ou fim de linha comm).\bcorresponde a um limite de palavra — a transição entre um caractere de palavra e um que não é.\Bcorresponde a um não-limite de palavra.
Grupos e Retroreferências
Grupos de captura (...) capturam o texto correspondente, ao qual você pode se referir depois como \1, \2, etc. (retroreferências), ou acessar através do array de resultados.
Grupos de não-captura (?:...) agrupam subpadrões sem criar uma captura, o que é mais eficiente quando você não precisa do valor capturado.
Grupos nomeados (?<ano>\d{4}) permitem referenciar capturas por nome (match.groups.ano em JavaScript), tornando os padrões muito mais legíveis.
Lookahead e Lookbehind
Essas asserções de largura zero correspondem a uma posição, não a um caractere:
\d+(?= reais) → corresponde a dígitos somente se seguidos de " reais"
\d+(?! reais) → corresponde a dígitos que NÃO são seguidos de " reais"
(?<=R\$)\d+ → corresponde a dígitos somente se precedidos por "R$"
(?<!R\$)\d+ → corresponde a dígitos que NÃO são precedidos por "R$"
Lookaheads e lookbehinds não consomem caracteres, portanto o texto correspondente não inclui a parte do lookahead/lookbehind.
Flags
| Flag | Nome | Efeito |
|---|---|---|
i |
Sem distinção de maiúsculas | [a-z] também corresponde a [A-Z] |
g |
Global | Encontra todas as correspondências, não apenas a primeira |
m |
Multilinha | ^ e $ correspondem a limites de linha |
s |
dotAll | . também corresponde a quebras de linha |
u |
Unicode | Habilita correspondência Unicode completa; necessário para \p{} |
y |
Sticky | Corresponde apenas na posição lastIndex |
x |
Verboso/Estendido | Permite espaços em branco e comentários (apenas PCRE/Python) |
A flag x (verbosa) é especialmente valiosa para documentar padrões complexos:
import re
pattern = re.compile(r"""
^ # início da string
(?P<year>\d{4}) # ano com 4 dígitos
-
(?P<month>0[1-9]|1[0-2]) # mês 01–12
-
(?P<day>0[1-9]|[12]\d|3[01]) # dia 01–31
$
""", re.VERBOSE)
Padrões Comuns com Exemplos Reais
Validação de E-mail
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
Detalhamento:
^[a-zA-Z0-9._%+-]+— parte local (letras, dígitos e caracteres especiais selecionados)@— arroba literal[a-zA-Z0-9.-]+— nome do domínio\.[a-zA-Z]{2,}$— TLD com 2+ letras
Correspondência de URL
https?://(?:www\.)?[a-zA-Z0-9-]+(?:\.[a-zA-Z]{2,})+(?:/[^\s]*)?
Data ISO (YYYY-MM-DD)
\b\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])\b
Valida mês 01–12 e dia 01–31. Observe que não valida intervalos de dias específicos por mês (ex. 30 de fevereiro passaria na validação).
Endereço IPv4
\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b
Detalhamento:
25[0-5]— 250–2552[0-4]\d— 200–249[01]?\d\d?— 0–199
Número de Telefone (EUA)
\+?1?\s?[\(]?\d{3}[\)]?[-.\s]?\d{3}[-.\s]?\d{4}
Corresponde a formatos como (555) 123-4567, 555-123-4567, +1 555 123 4567.
Código de Cor Hexadecimal
#(?:[0-9A-Fa-f]{3}){1,2}\b
Corresponde a cores hex de 3 dígitos (#F00) e 6 dígitos (#FF0000).
Expressões Regulares em Diferentes Linguagens de Programação
JavaScript
// Sintaxe literal com flags
const regex = /^hello\s+world$/im;
const match = "Hello World".match(regex);
// Sintaxe construtora (útil para padrões dinâmicos)
const term = "world";
const dynamic = new RegExp(`hello\\s+${term}`, "im");
// Substituir todas as ocorrências
const result = "foo bar foo".replaceAll(/foo/g, "baz");
// Capturas nomeadas (ES2018+)
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const { year, month, day } = "2024-03-15".match(dateRegex).groups;
Python
import re
# Compilar para reutilização
pattern = re.compile(r'^hello\s+world$', re.IGNORECASE | re.MULTILINE)
match = pattern.match("Hello World")
# Encontrar todas as correspondências
dates = re.findall(r'\d{4}-\d{2}-\d{2}', text)
# Substituir com uma função
result = re.sub(r'\b\d+\b', lambda m: str(int(m.group()) * 2), "1 mais 2 é 3")
# Grupos nomeados
m = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})', "2024-03")
print(m.group('year')) # 2024
Java
import java.util.regex.*;
Pattern p = Pattern.compile("^hello\\s+world$",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher("Hello World");
boolean found = m.matches();
// Extrair grupos
Pattern datePattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher dm = datePattern.matcher("Hoje é 2024-03-15");
if (dm.find()) {
String year = dm.group(1);
}
Go
import "regexp"
re := regexp.MustCompile(`(?im)^hello\s+world$`)
match := re.FindString("Hello World")
// Encontrar todas as sub-correspondências
dateRe := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
all := dateRe.FindAllStringSubmatch(text, -1)
for _, m := range all {
year, month, day := m[1], m[2], m[3]
_ = year; _ = month; _ = day
}
// Grupos nomeados
namedRe := regexp.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})`)
match2 := namedRe.FindStringSubmatch("2024-03")
yearIdx := namedRe.SubexpIndex("year")
fmt.Println(match2[yearIdx]) // 2024
Performance e Backtracking Catastrófico
Como o Backtracking Funciona
A maioria dos motores de regex usa correspondência baseada em NFA (Autômato Finito Não Determinístico), o que significa que eles podem tentar múltiplos caminhos através do padrão quando uma tentativa anterior falha. Esse backtracking é o que possibilita recursos como lookaheads e retroreferências — mas também pode ser uma armadilha de performance.
O Caso Catastrófico
Considere o padrão (a+)+ aplicado à string "aaaaaX":
- O
+externo tenta corresponder ao máximo de grupos possível. - Quando o motor chega ao
Xe falha, ele faz backtracking e tenta diferentes maneiras de dividir os caracteresaentre as repetições do grupo. - Para uma string de comprimento n, há 2^(n-1) divisões possíveis — levando à complexidade de tempo exponencial.
(a+)+ em "aaaaaaaaaaaaaaaaaX" → pode levar segundos ou minutos!
Outros padrões perigosos incluem (a|aa)+, (\w+\s*)+, e qualquer coisa com quantificadores aninhados sobre classes de caracteres sobrepostas.
Como Evitar
- Evitar quantificadores aninhados sobre o mesmo conjunto de caracteres:
(a+)+→ usea+em vez disso. - Usar grupos atômicos
(?>...)ou quantificadores possessivosa++(PCRE) para evitar backtracking em grupos já correspondidos. - Ser específico: substituir
.*por uma classe de caracteres que exclua delimitadores (ex.[^"]*dentro de strings entre aspas). - Ancorar padrões onde possível para que o motor falhe rapidamente.
- Usar timeout em código de produção ao processar entradas não confiáveis.
Como Ler uma Expressão Regular Complexa
Vamos decompor ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$:
^ → âncora: início da string
[a-zA-Z0-9._%+-]+ → um ou mais caracteres permitidos na parte local
@ → @ literal
[a-zA-Z0-9.-]+ → um ou mais caracteres de domínio
\. → ponto literal (escapado)
[a-zA-Z]{2,} → TLD: 2 ou mais letras
$ → âncora: fim da string
Dica: Use o testador de regex deste site para destacar cada grupo capturado e ver exatamente qual parte da entrada corresponde a cada token. Construa seu padrão incrementalmente — comece com a peça válida mais simples, verifique-a e então expanda.
Melhores Práticas
- Compilar uma vez, usar muitas vezes. Pré-compilar um padrão (ex.
re.compile()no Python,Pattern.compile()no Java) é muito mais eficiente do que re-parseá-lo a cada chamada. - Preferir grupos de não-captura
(?:...)quando o valor capturado não é necessário. Isso sinaliza a intenção e evita alocação desnecessária de memória. - Usar raw strings para padrões. No Python, use
r'\d+'em vez de'\\d+'para evitar escape duplo. No JavaScript, a sintaxe literal/\d+/lida com isso automaticamente. - Nomear suas capturas.
(?<ano>\d{4})é muito mais manutenível do que depender do índice de grupo\1. - Testar com casos extremos: strings vazias, strings que quase-mas-não-totalmente correspondem, caracteres Unicode e entradas muito longas.
- Documentar padrões complexos com a flag
x(verbosa) no Python/PCRE, ou com comentários inline no seu código. - Nunca usar regex para fazer parsing completo de HTML ou XML. Use uma biblioteca de parser adequada.
- Validar entradas no lado do servidor. A validação regex no lado do cliente melhora a UX, mas não deve ser a única linha de defesa.
Perguntas Frequentes (FAQ)
P: Qual é a diferença entre match() e search() no Python?
R: re.match() só corresponde no início da string. re.search() varre toda a string em busca de uma correspondência. Use re.fullmatch() para exigir que o padrão corresponda à string inteira.
P: Por que ^ dentro de uma classe de caracteres [^abc] significa algo diferente?
R: Dentro de uma classe de caracteres, ^ como primeiro caractere nega a classe — corresponde a qualquer caractere que não esteja no conjunto. Fora de uma classe de caracteres, ^ é uma âncora para o início da string.
P: Posso usar regex para fazer parsing de HTML?
R: Para extrações simples e bem definidas de estruturas HTML conhecidas, regex pode funcionar. Mas HTML não é uma linguagem regular — permite aninhamento arbitrário e tags de fechamento opcionais. Use um parser HTML adequado (ex. BeautifulSoup no Python, DOMParser no JS) para parsing robusto.
P: Qual é a diferença entre quantificadores gulosos e possessivos?
R: Quantificadores gulosos fazem backtracking — eles tentam a correspondência máxima e devolvem caracteres se necessário. Quantificadores possessivos (ex. a++ no PCRE) nunca devolvem — uma vez que correspondem, a correspondência fica bloqueada. Isso previne o backtracking catastrófico, mas também pode fazer com que uma correspondência falhe quando um quantificador guloso teria tido sucesso.
P: Como faço para corresponder a um ponto literal . ou parêntese (?
R: Escape-os com uma barra invertida: \. corresponde a um ponto literal, \( corresponde a um parêntese aberto literal.
P: Regex é sensível a maiúsculas por padrão?
R: Sim. Use a flag i (/padrão/i no JavaScript, re.IGNORECASE no Python) para habilitar a correspondência insensível a maiúsculas.
P: O que \b corresponde?
R: \b é uma asserção de limite de palavra de largura zero. Ela corresponde à posição entre um caractere de palavra (\w) e um caractere de não-palavra (\W).
P: Como testo se uma string inteira corresponde a um padrão?
R: Ancore com ^ e $: ^padrão$. No Python, você também pode usar re.fullmatch(). No JavaScript, use .test() com âncoras ou verifique que match()[0].length === input.length.
Use o testador de regex deste site para experimentar com cada padrão neste guia. Cole qualquer padrão, digite sua string de teste e veja as correspondências destacadas em tempo real.