Expresiones regulares para números, teléfonos y direcciones IP
Patrones listos para copiar para entradas numéricas, números de teléfono y direcciones IP, todos probados, con notas sobre cuándo un parser o una biblioteca hace mejor el trabajo.
Los números, los teléfonos y las direcciones IP parecen objetivos fáciles para una regex porque son casi todo dígitos. Cada uno esconde una trampa: \d significa cosas distintas según el lenguaje, los teléfonos siguen reglas nacionales que ningún patrón puede recoger y una dirección IP solo es válida si cada una de sus partes está dentro de rango. Los patrones siguientes se probaron en Node.js salvo que se indique otro motor, y cada ejemplo aceptado o rechazado es uno que ejecutamos.
Solo números
| Lo que busca | Patrón | Acepta | Rechaza |
|---|---|---|---|
| Solo dígitos | ^[0-9]+$ | 123, 007 | -42, 3.14, un espacio inicial, una entrada vacía |
| Entero con signo opcional | ^[+-]?[0-9]+$ | -42, +42, 007 | 3.14, 1e3 |
| Entero sin ceros a la izquierda | ^-?(?: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 al estilo JSON | ^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$ | 1.5E-7, -0 | .5, 01.5, +42 |
| Separador de miles con coma y punto decimal | ^[0-9]{1,3}(?:,[0-9]{3})*(?:\.[0-9]+)?$ | 1,000, 12,345,678.90, 100 | 1000, 1,00 |
Una regex comprueba la forma, no el valor. Para un rango como de 1 a 100, compruebe los dígitos con la regex y después compare el número en código. Tampoco se salte la regex confiando solo en la conversión: en JavaScript, Number('') es 0, Number(' 42 ') es 42, Number('0x1A') es 26 y parseInt('12abc') es 12.
¿\d solo coincide con los dígitos del 0 al 9?
No en todas partes. Probamos ^\d+$ con los dígitos arábigo-índicos ٣٤:
| Motor | ¿Coincidió con dígitos no ASCII? | Para limitarlo a 0–9 |
|---|---|---|
| JavaScript | No, ni siquiera con la bandera u | No hace falta nada; \p{Nd} con u coincide con todos los dígitos decimales |
| Python 3 (patrones str) | Sí, también con los de ancho completo 12 | [0-9] o la bandera re.ASCII |
| PHP (PCRE) | No; sí con el modificador u | [0-9] |
| .NET | Sí | [0-9] o RegexOptions.ECMAScript |
| Perl 5 | Sí | [0-9] o el modificador /a |
Esto importa cuando un valor pasa la regex y llega a un código que lo interpreta de otra manera. En Python, int('٣٤') devuelve 34, mientras que una columna de base de datos u otro servicio pueden rechazarlo. Escriba [0-9] cuando quiera decir dígitos ASCII: significa lo mismo en todos los motores.
Números de teléfono
El único formato con una definición internacional sencilla es E.164: un signo más, un código de país que no empieza por 0 y, como máximo, 15 dígitos en total.
^\+[1-9]\d{1,14}$ E.164, signo más obligatorio (lo mejor para almacenar)
^\+?[1-9]\d{1,14}$ signo más opcional
Con el signo más obligatorio, +14155552671 y +442071838750 pasan, mientras que +0123456789, un número de 16 dígitos y 004914155552671 (escrito con el prefijo internacional 00) fallan. La gente escribe espacios, puntos, guiones y paréntesis, así que elimínelos primero con value.replace(/[\s().-]/g, ''); así, +1 415 555 2671 se convierte en +14155552671.
Los límites aparecen enseguida. El patrón solo comprueba la forma, así que +12 pasa aunque ningún número real sea tan corto. Peor aún: +49 (0)30 1234567 queda como +490301234567 tras la limpieza y pasa, pero es un número equivocado, porque el 0 entre paréntesis es un prefijo de marcación nacional que hay que quitar al llamar desde el extranjero.
Los formatos nacionales no salen mejor parados. El patrón habitual para Estados Unidos, ^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$, aceptó (415-555-2671, con un paréntesis sin cerrar, y 000-000-0000, que no puede ser un número real. Cada país tiene sus propias longitudes, prefijos y rangos de móviles, y además cambian. Para validar y dar formato de verdad, use libphonenumber de Google o uno de sus ports: libphonenumber-js para JavaScript, phonenumbers para Python y giggsey/libphonenumber-for-php para PHP. Guarde el resultado en E.164 y, si es importante, confirme la titularidad con un código por SMS.
Direcciones IPv4
El atajo popular ^\d{1,3}(?:\.\d{1,3}){3}$ aceptó 256.1.1.1 y 999.999.999.999. Para limitar cada parte a 0–255, hay que escribir los rangos uno a uno:
^(?:(?: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)$
Comprobamos la parte del octeto con todos los números de 0 a 999 escritos con uno, dos y tres dígitos, y el patrón completo con 200 000 direcciones aleatorias: coincidió siempre con net.isIPv4() de Node. Acepta 0.0.0.0, 192.168.1.1 y 255.255.255.255, y rechaza 192.168.1, 192.168.1.1.1 y los ceros a la izquierda, como en 192.168.01.1.
Rechazar los ceros a la izquierda es deliberado. Algunos parsers los interpretan como octal: socket.inet_aton('010.0.0.1') de Python nos devolvió la dirección 8.0.0.1. net.isIPv4() de Node, ipaddress.ip_address() de Python y filter_var($ip, FILTER_VALIDATE_IP) de PHP también rechazaron 192.168.01.1.
Para encontrar direcciones dentro de una línea de log, quite ^ y $, añada \b a ambos lados y use la bandera g. Con from 10.0.0.5 to 192.168.1.300 via 8.8.8.8 devolvió 10.0.0.5 y 8.8.8.8. Sin \b, el texto 1192.168.1.300x produjo una coincidencia falsa: 192.168.1.30. Queda una limitación: en 1.2.3.4.5, la versión con \b sigue encontrando 1.2.3.4.
Direcciones IPv6: use un parser
IPv6 permite que :: sustituya una serie de grupos de ceros, admite ceros a la izquierda opcionales, IPv4 incrustada como ::ffff:192.0.2.1 e identificadores de zona como fe80::1%eth0. Una regex que cubra todo eso es larga y fácil de equivocar de forma sutil. Un parser es más corto y está mejor probado:
- Node.js:
net.isIPv6(value), onet.isIP(value), que devuelve 4, 6 o 0. Ambos aceptaron IPv4 incrustada e identificadores de zona. - Python:
ipaddress.ip_address(value)lanzaValueErrorsi la dirección no es válida. Aceptó IPv4 incrustada e identificadores de zona. - PHP:
filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6). Aceptó IPv4 incrustada, pero rechazófe80::1%eth0. - Navegadores: no hay un validador de IP integrado, así que use una biblioteca como
ipaddr.js.
Si aun así necesita una regex, por ejemplo en un esquema o en un archivo de configuración que solo admite eso, este patrón de 408 caracteres cubre las formas hexadecimales y rechaza a propósito la IPv4 incrustada y los identificadores de zona. Con 500 000 cadenas generadas y mutadas, sin puntos ni signos de porcentaje, coincidió con net.isIPv6() en todas ellas.
^(?:(?:[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}|:))$
Errores que dejan pasar entradas incorrectas
- Faltan las anclas.
/\d+/.test('abc123')estrueporque encuentra dígitos en cualquier parte. Para validar hacen falta^y$. - Alternancia fuera de un grupo.
^cat|dog$significa «empieza por cat o termina en dog», así que coincidió concats and dogs!. Escriba^(?:cat|dog)$. - La bandera
m. Con ella,^y$coinciden en cada línea, así que/^\d+$/maceptó la cadena de dos líneas"abc\n123". No use nuncampara validar un único valor. $antes de un salto de línea final. En Python, PHP y .NET,^\d+$aceptó"42\n". Usere.fullmatch()en Python, el modificadorDen PHP o\zen PHP y .NET. JavaScript lo rechazó.- La bandera
gcontest(). Una regex congrecuerdalastIndex, así que llamar tres veces a/^\d+$/g.test('42')sobre el mismo objeto devolvió true, false, true. - Alternativas sin anclar en el orden equivocado. Al buscar en
255.255.255.255con el octeto escrito como(?:\d|1\d\d|2[0-4]\d|25[0-5])solo se obtuvo255.255.255.2, porque el motor se queda con la primera alternativa que funciona. Ponga primero las alternativas más largas o ancle el patrón.
Pruebe su patrón antes de publicarlo
Pegue un patrón en el probador de expresiones regulares sin las barras, ponga las banderas gm y escriba sus ejemplos, uno por línea: los válidos, los inválidos y los casos incómodos de esta página. Cada línea que coincide se resalta. El probador usa el motor JavaScript de su navegador, así que repase las diferencias de \d y $ descritas arriba si el patrón va a ejecutarse en Python, PHP o .NET. ¿Lo que quiere validar son direcciones de correo? Consulte Regex para validar email.