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ê quer | Padrão | Aceita | Rejeita |
|---|---|---|---|
| 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, 007 | 3.14, 1e3 |
| Inteiro sem zeros à esquerda | ^-?(?:0|[1-9][0-9]*)$ | 0, -42 | 007, +42 |
| Decimal | ^[+-]?(?:[0-9]+(?:\.[0-9]+)?|\.[0-9]+)$ | 3.14, -0.5, .5 | 5., 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, 100 | 1000, 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 ٣٤:
| Motor | Casou dígitos fora do ASCII? | Para restringir a 0–9 |
|---|---|---|
| JavaScript | Não, nem com a flag u | Nada 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] |
| .NET | Sim | [0-9] ou RegexOptions.ECMAScript |
| Perl 5 | Sim | [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), ounet.isIP(value), que retorna 4, 6 ou 0. Os dois aceitaram IPv4 embutido e identificadores de zona. - Python:
ipaddress.ip_address(value)lançaValueErrorquando 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 rejeitoufe80::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 comcats and dogs!. Escreva^(?:cat|dog)$. - A flag
m. Com ela,^e$casam em cada linha, então/^\d+$/maceitou a string de duas linhas"abc\n123". Nunca usempara validar um valor único. $antes de uma quebra de linha final. Em Python, PHP e .NET,^\d+$aceitou"42\n". Usere.fullmatch()em Python, o modificadorDem PHP, ou\zem PHP e .NET. O JavaScript rejeitou.- A flag
gcomtest(). Uma regex comgguarda olastIndex, 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.255com 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.