Artigo Build·Desenvolvimento·13 min de leitura

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.

Vitor Morais

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 match

Grupos 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'); // true

Benefí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;
Grupos numerados vs named groups
CritérioNumeradosNamed
Acessomatch[1]match.groups.nome
LeituraPrecisa contar gruposDescritivo por si
RefatoraçãoAdicionar grupo quebra índicesImune a adições
Tipagem TSanyInferível via template
Em replace$1, $2$<nome>
Suporte JSSempreES2018 (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ções

Lookbehind: 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 lookbehind

Suporte 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

Todos os tipos de grupo em regex JavaScript moderno
CritérioSintaxeConsome?Captura?Uso típico
Capturante(x)SimSimExtrair valor
Não-capturante(?:x)SimNãoAgrupar sem capturar
Named(?<nome>x)SimSim (por nome)Código legível
Lookahead positivo(?=x)NãoNãoVerificar à frente
Lookahead negativo(?!x)NãoNãoNegar à frente
Lookbehind positivo(?<=x)NãoNãoVerificar atrás
Lookbehind negativo(?<!x)NãoNãoNegar atrás
Atomic (PCRE, não JS)(?>x)SimNãoPrevenir 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 mudaram

7. 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.matchAll exige 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 g com matchAll.
  • ✅ 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.

#regex#grupos captura#named groups#lookahead#lookbehind#backreference#javascript#python#go#redos

Continue lendo