Regex para números, telefones e endereços IP

Padrões prontos para copiar para entradas numéricas, telefones e endereços IP, todos testados, com notas sobre quando um parser ou uma biblioteca resolve melhor.

Números, telefones e endereços IP parecem alvos fáceis para uma regex, porque são quase só dígitos. Cada um tem sua armadilha: \d significa coisas diferentes em linguagens diferentes, telefones seguem regras nacionais que nenhum padrão consegue capturar, e um endereço IP só é válido se cada parte estiver dentro da faixa permitida. Os padrões abaixo foram testados no Node.js, salvo quando outro motor é citado, e cada exemplo aceito ou rejeitado é um caso que realmente rodamos.

Somente números

O que você querPadrãoAceitaRejeita
Só dígitos^[0-9]+$123, 007-42, 3.14, um espaço no início, entrada vazia
Inteiro, sinal opcional^[+-]?[0-9]+$-42, +42, 0073.14, 1e3
Inteiro sem zeros à esquerda^-?(?:0|[1-9][0-9]*)$0, -42007, +42
Decimal^[+-]?(?:[0-9]+(?:\.[0-9]+)?|\.[0-9]+)$3.14, -0.5, .55., 1e3, 1,000
Número no estilo JSON^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$1.5E-7, -0.5, 01.5, +42
Separador de milhar com vírgula^[0-9]{1,3}(?:,[0-9]{3})*(?:\.[0-9]+)?$1,000, 12,345,678.90, 1001000, 1,00

Uma regex verifica o formato, não o valor. Para uma faixa como de 1 a 100, case os dígitos e depois compare o número no código. E não pule a regex confiando só na conversão: em JavaScript, Number('') dá 0, Number(' 42 ') dá 42, Number('0x1A') dá 26 e parseInt('12abc') dá 12.

O \d casa só de 0 a 9?

Não em todo lugar. Testamos ^\d+$ com os dígitos arábico-índicos ٣٤:

MotorCasou dígitos fora do ASCII?Para restringir a 0–9
JavaScriptNão, nem com a flag uNada a fazer; \p{Nd} com u casa todos os dígitos decimais
Python 3 (padrões str)Sim, inclusive os de largura total 12[0-9] ou a flag re.ASCII
PHP (PCRE)Não; sim com o modificador u[0-9]
.NETSim[0-9] ou RegexOptions.ECMAScript
Perl 5Sim[0-9] ou o modificador /a

Isso importa quando um valor passa pela regex e depois chega a um código que o interpreta de outro jeito. int('٣٤') em Python retorna 34, enquanto uma coluna de banco de dados ou outro serviço pode recusar o mesmo valor. Escreva [0-9] quando quiser dígitos ASCII; isso significa a mesma coisa em todos os motores.

Números de telefone

O único formato com uma definição simples e internacional é o E.164: um sinal de mais, um código de país que não começa com 0 e no máximo 15 dígitos no total.

^\+[1-9]\d{1,14}$     E.164, sinal de mais obrigatório (melhor para armazenar)
^\+?[1-9]\d{1,14}$    sinal de mais opcional

Com o mais obrigatório, +14155552671 e +442071838750 passam, enquanto +0123456789, um número de 16 dígitos e 004914155552671 (escrito com o prefixo internacional 00) falham. As pessoas digitam espaços, pontos, hifens e parênteses, então remova tudo isso antes com value.replace(/[\s().-]/g, ''); assim +1 415 555 2671 vira +14155552671.

Os limites aparecem rápido. O padrão só verifica o formato, então +12 passa, embora nenhum número real seja tão curto. Pior: +49 (0)30 1234567 vira +490301234567 depois da limpeza e passa, mas é o número errado: o 0 entre parênteses é um prefixo de longa distância nacional que precisa ser descartado quando se liga do exterior.

