regex test debug developer-tools regexp

Probador de Regex: La Guía Esencial para la Depuración de Expresiones Regulares

Simplifica el desarrollo de tus expresiones regulares con nuestro probador en tiempo real. Incluye coincidencias en vivo y fragmentos comunes.

Introducción

Las expresiones regulares — comúnmente llamadas regex o regexp — son secuencias de caracteres que definen un patrón de búsqueda. Son una de las herramientas más poderosas en el arsenal de cualquier desarrollador, permitiendo buscar, validar, extraer y transformar texto con una sola expresión compacta.

Ya sea validando una dirección de correo electrónico en un formulario web, extrayendo datos de registros de logs, o realizando un complejo buscar-y-reemplazar en miles de archivos, las expresiones regulares te permiten expresar exactamente lo que buscas en una notación concisa y portable que entiende prácticamente cualquier lenguaje de programación y la mayoría de los editores de texto.


Breve Historia de las Expresiones Regulares

La historia de las expresiones regulares se remonta a los fundamentos de la ciencia de la computación teórica:

  • 1951 — El matemático Stephen Kleene formaliza el concepto de lenguajes regulares e introduce la notación de la estrella de Kleene (*) como parte de su trabajo en teoría de autómatas.
  • 1968Ken Thompson implementa expresiones regulares en el editor de texto QED y luego en herramientas Unix como grep, sed y awk.
  • 1986POSIX estandariza dos variantes: BRE (Expresiones Regulares Básicas) y ERE (Expresiones Regulares Extendidas).
  • 1997Philip Hazel crea la librería PCRE (Perl Compatible Regular Expressions), que se convierte en el estándar de facto para regex avanzado.
  • 1999ECMAScript 3 estandariza el objeto RegExp de JavaScript.
  • 2015ES6 añade las banderas u (Unicode) e y (sticky).
  • 2018ES2018 añade grupos de captura nombrados ((?<nombre>...)) y aserciones lookbehind.

POSIX vs PCRE vs JavaScript RegExp

Característica BRE/ERE (POSIX) PCRE JavaScript RegExp
Lookahead
Lookbehind ✓ (ES2018+)
Grupos nombrados ✓ (ES2018+)
Coincidencia no voraz
Unicode Limitado ✓ (con bandera u)
Retroreferencias

Referencia de Sintaxis Core

Tabla de Referencia Rápida

Patrón Significado
. Cualquier carácter excepto salto de línea
^ Inicio de cadena / inicio de línea (con bandera m)
$ Fin de cadena / fin de línea (con bandera m)
\d Cualquier dígito [0-9]
\D Cualquier no-dígito
\w Carácter de palabra [a-zA-Z0-9_]
\W Carácter de no-palabra
\s Espacio en blanco (espacio, tabulador, salto de línea…)
\S Carácter que no es espacio en blanco
[abc] Clase de caracteres — coincide con a, b o c
[^abc] Clase negada — coincide con cualquier cosa excepto a, b, c
[a-z] Rango de caracteres
* 0 o más veces (voraz)
+ 1 o más veces (voraz)
? 0 o 1 vez (voraz)
{n} Exactamente n veces
{n,m} Entre n y m veces (voraz)
*? +? ?? Equivalentes perezosos (no voraces)
(abc) Grupo de captura
(?:abc) Grupo de no-captura
(?<nombre>abc) Grupo de captura nombrado
| Alternancia — coincide con izquierda O derecha
(?=...) Lookahead positivo
(?!...) Lookahead negativo
(?<=...) Lookbehind positivo
(?<!...) Lookbehind negativo

Clases de Caracteres

Las clases de caracteres permiten coincidir con un carácter de un conjunto. [aeiou] coincide con cualquier vocal. [a-zA-Z] coincide con cualquier letra. [^0-9] coincide con cualquier carácter que no sea un dígito.

Las clases abreviadas más comunes:

  • \d equivale a [0-9]
  • \w equivale a [a-zA-Z0-9_]
  • \s coincide con espacio, tabulador (\t), salto de línea (\n), retorno de carro (\r) y otros espacios en blanco

Cuantificadores: Voraces vs Perezosos

