Expressões regulares
Classes, âncoras, quantificadores, alternância, capturas, referências, lookaround e backtracking.
Encontrar LTW-042 numa frase e validar um campo que deve conter apenas esse código são tarefas diferentes. Na pesquisa, texto antes ou depois pode ser permitido. Na validação, xLTW-042 tem de falhar, mesmo contendo um código reconhecível.
Peças e precedência
Em LTW-042, precisamos de reconhecer o prefixo, o hífen e os dígitos. [0-9] aceita um dígito, e [0-9]{3} exige três. As âncoras limitam a posição em que o padrão pode casar. A tabela reúne essas peças e as outras operações usadas abaixo.
| Padrão | Significado habitual |
|---|---|
abc | Os caracteres literais nessa ordem |
[abc], [0-9] | Um caráter do conjunto ou intervalo |
[^0-9] | Um caráter fora do conjunto |
. | Um caráter, habitualmente excluindo mudanças de linha |
\d, \w, \s | Classes abreviadas de dígito, palavra e espaço |
^, $ | Posições de início e fim, com pormenores dependentes do motor e flags |
\b | Fronteira de palavra, sem consumir caracteres |
a?, a*, a+ | Zero ou um, zero ou mais, um ou mais a |
a{3}, a{2,4} | Exatamente três, ou entre dois e quatro a |
a|b | Alternativa, quando a barra não está escapada no padrão |
(ab) | Grupo que captura |
(?:ab) | Grupo sem captura |
As abreviaturas e o tratamento de Unicode dependem do motor; [A-Za-z] não representa todas as letras portuguesas. Quantificadores aplicam-se à peça anterior: ab+ exige um a e um ou mais b; (ab)+ repete o par.
Alternância tem baixa precedência. Em ^LTW|BD$, as âncoras prendem-se a cada alternativa separadamente: o padrão aceita entradas que comecem por LTW ou terminem por BD. Para prender ambas as alternativas às pontas, agrupa primeiro: ^(?:LTW|BD)$.
O vídeo mostra classes, grupos e quantificadores. Pausa cada exemplo para prever a correspondência antes de a veres.
Construir e testar um formato
Queremos aceitar LTW-042 ou BD-007, com três dígitos no fim, e capturar esses dígitos. As peças são início, grupo de prefixos, hífen, grupo de três dígitos e fim:
/^(?:LTW|BD)-([0-9]{3})$/;
O primeiro grupo só organiza alternativas. O segundo captura e, por ser o primeiro grupo capturante, fica em índice 1. O índice 0 é sempre a correspondência completa.
const codigo = /^(?:LTW|BD)-([0-9]{3})$/;
for (const texto of ['LTW-042', 'BD-007', 'LTW-42', 'xLTW-042']) {
const resultado = texto.match(codigo);
console.log(texto + ': ' + (resultado ? resultado[1] : 'inválido'));
}
const frase = 'A <b>rede</b> e <i>web</i>.';
console.log(frase.match(/<.*>/)[0]);
console.log(frase.match(/<.*?>/)[0]);
console.log('livro livro novo'.match(/\b(\w+)\s+\1\b/)[0]);Executa o código para ver a saída e os gráficos.
A primeira parte escreve:
LTW-042: 042
BD-007: 007
LTW-42: inválido
xLTW-042: inválido
Dois dígitos falham a repetição e o x inicial falha a âncora. Isto verifica o formato, não prova que o código exista no catálogo. Para dados externos, define também limites de comprimento e regras semânticas.
Guloso, preguiçoso e primeiro resultado
No texto A <b>rede</b> e <i>web</i>., <.*> escolhe <b>rede</b> e <i>web</i>. O * guloso tenta consumir tanto quanto possível e depois recua até conseguir o > final. É por isso que a correspondência atravessa a primeira etiqueta de fecho.
<.*?> escolhe primeiro <b>: o quantificador preguiçoso tenta o mínimo e expande se o restante padrão não puder casar. <[^>]*> também pára antes do primeiro >, porque a classe negada nunca consome esse caráter. Estes padrões servem para observar quantificadores, mas não são parsers seguros de HTML: aspas, comentários e estrutura aninhada exigem um parser.
Ao pedir a primeira correspondência, o motor começa na primeira posição em que o padrão pode casar. “Guloso” não significa escolher uma posição inicial mais à direita para obter um resultado maior. Flags como g em JavaScript pedem correspondências globais; i ignora diferenças de maiúsculas conforme as regras do motor; m altera as âncoras para linhas; s permite que . inclua quebras de linha.
Capturas e referências
Uma referência \1 exige o mesmo texto que o primeiro grupo capturou:
/\b(\w+)\s+\1\b/.exec('livro livro novo')[0]; // livro livro
Não exige “outra palavra qualquer”; exige livro de novo. Numa substituição JavaScript, $1 refere a primeira captura:
'2026-10-02'.replace(/^(\d{4})-(\d{2})-(\d{2})$/, '$3/$2/$1'); // 02/10/2026
O padrão verifica a forma numérica, não que o dia exista no calendário. 2026-02-31 também tem a forma certa. Valida a data com regras de calendário quando esse é o objetivo.
Lookaround e backtracking
Lookaround verifica contexto sem o incluir na correspondência. livro(?=s) escolhe livro apenas quando vem seguido de s; livro(?!s) exige que não venha s. (?<=EUR )[0-9]+ encontra dígitos precedidos de EUR ; o prefixo não faz parte do resultado. Lookbehind e algumas regras têm limites de compatibilidade entre motores.
Backtracking é a tentativa de alternativas após uma escolha não permitir acabar o padrão. Em a.*b, o motor pode consumir até ao fim e recuar à procura de um b final. Padrões com repetições ambíguas, como (a+)+$, podem exigir muitas tentativas numa entrada quase válida. Limita a entrada e evita estruturas com custo explosivo em dados controlados pelo cliente.
Expressões regulares clássicas correspondem a linguagens reconhecíveis por autómatos finitos. Motores práticos incluem extensões, como referências a capturas, que ultrapassam esse modelo. Não uses a palavra “regular” para assumir que qualquer padrão de um motor moderno tem custo simples ou expressividade idêntica.
JavaScript, PHP e HTML
Em JavaScript, test devolve booleano e exec/match permitem obter capturas. match sem g devolve a primeira correspondência e as capturas; com g, devolve as correspondências completas, sem as capturas de cada uma. Para obter capturas de todas, usa matchAll com g.
const texto = 'LTW-042 e BD-007';
for (const resultado of texto.matchAll(/(?:LTW|BD)-([0-9]{3})/g)) {
console.log(resultado[1]); // 042, depois 007
}
Um objeto regex com flag g conserva lastIndex entre chamadas de test: /a/g sobre 'a' dá true na primeira chamada e false na segunda com o mesmo objeto. Evita g numa validação repetida sem controlar esse estado.
Em PHP, os padrões PCRE precisam de delimitadores. preg_match devolve 1 para correspondência, 0 para ausência e false para erro. Para extrair todas as ocorrências usa preg_match_all, que preenche um array com a correspondência completa e as capturas. Para substituir usa preg_replace, com $1 para a primeira captura no texto de substituição. Para exigir o texto completo de forma estrita em PCRE, \A e \z fixam as pontas sem o caso especial de uma quebra de linha final de $:
<?php
$valido = preg_match('/\A(?:LTW|BD)-[0-9]{3}\z/', $codigo) === 1;
No HTML, pattern recebe o padrão sem /.../ e aplica-o ao valor completo nos tipos suportados. pattern="(?:LTW|BD)-[0-9]{3}" não exige sozinho uma entrada não vazia; acrescenta required se for obrigatória. A sintaxe atual usa regras Unicode próprias, por isso confirma compatibilidade ao usar classes avançadas. O servidor tem de validar novamente, independentemente do formulário.
Exercícios
Prever a primeira correspondência
No texto preço [12] e [35], escreve a primeira correspondência de /\[.*\]/ e /\[.*?\]/. Propõe uma classe negada que pare no primeiro fecho.
Pista
O início é o primeiro [ onde o padrão pode casar.
Mais uma pista
Guloso tenta o máximo; preguiçoso tenta o mínimo que permite acabar.
Solução
O guloso produz uma única correspondência, [12] e [35], do primeiro [ ao último ]. O preguiçoso produz [12]. Uma alternativa é /\[[^\]]*\]/, que admite apenas caracteres diferentes de ] dentro dos colchetes. A escolha gulosa não começa automaticamente no segundo [ para obter outra correspondência.
Confere a tua resposta:
- Obtive os dois resultados distintos.
- Defini a classe que exclui o fecho.
Erros frequentes
Não esquecer que * admite zero caracteres nem confundir primeiro início com menor resultado.
Exigir a mesma palavra
Compara /\b(\w+)\s+\1\b/ e /\b\w+\s+\w+\b/ sobre rede web rede rede. Qual o primeiro resultado de cada?
Pista
A referência exige o texto da primeira captura.
Solução
O padrão com referência tenta primeiro em cada posição inicial. Em rede web, a captura é rede mas o texto seguinte é web, por isso falha. Em web rede, a captura é web e o seguinte é rede, por isso falha. Em rede rede no fim, a repetição confirma-se. O segundo padrão só exige duas palavras e escolhe rede web logo no início. A correspondência global não foi pedida, por isso paramos no primeiro resultado de cada padrão.
Confere a tua resposta:
- Distingui identidade de formato de palavra.
- Indiquei o primeiro resultado de cada.
Erros frequentes
Não tratar \1 como um novo grupo livre nem escolher a ocorrência repetida final em ambos.
Validar formato e significado
Uma aplicação extrai datas com /\b\d{4}-\d{2}-\d{2}\b/ de Entrega: 2026-02-31; próxima: 2026-03-01. Usa depois (?!;) para recusar uma correspondência seguida imediatamente de ponto e vírgula. Escreve o novo padrão, prevê o resultado de cada versão e explica que validação ainda falta antes de aceitar uma data.
Pista
Segue primeiro a correspondência completa; só depois verifica o caráter seguinte.
Mais uma pista
Coloca (?!;) depois da fronteira final. O mês e o dia ainda são apenas pares de dígitos.
Solução
O padrão original encontra primeiro 2026-02-31. Com /\b\d{4}-\d{2}-\d{2}\b(?!;)/, essa ocorrência falha porque vem seguida de ;; a primeira correspondência passa a ser 2026-03-01. O lookahead não consome caracteres. A fronteira final impede aceitar apenas um prefixo dos dígitos para fugir ao teste do separador. Nenhuma versão valida o calendário: fevereiro não tem 31 dias. Depois de extrair, verifica mês, dia e ano, incluindo anos bissextos.
Confere a tua resposta:
- Usei uma asserção que não consome o separador e obtive 2026-03-01.
- Distingui extração, contexto e validade da data.
Erros frequentes
Não substituir a asserção por uma classe que consome o separador, nem aceitar uma data só porque tem o formato certo.
Devolver a captura ou ausência
Define numeroCodigo(texto): recebe uma string sem quebras de linha. Se toda a string tiver prefixo LTW ou BD, hífen e exatamente três dígitos de 0 a 9, devolve os dígitos como string. Caso contrário, devolve null. Chamadas repetidas devem dar o mesmo resultado.
function numeroCodigo(texto) {
return null;
}Verifica o código para comparar o resultado com os testes.
Testes
Zeros e ambos os prefixos
Código do testeconsole.log(JSON.stringify(numeroCodigo('LTW-042'))); console.log(JSON.stringify(numeroCodigo('BD-007')));Esperado:"042" "007"
Comprimento e prefixo inválidos
Código do testeconsole.log(numeroCodigo('LTW-42')); console.log(numeroCodigo('LTW-0042')); console.log(numeroCodigo('IA-042')); console.log(numeroCodigo('ltw-042'));Esperado:null null null null
Entrada completa
Código do testeconsole.log(numeroCodigo('xLTW-042')); console.log(numeroCodigo('LTW-042x')); console.log(numeroCodigo(''));Esperado:null null null
Validação repetida
Código do testeconsole.log(JSON.stringify(numeroCodigo('BD-000'))); console.log(JSON.stringify(numeroCodigo('BD-000')));Esperado:"000" "000"
console.log(JSON.stringify(numeroCodigo('LTW-042')));
console.log(JSON.stringify(numeroCodigo('BD-007')));"042" "007"
console.log(numeroCodigo('LTW-42'));
console.log(numeroCodigo('LTW-0042'));
console.log(numeroCodigo('IA-042'));
console.log(numeroCodigo('ltw-042'));null null null null
console.log(numeroCodigo('xLTW-042'));
console.log(numeroCodigo('LTW-042x'));
console.log(numeroCodigo(''));null null null
console.log(JSON.stringify(numeroCodigo('BD-000')));
console.log(JSON.stringify(numeroCodigo('BD-000')));"000" "000"
Pista
Mais uma pista
null quando não há correspondência. O índice 1 contém a primeira captura.Solução
function numeroCodigo(texto) {
const resultado = texto.match(/^(?:LTW|BD)-([0-9]{3})$/);
return resultado === null ? null : resultado[1];
}exec sem flag global também resolve. Uma validação manual que cumpra o mesmo contrato passa os testes. Preserva os zeros, porque a captura é texto.
Erros frequentes
null com a string 'null'.