Os formatos nacionais não são melhores. O padrão americano comum ^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$ aceitou (415-555-2671, com um parêntese sem fechar, e 000-000-0000, que não pode ser um número real. Cada país tem seus próprios comprimentos, prefixos e faixas de celular, e eles mudam. Para validar e formatar de verdade, use a libphonenumber do Google ou uma de suas versões: libphonenumber-js para JavaScript, phonenumbers para Python, giggsey/libphonenumber-for-php para PHP. Guarde o resultado em E.164 e, se a titularidade importar, confirme com um código por SMS.

Endereços IPv4

O atalho popular ^\d{1,3}(?:\.\d{1,3}){3}$ aceitou 256.1.1.1 e 999.999.999.999. Para limitar cada parte a 0–255, escreva as faixas por extenso:

^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$

Testamos a parte do octeto com todos os números de 0 a 999 escritos com um, dois e três dígitos, e o padrão completo com 200.000 endereços aleatórios; o resultado bateu com o net.isIPv4() do Node em todos os casos. Ele aceita 0.0.0.0, 192.168.1.1 e 255.255.255.255, e rejeita 192.168.1, 192.168.1.1.1 e zeros à esquerda como em 192.168.01.1.

Rejeitar zeros à esquerda é proposital. Alguns parsers os leem como octal: socket.inet_aton('010.0.0.1') em Python nos devolveu o endereço 8.0.0.1. O net.isIPv4() do Node, o ipaddress.ip_address() do Python e o filter_var($ip, FILTER_VALIDATE_IP) do PHP também rejeitaram 192.168.01.1.

Para encontrar endereços dentro de uma linha de log, tire ^ e $, acrescente \b dos dois lados e use a flag g. Em from 10.0.0.5 to 192.168.1.300 via 8.8.8.8 isso retornou 10.0.0.5 e 8.8.8.8. Sem \b, o texto 1192.168.1.300x produziu uma correspondência falsa, 192.168.1.30. Resta uma limitação: em 1.2.3.4.5, a versão com \b ainda casa 1.2.3.4.

Endereços IPv6: use um parser

O IPv6 permite que :: substitua uma sequência de grupos zerados, zeros à esquerda opcionais, IPv4 embutido como ::ffff:192.0.2.1 e identificadores de zona como fe80::1%eth0. Uma regex que cubra tudo isso é longa e fácil de errar em detalhes. Um parser é mais curto e mais bem testado:

  • Node.js: net.isIPv6(value), ou net.isIP(value), que retorna 4, 6 ou 0. Os dois aceitaram IPv4 embutido e identificadores de zona.
  • Python: ipaddress.ip_address(value) lança ValueError quando o valor é inválido. Aceitou IPv4 embutido e identificadores de zona.
  • PHP: filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6). Aceitou IPv4 embutido, mas rejeitou fe80::1%eth0.
  • Navegadores: não há validador de IP nativo, então use uma biblioteca como ipaddr.js.

Se mesmo assim você precisar de uma regex, por exemplo em um schema ou arquivo de configuração que só aceita isso, este padrão de 408 caracteres cobre as formas hexadecimais e rejeita de propósito IPv4 embutido e identificadores de zona. Em 500.000 strings geradas e mutadas, sem pontos nem sinais de porcentagem, ele concordou com net.isIPv6() em todas.

^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:))$

Erros que deixam entrada ruim passar

  • Faltam as âncoras. /\d+/.test('abc123') é true, porque encontra dígitos em qualquer posição. Validação precisa de ^ e $.
  • Alternância fora de um grupo. ^cat|dog$ significa "começa com cat ou termina com dog", então casou com cats and dogs!. Escreva ^(?:cat|dog)$.
  • A flag m. Com ela, ^ e $ casam em cada linha, então /^\d+$/m aceitou a string de duas linhas "abc\n123". Nunca use m para validar um valor único.
  • $ antes de uma quebra de linha final. Em Python, PHP e .NET, ^\d+$ aceitou "42\n". Use re.fullmatch() em Python, o modificador D em PHP, ou \z em PHP e .NET. O JavaScript rejeitou.
  • A flag g com test(). Uma regex com g guarda o lastIndex, então chamar /^\d+$/g.test('42') três vezes no mesmo objeto retornou true, false, true.
  • Alternativas sem âncora na ordem errada. Buscar em 255.255.255.255 com o octeto escrito como (?:\d|1\d\d|2[0-4]\d|25[0-5]) casou só 255.255.255.2, porque o motor fica com a primeira alternativa que funciona. Coloque as alternativas mais longas primeiro ou ancore o padrão.

Teste o padrão antes de colocar em produção

Cole um padrão no Testador de Expressões Regulares sem as barras, defina as flags como gm e digite seus exemplos um por linha: os válidos, os inválidos e os casos estranhos desta página. Cada linha que corresponde fica destacada. O testador usa o motor JavaScript do seu navegador, então revise as diferenças de \d e $ descritas acima se o padrão for rodar em Python, PHP ou .NET. Quer validar endereços de e-mail? Veja Regex para validar e-mail.

Perguntas Frequentes

Qual regex aceita somente números?

Use ^[0-9]+$ para aceitar só dígitos, ou ^[+-]?[0-9]+$ para permitir sinal. Prefira [0-9] a \d, porque em Python, em .NET e no PHP com o modificador u o \d também casa dígitos de outros sistemas de escrita.

Qual regex valida telefones internacionais?

Para o formato E.164, ^\+[1-9]\d{1,14}$ depois de remover espaços, pontos, hifens e parênteses. Ela só verifica o formato; para saber se um número é realmente válido no país dele, use a libphonenumber ou uma de suas versões.

Qual é a regex para um endereço IPv4 válido?

^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$ limita cada parte a 0–255 e rejeita zeros à esquerda. Para IPv6, use um parser como net.isIPv6 no Node.js ou ipaddress no Python.

Por que minha regex aceita uma entrada com quebra de linha no final?

Em Python, PHP e .NET, o $ também casa logo antes de uma quebra de linha final. Use re.fullmatch em Python, o modificador D em PHP, ou \z no lugar de $.