Por defecto, los cuantificadores son voraces — coinciden con tanto como sea posible. Considera la cadena HTML <b>negrita</b> y <i>cursiva</i>:

<.*>    → coincide con toda la cadena desde <b> hasta </i> (voraz)
<.*?>   → coincide con <b>, luego </b>, luego <i>, luego </i> (perezoso)

Añadir ? después de un cuantificador lo hace perezoso (no voraz): coincide con lo mínimo posible mientras el patrón global siga siendo exitoso.

Anclas

  • ^ coincide con el inicio de la cadena (o de la línea con la bandera m).
  • $ coincide con el final de la cadena (o de la línea con m).
  • \b coincide con un límite de palabra — la transición entre un carácter de palabra y uno que no lo es.
  • \B coincide con un no-límite de palabra.

Grupos y Retroreferencias

Los grupos de captura (...) capturan el texto coincidente, al que puedes referirte después como \1, \2, etc. (retroreferencias), o acceder a través del array de resultados.

Los grupos de no-captura (?:...) agrupan subpatrones sin crear una captura, lo cual es más eficiente cuando no necesitas el valor capturado.

Los grupos nombrados (?<año>\d{4}) permiten referenciar capturas por nombre (match.groups.año en JavaScript), haciendo los patrones mucho más legibles.

Lookahead y Lookbehind

Estas aserciones de anchura cero coinciden con una posición, no con un carácter:

\d+(?= euros)     → coincide con dígitos solo si van seguidos de " euros"
\d+(?! euros)     → coincide con dígitos que NO van seguidos de " euros"
(?<=\$)\d+        → coincide con dígitos solo si están precedidos por "$"
(?<!\$)\d+        → coincide con dígitos que NO están precedidos por "$"

Los lookaheads y lookbehinds no consumen caracteres, por lo que el texto coincidente no incluye la parte del lookahead/lookbehind.


Banderas (Flags)

Bandera Nombre Efecto
i Sin distinción de mayúsculas [a-z] también coincide con [A-Z]
g Global Encuentra todas las coincidencias, no solo la primera
m Multilínea ^ y $ coinciden con límites de línea
s dotAll . también coincide con saltos de línea
u Unicode Habilita coincidencia Unicode completa; requerido para \p{}
y Sticky Solo coincide en la posición lastIndex
x Verboso/Extendido Permite espacios en blanco y comentarios (solo PCRE/Python)

La bandera x (verbosa) es especialmente valiosa para documentar patrones complejos:

import re
pattern = re.compile(r"""
    ^                        # inicio de cadena
    (?P<year>\d{4})          # año de 4 dígitos
    -
    (?P<month>0[1-9]|1[0-2]) # mes 01–12
    -
    (?P<day>0[1-9]|[12]\d|3[01])  # día 01–31
    $
""", re.VERBOSE)

Patrones Comunes con Ejemplos Reales

Validación de Correo Electrónico

^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$

Desglose:

  • ^[a-zA-Z0-9._%+-]+ — parte local (letras, dígitos y caracteres especiales permitidos)
  • @ — arroba literal
  • [a-zA-Z0-9.-]+ — nombre de dominio
  • \.[a-zA-Z]{2,}$ — TLD de 2+ letras

Coincidencia de URL

https?://(?:www\.)?[a-zA-Z0-9-]+(?:\.[a-zA-Z]{2,})+(?:/[^\s]*)?

Fecha ISO (YYYY-MM-DD)

\b\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])\b

Valida mes 0112 y día 0131. No valida rangos de días específicos por mes (ej. el 30 de febrero pasaría la validación).

Dirección IPv4

\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b

Desglose:

  • 25[0-5] — 250–255
  • 2[0-4]\d — 200–249
  • [01]?\d\d? — 0–199

Número de Teléfono (EE. UU.)

\+?1?\s?[\(]?\d{3}[\)]?[-.\s]?\d{3}[-.\s]?\d{4}

Coincide con formatos como (555) 123-4567, 555-123-4567, +1 555 123 4567.

Código de Color Hexadecimal

#(?:[0-9A-Fa-f]{3}){1,2}\b

