Grupos de Captura em Regex: Named Groups, Lookahead e Lookbehind
Grupos de captura transformam regex de ferramenta de busca em ferramenta de extração e transformação de dados. Veja grupos simples, nomeados, não-capturantes, lookahead, lookbehind, backreferences, atomic groups — com exemplos reais em JavaScript, Python e Go.
Por Vitor Morais
Fundador do MochaLabz ·
Teste seus grupos de captura online
Visualize grupos capturados com destaque em tempo real. Suporte a flags e explicação por parte.
Testar regex →Grupos de captura são o recurso que transforma regex de uma ferramenta de busca em uma ferramenta de extração e transformação. Parênteses simples marcam partes do padrão para serem armazenadas; combinadas com named groups, lookahead/lookbehind e backreferences, viram uma linguagem completa para manipular strings estruturadas. Este guia cobre todos os tipos, com exemplos reais em JavaScript, Python e Go.
Grupos de captura básicos: o coração da extração
A ideia é simples: tudo entre parênteses ( ) é um grupo. Quando a regex matcha, cada grupo é armazenado separadamente:
const texto = 'Data: 18/04/2026 às 14:30';
const regex = /(\d{2})\/(\d{2})\/(\d{4}) às (\d{2}):(\d{2})/;
const match = texto.match(regex);
match[0]; // "18/04/2026 às 14:30" — match completo
match[1]; // "18" — grupo 1 (dia)
match[2]; // "04" — grupo 2 (mês)
match[3]; // "2026" — grupo 3 (ano)
match[4]; // "14" — grupo 4 (hora)
match[5]; // "30" — grupo 5 (minuto)
match.index; // posição no texto onde começou o matchGrupos não-capturantes: (?:)
Quando você precisa agrupar (para aplicar quantificador ou alternativa) mas não precisa extrair o valor, use (?:). Evita poluir a lista de grupos:
// ❌ Grupos capturantes desnecessários
const bad = /(https?):\/\/(www\.)?(\w+\.\w+)/;
'https://www.gmail.com'.match(bad);
// ['https://www.gmail.com', 'https', 'www.', 'gmail.com']
// Precisamos de match[3] mas grupos 1 e 2 foram "desperdiçados"
// ✅ Não-capturantes para o que não precisa extrair
const good = /(?:https?):\/\/(?:www\.)?(\w+\.\w+)/;
'https://www.gmail.com'.match(good);
// ['https://www.gmail.com', 'gmail.com']
// Agora match[1] é direto o que interessa
// ✅ Alternativa agrupada sem captura
/(?:jpg|jpeg|png|gif)$/.test('foto.jpg'); // trueBenefício extra
Grupos não-capturantes são ligeiramente mais rápidos (engine não precisa alocar memória para guardar o valor) e deixam a regex mais legível em termos de intenção.
Named groups: o recurso que faz código limpo
Em vez de depender de índices numéricos frágeis, nomeie os grupos com (?<nome>...):
const regex = /(?<dia>\d{2})\/(?<mes>\d{2})\/(?<ano>\d{4})/;
const m = '18/04/2026'.match(regex)!;
m.groups!.dia; // "18"
m.groups!.mes; // "04"
m.groups!.ano; // "2026"
// TypeScript infere tipo de groups:
type DateGroups = { dia: string; mes: string; ano: string };
// Reformatação usando named groups em replace
const iso = '18/04/2026'.replace(
/(?<dia>\d{2})\/(?<mes>\d{2})\/(?<ano>\d{4})/,
'$<ano>-$<mes>-$<dia>'
);
// "2026-04-18"
// Destructuring direto
const { groups: { dia, mes, ano } = {} as any } = m;| Critério | Numerados | Named |
|---|---|---|
| Acesso | match[1] | match.groups.nome |
| Leitura | Precisa contar grupos | Descritivo por si |
| Refatoração | Adicionar grupo quebra índices | Imune a adições |
| Tipagem TS | any | Inferível via template |
| Em replace | $1, $2 | $<nome> |
| Suporte JS | Sempre | ES2018 (Node 10+) |
Lookahead: verificar sem consumir
Lookahead verifica que um padrão existe adiante mas NÃO consome caracteres — útil para condições sem incluir no match:
// Lookahead positivo (?=...) — padrão EXISTE à frente
/preço(?= alto)/.test('preço alto'); // true
/preço(?= alto)/.test('preço baixo'); // false
'preço alto aqui'.match(/preço(?= alto)/);
// ['preço'] ← só "preço", sem consumir " alto"
// Lookahead negativo (?!...) — padrão NÃO existe à frente
/preço(?! alto)/.test('preço baixo'); // true
/preço(?! alto)/.test('preço alto'); // false
// Extrair valor numérico ANTES de "px"
'font-size: 16px'.match(/\d+(?=px)/);
// ['16'] ← só o número
// Validação de senha — múltiplos lookahead combinados
const SENHA_FORTE =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%]).{12,}$/;
// Cada (?=...) verifica: "em algum lugar da string há..."
// Sem consumir, permitindo sobreposição de condiçõesLookbehind: verificar o que veio antes
Simétrico ao lookahead, mas olha para trás no texto:
// Lookbehind positivo (?<=...) — padrão EXISTE atrás
// Extrair valor após "R$ "
'Preço: R$ 99,90'.match(/(?<=R\$ )[\d,]+/);
// ['99,90'] ← só o valor, sem "R$ "
// Lookbehind negativo (?<!...)
// Encontrar "o" que NÃO é precedido por "n"
'nó bolo'.match(/(?<!n)o/g);
// ['o'] ← só em "bolo", não em "nó"
// Remover prefixos sem consumir
const sem_prefixo = 'R$ 99,90'.replace(/R\$\s?(?=\d)/, '');
// "99,90"
// Extrair tag sem incluir "#"
const hashtags = '#dev #seo #ia'.match(/(?<=#)\w+/g);
// ['dev', 'seo', 'ia']
// ATENÇÃO: Safari < 16.4 e Node < 10 não suportam lookbehindSuporte em JavaScript
Lookbehind foi adicionado em ES2018. Hoje funciona em Node 10+, Chrome 62+, Firefox 78+ e Safari 16.4+ (abril/2023). Em ambientes modernos, uso livre. Para código que precisa rodar em Safari muito antigo, use alternativas com grupo capturante.
Backreferences: referenciar grupo dentro da regex
Backreference permite que o padrão se refira a um grupo capturado anteriormente. É como dizer “o mesmo que apareceu antes”:
// Encontrar palavras duplicadas adjacentes
const DUPLICADA = /\b(\w+)\s+\1\b/gi;
'o gato gato subiu'.match(DUPLICADA);
// ['gato gato']
// Detectar tags HTML abertas/fechadas combinando
/<(\w+)>.*?<\/\1>/.test('<p>texto</p>'); // true
/<(\w+)>.*?<\/\1>/.test('<p>texto</div>'); // false (span!)
// Com named groups: \k<nome>
const dup = /(?<w>\w+)\s+\k<w>/g;
'meu meu sapato sapato bonito'.match(dup);
// ['meu meu', 'sapato sapato']
// Encontrar string palíndroma de até 4 chars
/(\w)(\w)\2\1/.test('arara'); // true (a-r-a-r-a padrão)
// ⚠️ Backreferences podem causar catastrophic backtracking
// em regex complexas. Veja mais abaixo.Sumário visual: todos os tipos de grupo
| Critério | Sintaxe | Consome? | Captura? | Uso típico |
|---|---|---|---|---|
| Capturante | (x) | Sim | Sim | Extrair valor |
| Não-capturante | (?:x) | Sim | Não | Agrupar sem capturar |
| Named | (?<nome>x) | Sim | Sim (por nome) | Código legível |
| Lookahead positivo | (?=x) | Não | Não | Verificar à frente |
| Lookahead negativo | (?!x) | Não | Não | Negar à frente |
| Lookbehind positivo | (?<=x) | Não | Não | Verificar atrás |
| Lookbehind negativo | (?<!x) | Não | Não | Negar atrás |
| Atomic (PCRE, não JS) | (?>x) | Sim | Não | Prevenir backtracking |
Casos de uso práticos
1. Reformatação de data
// DD/MM/AAAA → AAAA-MM-DD (ISO)
function toIso(data: string): string {
return data.replace(
/(?<dia>\d{2})\/(?<mes>\d{2})\/(?<ano>\d{4})/,
'$<ano>-$<mes>-$<dia>'
);
}
toIso('18/04/2026'); // "2026-04-18"2. Extrair número de ticket em log
const log = '[2026-04-18] Erro no ticket #1234: timeout no DB';
const match = log.match(/ticket #(\d+)/);
match?.[1]; // "1234"
// Com named group
const m = log.match(/ticket #(?<ticketId>\d+)/);
m?.groups?.ticketId; // "1234"3. Validação + extração de e-mail
const EMAIL = /^(?<local>[^\s@]+)@(?<dominio>[^\s@]+\.[^\s@]+)$/;
function parseEmail(input: string) {
const m = input.trim().toLowerCase().match(EMAIL);
if (!m?.groups) return null;
return {
local: m.groups.local,
dominio: m.groups.dominio,
topLevel: m.groups.dominio.split('.').pop(),
};
}
parseEmail('ana@gmail.com');
// { local: 'ana', dominio: 'gmail.com', topLevel: 'com' }4. Extrair valores monetários
const MOEDA = /(?<simbolo>R\$|US\$)\s?(?<valor>[\d.,]+)/g;
const texto = 'Preço: R$ 99,90 (antes US$ 25.00)';
[...texto.matchAll(MOEDA)].map((m) => m.groups);
// [
// { simbolo: 'R$', valor: '99,90' },
// { simbolo: 'US$', valor: '25.00' }
// ]5. Parsear user-agent simplificado
const UA = /(?<browser>Chrome|Firefox|Safari|Edge)\/(?<version>[\d.]+)/;
const m = 'Mozilla/5.0 Chrome/120.0.0'.match(UA);
m?.groups; // { browser: 'Chrome', version: '120.0.0' }6. Remover código duplicado em diff
// Linhas que começam com + seguidas por idêntica começando com -
const codigo = `
+const x = 1;
-const x = 1;
+const y = 2;
`;
// Remove pares idênticos
codigo.replace(/\+(.+)\n-\1\n/g, '');
// Resta só linhas que realmente mudaram7. Extrair markdown inline
// Links markdown: [texto](url)
const LINK = /\[(?<texto>[^\]]+)\]\((?<url>[^)]+)\)/g;
const md = 'Veja [o artigo](https://exemplo.com/artigo).';
[...md.matchAll(LINK)].map((m) => m.groups);
// [{ texto: 'o artigo', url: 'https://exemplo.com/artigo' }]Diferenças entre JavaScript, Python e Go
Python
import re
# Named group: (?P<nome>...) (Python syntax)
m = re.match(r'(?P<dia>\d{2})/(?P<mes>\d{2})/(?P<ano>\d{4})',
'18/04/2026')
m.group('dia') # '18'
m.groupdict() # {'dia': '18', 'mes': '04', 'ano': '2026'}
# Backreference: \1 ou (?P=nome)
re.search(r'\b(\w+)\s+\1\b', 'gato gato') # match
# Atomic group: (?>...) — Python 3.11+
re.match(r'(?>a+)b', 'aaab') # match
# Possessive quantifier: a++ — Python 3.11+Go (via re2)
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(?P<dia>\d{2})/(?P<mes>\d{2})/(?P<ano>\d{4})`)
m := re.FindStringSubmatch("18/04/2026")
// Por índice
fmt.Println(m[1]) // "18"
// Por nome (via SubexpNames)
for i, nome := range re.SubexpNames() {
if nome != "" { fmt.Printf("%s = %s\n", nome, m[i]) }
}
}
// ATENÇÃO: Go usa engine RE2 que NÃO suporta:
// - Lookahead / lookbehind
// - Backreferences
// - Atomic groups
// Isso é decisão deliberada para garantir complexidade linear.Atomic groups e catastrophic backtracking
Regex com muitos grupos aninhados e quantificadores podem entrar em catastrophic backtracking: o engine testa combinações exponenciais que travam o processo por minutos. Padrões clássicos:
// ❌ ReDoS clássico
const BAD = /^(a+)+$/;
BAD.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!');
// Com 30+ 'a' seguido de !, trava por horas.
// ❌ Outro exemplo
const BAD2 = /^(\w+)*$/;
BAD2.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!');
// ✅ Atomic group previne (PCRE, Python 3.11+)
const GOOD = /^(?>a+)+$/; // matcha ou falha rápido
// ✅ Alternativa em JS: evite quantificador sobre grupo
const SAFE = /^a+$/;
// ✅ Em dúvida, bound input length antes de testar
function testarSeguro(s: string, r: RegExp): boolean {
if (s.length > 200) return false;
return r.test(s);
}Auditoria automática
Em produção, use safe-regex (npm) para auditar se suas regex têm padrões de ReDoS. E sempre limite tamanho do input antes de validar contra entrada não-confiável.
Substituição com função callback
Quando a lógica de substituição é complexa, use função como argumento de replace():
// Converte anos 2 dígitos para 4 dígitos inteligentemente
'Visto em 18/04/26, renovado em 18/04/28'.replace(
/(\d{2})\/(\d{2})\/(\d{2})(?!\d)/g,
(_, dia, mes, ano2) => {
// Anos 00-29 → 2000-2029; 30-99 → 1930-1999
const ano4 = Number(ano2) < 30 ? 2000 + Number(ano2) : 1900 + Number(ano2);
return `${dia}/${mes}/${ano4}`;
}
);
// "Visto em 18/04/2026, renovado em 18/04/2028"
// Com named groups: o objeto groups é o último argumento
texto.replace(
/(?<dia>\d{2})\/(?<mes>\d{2})\/(?<ano>\d{4})/g,
(_match, _p1, _p2, _p3, _offset, _str, groups) => {
const { dia, mes, ano } = groups as Record<string, string>;
return `${ano}-${mes}-${dia}`;
}
);Performance e boas práticas
- Use (?:) sempre que não precisar extrair — poupa memória e índices.
- Named groups em regex com 2+ grupos — legibilidade compensa.
- Compile regex uma vez fora de loops. Em JS, atribua a variável; não crie
new RegExp(...)a cada iteração. - Lookahead/lookbehind quando natural — para forçar tudo num match só, fica mais legível que parsing manual.
- Evite regex muito aninhada — se está ficando monstro, quebre em passos menores.
- Audite contra ReDoS em regex que aceitam input do usuário.
- Prefira funções específicas para parsing estruturado complexo (URL.parse, JSON.parse, libs de data).
Erros comuns com grupos
Anti-padrões frequentes
- Acessar match[1] sem verificar match:
text.match(/x/)retorna null se não matcha. Verifique antes. - Usar grupo capturante só para agrupar: desperdício de memória; use
(?:). - Esquecer flag g em matchAll:
String.matchAllexige flag global ou lança erro. - Backreference numérica em regex com named groups: misturar estilos é fonte de bugs. Padronize.
- Regex complexa sem comentários: use flag
x(não em JS nativo) ou comentário externo. - Pensar que regex resolve tudo: para estrutura complexa (HTML completo, email RFC), use parser dedicado.
Checklist de grupos bem usados
- ✅ Grupos capturantes apenas para valores que você vai usar.
- ✅ Grupos não-capturantes
(?:)para agrupamento puro. - ✅ Named groups em regex com 2+ valores a extrair.
- ✅ Lookahead/lookbehind quando a condição é intrínseca ao match.
- ✅ Backreference para detectar repetições ou pareamento.
- ✅ Regex compilada fora de loops.
- ✅ Flag
gcommatchAll. - ✅ Null-check em
match(). - ✅ Auditoria com safe-regex em input do usuário.
- ✅ Bound de tamanho antes de validar strings não-confiáveis.
Para fundamentos de regex, veja expressões regulares do zero; para uso prático em formulários brasileiros, regex para validação de formulários.
Perguntas frequentes
O que é um grupo de captura em regex?+
Grupo de captura é uma parte da regex delimitada por parênteses () que não apenas matcha uma sequência, mas também ARMAZENA o valor correspondente para uso posterior (no match, em substituição, ou em backreference). Transforma regex de ferramenta de busca em ferramenta de extração de dados.
Qual a diferença entre grupo capturante e não-capturante?+
Grupo capturante () guarda o valor matchado em match[1], match[2] etc. Grupo não-capturante (?:) faz o mesmo agrupamento lógico mas NÃO guarda o valor. Use não-capturante quando só precisa agrupar para aplicar quantificador ou alternativa, sem precisar extrair o valor. Reduz memória e evita índices de grupo confusos em regex complexas.
O que são named groups e quando usar?+
Named groups permitem nomear grupos com (?<nome>...). Acesse via match.groups.nome em vez de match[1]. Vantagens: código mais legível, menos frágil (adicionar grupo no meio não quebra índices), autocompletável em TypeScript. Use sempre que tiver 2+ grupos importantes em uma regex. JavaScript suporta desde ES2018, Python desde sempre, Go com (?P<nome>) via re2.
Qual a diferença entre lookahead e match normal?+
Lookahead (?=...) verifica que um padrão EXISTE à frente mas não consome caracteres no match. Útil para validação condicional: /preço(?= alto)/ matcha "preço" mas SÓ quando seguido de " alto" — e o match retorna apenas "preço", não "preço alto". Permite combinações de condições que regex linear não conseguiria.
Lookbehind funciona em JavaScript?+
Sim, desde ES2018 (Node 10+, navegadores modernos). Lookbehind positivo (?<=...) e negativo (?<!...) funcionam em JavaScript moderno. Safari adicionou em 2020 (iOS 16.4). Para código que precisa rodar em ambientes muito antigos, use alternativas com grupo capturante e manipulação manual.
O que é backreference e quando usar?+
Backreference permite referenciar um grupo capturado ANTES no próprio padrão: \1 refere ao primeiro grupo, \2 ao segundo. Útil para encontrar repetições: /\b(\w+)\s+\1\b/ encontra palavras duplicadas. Para named groups, use \k<nome>. É o que torna regex capaz de matchar padrões recursivos simples (tags HTML balanceadas limitadamente).
Posso usar grupos de captura em replace()?+
Sim. String.replace() aceita $1, $2, ... para referenciar grupos capturados no padrão de substituição. Para named groups, use $<nome>. Exemplo: '18/04/2026'.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$2-$1') → '2026-04-18'. É a forma mais comum de reformatar strings com regex.
O que são grupos atomic e para que servem?+
Grupos atomic (?>...) são grupos não-capturantes que TRAVAM o resultado — uma vez que o grupo matcha, o engine não volta atrás para tentar alternativas. Úteis para prevenir catastrophic backtracking em regex complexas (ReDoS). JavaScript não suporta atomic groups nativamente em 2026 (só via flags experimentais); Python suporta desde 3.11, e engines como PCRE/Boost/Java suportam há muito.
Continue lendo
Expressões Regulares (Regex): Guia Completo 2026 com Exemplos Práticos
Guia definitivo de regex: metacaracteres, quantificadores, classes, grupos, lookaround, flags e padrões prontos em JavaScript e Python. Do básico ao avançado.
O que Torna um E-mail Válido? Regras Técnicas e Validação (2026)
Estrutura conforme RFC 5321/5322, caracteres permitidos, exemplos surpreendentes, regex balanceada vs literal, validação por DNS/SMTP, IDN/Unicode e detecção de e-mails descartáveis.
Validação de Documentos Brasileiros em Formulários (2026): CPF, CNPJ, CEP, Telefone
Guia completo de validação de documentos BR em formulários web: CPF, CNPJ, CEP, telefone, RG, PIS. Máscara em tempo real, regex, frontend vs backend e UX recomendada.
Regex para Validação de Formulários (2026): E-mail, CPF, Telefone, CEP
Coleção de regex prontas para campos brasileiros: e-mail, CPF, CNPJ, telefone, CEP, URL, data, senha forte, cartão. Inclui máscara em tempo real, integração React e ReDoS.