Coincide con colores hex de 3 dígitos (#F00) y 6 dígitos (#FF0000).


Expresiones Regulares en Diferentes Lenguajes de Programación

JavaScript

// Sintaxis literal con banderas
const regex = /^hello\s+world$/im;
const match = "Hello World".match(regex);

// Sintaxis de constructor (útil para patrones dinámicos)
const term = "world";
const dynamic = new RegExp(`hello\\s+${term}`, "im");

// Reemplazar todas las ocurrencias
const result = "foo bar foo".replaceAll(/foo/g, "baz");

// Capturas nombradas (ES2018+)
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const { year, month, day } = "2024-03-15".match(dateRegex).groups;

Python

import re

# Compilar para reutilización
pattern = re.compile(r'^hello\s+world$', re.IGNORECASE | re.MULTILINE)
match = pattern.match("Hello World")

# Encontrar todas las coincidencias
dates = re.findall(r'\d{4}-\d{2}-\d{2}', text)

# Sustituir con una función
result = re.sub(r'\b\d+\b', lambda m: str(int(m.group()) * 2), "1 más 2 es 3")

# Grupos nombrados
m = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})', "2024-03")
print(m.group('year'))  # 2024

Java

import java.util.regex.*;

Pattern p = Pattern.compile("^hello\\s+world$",
    Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher("Hello World");
boolean found = m.matches();

// Extraer grupos
Pattern datePattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher dm = datePattern.matcher("Hoy es 2024-03-15");
if (dm.find()) {
    String year = dm.group(1);
}

Go

import "regexp"

re := regexp.MustCompile(`(?im)^hello\s+world$`)
match := re.FindString("Hello World")

// Encontrar todas las sub-coincidencias
dateRe := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
all := dateRe.FindAllStringSubmatch(text, -1)
for _, m := range all {
    year, month, day := m[1], m[2], m[3]
    _ = year; _ = month; _ = day
}

// Grupos nombrados
namedRe := regexp.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})`)
match2 := namedRe.FindStringSubmatch("2024-03")
yearIdx := namedRe.SubexpIndex("year")
fmt.Println(match2[yearIdx]) // 2024

Rendimiento y Backtracking Catastrófico

Cómo Funciona el Backtracking

La mayoría de los motores de regex utilizan coincidencia basada en NFA (Autómata Finito No Determinista), lo que significa que pueden intentar múltiples caminos a través del patrón cuando un intento temprano falla. Este backtracking es lo que permite características como los lookaheads y las retroreferencias, pero también puede ser una trampa de rendimiento.

El Caso Catastrófico

Considera el patrón (a+)+ aplicado a la cadena "aaaaaX":

  1. El + externo intenta coincidir con tantos grupos como sea posible.
  2. Cuando el motor alcanza X y falla, hace backtracking e intenta diferentes formas de dividir los caracteres a entre las repeticiones del grupo.
  3. Para una cadena de longitud n, hay 2^(n-1) divisiones posibles — llevando a una complejidad de tiempo exponencial.
(a+)+  sobre "aaaaaaaaaaaaaaaaaX"  →  ¡puede tardar segundos o minutos!

Otros patrones peligrosos incluyen (a|aa)+, (\w+\s*)+, y cualquier cosa con cuantificadores anidados sobre clases de caracteres superpuestas.

Cómo Evitarlo

  1. Evitar cuantificadores anidados sobre el mismo conjunto de caracteres: (a+)+ → usar a+ en su lugar.
  2. Usar grupos atómicos (?>...) o cuantificadores posesivos a++ (PCRE) para prevenir el backtracking en grupos ya coincidentes.
  3. Ser específico: reemplazar .* con una clase de caracteres que excluya delimitadores (ej. [^"]* dentro de cadenas entre comillas).
  4. Anclar patrones donde sea posible para que el motor falle rápido.
  5. Usar un timeout en código de producción cuando se procesa entrada no confiable.

Cómo Leer una Expresión Regular Compleja

Desglosemos ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$:

^                    → ancla: inicio de cadena
[a-zA-Z0-9._%+-]+   → uno o más caracteres permitidos en la parte local
@                    → @ literal
[a-zA-Z0-9.-]+      → uno o más caracteres de dominio
\.                   → punto literal (escapado)
[a-zA-Z]{2,}        → TLD: 2 o más letras
$                    → ancla: fin de cadena

Consejo: Usa el probador de regex de este sitio para resaltar cada grupo capturado y ver exactamente qué parte de la entrada coincide con cada token. Construye tu patrón incrementalmente — empieza con la pieza válida más simple, verifícala, luego extiéndela.


Mejores Prácticas

  1. Compilar una vez, usar muchas veces. Pre-compilar un patrón (ej. re.compile() en Python, Pattern.compile() en Java) es mucho más eficiente que re-parsearlo en cada llamada.
  2. Preferir grupos de no-captura (?:...) cuando no necesitas el valor capturado. Señala la intención y evita la asignación de memoria innecesaria.
  3. Usar cadenas raw para los patrones. En Python, usa r'\d+' en lugar de '\\d+' para evitar el doble escapado. En JavaScript, la sintaxis literal /\d+/ lo maneja automáticamente.
  4. Nombrar tus capturas. (?<año>\d{4}) es mucho más mantenible que depender del índice de grupo \1.
  5. Probar con casos límite: cadenas vacías, cadenas que casi-pero-no-del-todo coinciden, caracteres Unicode y entradas muy largas.
  6. Documentar patrones complejos con la bandera x (verbosa) en Python/PCRE, o con comentarios inline en tu código.
  7. Nunca usar regex para parsear HTML o XML completos. Usa una librería de parser apropiada.
  8. Validar entradas en el lado del servidor. La validación regex del lado del cliente mejora la UX pero no debe ser la única línea de defensa.

Preguntas Frecuentes (FAQ)

P: ¿Cuál es la diferencia entre match() y search() en Python?
R: re.match() solo coincide al principio de la cadena. re.search() escanea toda la cadena buscando una coincidencia. Usa re.fullmatch() para requerir que el patrón coincida con toda la cadena.

P: ¿Por qué ^ dentro de una clase de caracteres [^abc] significa algo diferente?
R: Dentro de una clase de caracteres, ^ como primer carácter niega la clase — coincide con cualquier carácter que no esté en el conjunto. Fuera de una clase de caracteres, ^ es un ancla para el inicio de la cadena.

P: ¿Puedo usar regex para parsear HTML?
R: Para extracciones simples y bien definidas de estructuras HTML conocidas, regex puede funcionar. Pero HTML no es un lenguaje regular — permite anidamiento arbitrario y etiquetas de cierre opcionales. Usa un parser HTML apropiado (ej. BeautifulSoup en Python, DOMParser en JS) para un parseo robusto.

P: ¿Cuál es la diferencia entre cuantificadores voraces y posesivos?
R: Los cuantificadores voraces hacen backtracking — intentan la coincidencia máxima y devuelven caracteres si es necesario. Los cuantificadores posesivos (ej. a++ en PCRE) nunca devuelven — una vez que coinciden, la coincidencia queda bloqueada. Esto previene el backtracking catastrófico pero también puede causar que una coincidencia falle cuando un cuantificador voraz habría tenido éxito.

P: ¿Cómo hago coincidir un punto literal . o un paréntesis (?
R: Escápalos con una barra invertida: \. coincide con un punto literal, \( coincide con un paréntesis abierto literal.

P: ¿Es la regex sensible a mayúsculas por defecto?
R: Sí. Usa la bandera i (/patrón/i en JavaScript, re.IGNORECASE en Python) para habilitar la coincidencia insensible a mayúsculas.

P: ¿Qué coincide con \b?
R: \b es una aserción de límite de palabra de anchura cero. Coincide con la posición entre un carácter de palabra (\w) y un carácter de no-palabra (\W).

P: ¿Cómo pruebo si una cadena completa coincide con un patrón?
R: Ancla con ^ y $: ^patrón$. En Python, también puedes usar re.fullmatch(). En JavaScript, usa .test() con anclas o verifica que match()[0].length === input.length.


Usa el probador de regex de este sitio para experimentar con cada patrón de esta guía. Pega cualquier patrón, escribe tu cadena de prueba y observa las coincidencias resaltadas en tiempo real.