Cheat Sheet de Regex: metacaracteres, grupos y lookarounds (referencia completa)
Una expresión regular es un pequeño lenguaje de patrones para hacer coincidir texto: \d+ significa “uno o más dígitos”, ^Error significa “una línea que empieza con Error”. Eso es todo. Esta hoja de referencia de regex pone la sintaxis en una sola página que cabe en un scroll: metacaracteres, cuantificadores, anclas, grupos, lookarounds y flags, además de más de 15 patrones que puedes pegar hoy mismo en JavaScript o Python.
Está escrita para desarrolladoras y desarrolladores que ya saben qué es un string y quieren una referencia, no una introducción. Salta a la tabla de referencia rápida si solo necesitas los símbolos. Lee las secciones de lookaround y trampas si alguna vez una regex te colgó un servidor.
1. Qué es regex y por qué la sigues necesitando en 2026
Una regex es un patrón compilado en una máquina de estados que recorre un string y, o bien hace match, o bien falla. La gramática es pequeña; los casos de uso, no.
La IA puede redactarte un patrón, pero tres tareas siguen siendo de quien escribe regex a mano:
- Parseo de logs: tienes diez millones de líneas de logs de acceso de
nginxy necesitas cada solicitud 5xx de un user agent específico. Una regex de 40 caracteres sobregrep -Ecorre en segundos; una llamada a un LLM por línea, no. - Validación de formularios y campos: números de teléfono, códigos postales, timestamps ISO, claves de licencia. El patrón vive junto al input y se ejecuta en cada tecleo dentro del navegador.
- Buscar y reemplazar en masa: refactorizar mil archivos cuando necesitas capturar un nombre y volver a inyectarlo.
sed,ripgrepy el “Reemplazar en archivos” de tu editor hablan regex de forma nativa.
Para la mitad JSON del mismo set de herramientas, mira nuestra jq command-line cheat sheet.
1.1 Cómo leer un patrón de regex (tutorial relámpago de expresiones regulares)
La mayoría de los patrones se leen mejor de izquierda a derecha, un token a la vez. Toma ^[A-Z]\w+\d{2,4}$ como ejemplo:
^ancla la coincidencia al inicio del string.[A-Z]matchea exactamente una letra mayúscula.\w+matchea uno o más caracteres de palabra (letras, dígitos, guion bajo).\d{2,4}matchea entre dos y cuatro dígitos.$ancla al final del string.
Así que el patrón completo matchea Order42, Job1999. La habilidad consiste en leer primero las anclas, luego las clases de caracteres, después los cuantificadores y al final los límites.
2. Tabla de referencia rápida
Si solo viniste a copiar, esta es tu sección.
Metacaracteres
| Patrón | Coincide con |
|---|---|
. | Cualquier carácter excepto salto de línea (o cualquier carácter con la flag s/dotall) |
\d | Un dígito ([0-9], o todos los dígitos Unicode con la flag u) |
\D | Cualquier carácter que no sea dígito |
\w | Un carácter de palabra ([A-Za-z0-9_]) |
\W | Un carácter que no sea de palabra |
\s | Cualquier espacio en blanco (espacio, tab, salto de línea, …) |
\S | Cualquier carácter que no sea espacio en blanco |
Cuantificadores
| Patrón | Coincide con |
|---|---|
* | 0 o más (greedy) |
+ | 1 o más (greedy) |
? | 0 o 1 (greedy) |
{n} | Exactamente n veces |
{n,m} | Entre n y m veces |
{n,} | n o más veces |
*?, +?, ??, {n,m}? | Versiones lazy de cada cuantificador |
Anclas
| Patrón | Coincide con |
|---|---|
^ | Inicio del string (o inicio de línea con la flag m) |
$ | Fin del string (o fin de línea con la flag m) |
\b | Frontera de palabra |
\B | Frontera que no es de palabra |
\A | Inicio absoluto del string (Python) |
\Z | Fin absoluto del string (Python) |
Clases de caracteres
| Patrón | Coincide con |
|---|---|
[abc] | Cualquiera entre a, b, c |
[^abc] | Cualquier cosa excepto a, b, c |
[a-z] | Cualquier letra minúscula |
[0-9] | Cualquier dígito |
\p{L} | Cualquier letra Unicode (flag u en JS, por defecto en Python re) |
Grupos
| Patrón | Coincide con |
|---|---|
(...) | Grupo de captura |
(?:...) | Grupo sin captura |
(?<name>...) | Captura con nombre (JS ES2018+); Python usa (?P<name>...) |
\1, \2 | Retroreferencia al grupo 1, 2 |
Lookaround
| Patrón | Coincide con |
|---|---|
(?=...) | Lookahead positivo |
(?!...) | Lookahead negativo |
(?<=...) | Lookbehind positivo |
(?<!...) | Lookbehind negativo |
Flags
| Flag | Efecto |
|---|---|
i | Sin distinguir mayúsculas/minúsculas |
m | Multilínea: ^ y $ actúan por línea |
s | Dotall: . también matchea saltos de línea |
g | Global (JS): encuentra todas las coincidencias |
u | Modo Unicode |
y | Sticky (JS): ancla en lastIndex |
3. Metacaracteres y clases de caracteres
3.1 Literales vs caracteres especiales
La mayoría de los caracteres son literales. Los 12 metacaracteres que hay que escapar cuando los quieres tomar como ellos mismos son:
. ^ $ * + ? ( ) [ ] { } | \
Olvidarse de escapar . es, con diferencia, el bug de regex que más veces hemos visto en code reviews. \. matchea un punto literal. Dentro de una clase de caracteres, [.] también matchea un punto literal: la mayoría de los metacaracteres pierden su poder dentro de [...], excepto ], \, ^ (cuando va al inicio) y - (en medio).
3.2 Abreviaturas de caracteres
Las clases abreviadas parecen simples hasta que aparece Unicode:
// JavaScript — sin la flag u, \d es solo ASCII
/\d/.test('5'); // true
/\d/.test('٥'); // false (dígito árabe-índico)
/\d/u.test('٥'); // false — incluso con u, \d sigue siendo ASCII en JS
/\p{N}/u.test('٥'); // true — \p{N} es la clase de dígito consciente de Unicode
# Python — el módulo re trata \d como Unicode por defecto
import re
re.match(r'\d', '٥') # <Match span=(0, 1)>
re.match(r'(?a)\d', '٥') # None — (?a) fuerza ASCII
Si solo trabajas con input ASCII en inglés, \d y [0-9] son intercambiables. En el momento en que alguien pega un nombre con tilde, vas a querer \p{L} antes que \w.
3.3 Clases de caracteres personalizadas
// JavaScript
/[A-Za-z][A-Za-z0-9_-]{2,29}/.test('valid_handle-1'); // true
// Negación y rangos combinados
/[^aeiou\s]/g // cualquier carácter que no sea vocal ni espacio en blanco
Para categorías Unicode, \p{L} es “cualquier letra”, \p{N} es “cualquier número”, \p{Script=Han} es “cualquier carácter Han”. JavaScript requiere la flag u; Python soporta \p{...} solo mediante el paquete regex de PyPI, no el re de la stdlib.
Si trabajas en la línea de comandos, también te vas a topar con las clases de caracteres POSIX:
| Clase POSIX | Matchea | Equivalente ASCII |
|---|---|---|
[[:alpha:]] | letras | [A-Za-z] |
[[:digit:]] | dígitos | [0-9] (\d) |
[[:alnum:]] | letras + dígitos | [A-Za-z0-9] |
[[:space:]] | espacios en blanco | \s |
[[:upper:]] | mayúsculas | [A-Z] |
[[:lower:]] | minúsculas | [a-z] |
Las clases POSIX funcionan en grep -E, sed -E. No funcionan en JavaScript ni en el re de Python — ahí usa \d, \s, \w.
4. Cuantificadores y greedy vs lazy
4.1 Cuantificadores básicos
/a*/.exec('aaab') // ['aaa'] — 0 o más
/a+/.exec('aaab') // ['aaa'] — 1 o más
/a?/.exec('aaab') // ['a'] — 0 o 1
/a{2,3}/.exec('aaaab') // ['aaa'] — 2 a 3
4.2 Greedy vs lazy
Por defecto los cuantificadores son greedy: agarran tanto como pueden y luego retroceden lo necesario para que todo el patrón encaje. Añade ? para convertirlos en lazy.
const html = '<p>one</p><p>two</p>';
html.match(/<p>.*<\/p>/)[0]; // '<p>one</p><p>two</p>' (greedy se come ambos)
html.match(/<p>.*?<\/p>/)[0]; // '<p>one</p>' (lazy se detiene en el primero)
La versión lazy es casi siempre lo que quieres cuando extraes etiquetas o strings entre comillas. Y mejor todavía: evita . por completo y usa una clase negada. <p>[^<]*</p> es más rápido que <p>.*?</p> porque no hay nada hacia donde retroceder.
4.3 Backtracking catastrófico
Así es como una regex cuelga un servidor. Anida un cuantificador dentro de otro cuantificador con un solapamiento ambiguo, y el motor explora un número exponencial de caminos antes de rendirse.
// No hagas esto
/(a+)+b/.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'); // tarda segundos
Para 41 as seguidas de !, el motor prueba aproximadamente 2^41 puntos de división antes de decidir que falta la b. Tres salidas:
- Aplana el patrón:
/a+b/hace el mismo trabajo sin anidamiento. - Usa un grupo atómico (Python
regex, PCRE, Java, Ruby):(?>a+)+b. Una vez quea+matchea, el motor se niega a hacer backtrack hacia dentro. - Cambia de motor: el
regexpde Go, RE2 y el crateregexde Rust usan un NFA de tiempo lineal y, por diseño, no pueden tener backtracking catastrófico.
JavaScript y el re de Python son ambos motores con backtracking y no tienen grupos atómicos en la stdlib (el paquete regex de PyPI los añade en Python). Si controlas la longitud del input, no pasa nada. Cuando el input viene de un usuario, valida primero la longitud o precompila contra RE2.
5. Anclas y fronteras de palabra
5.1 ^ y $
Por defecto, ^ es el inicio del input completo y $ es el final. Con la flag m (multilínea), se convierten en el inicio y fin de cada línea:
const log = 'INFO start\nERROR boom\nINFO done';
log.match(/^ERROR.*/); // null — modo de línea única, ^ solo matchea el índice 0
log.match(/^ERROR.*/m); // ['ERROR boom']
5.2 \b y \B
\b es una aserción de ancho cero: matchea la posición entre un carácter de palabra (\w) y uno que no lo es. Útil para buscar palabras completas:
/\bcat\b/.test('the cat sat'); // true
/\bcat\b/.test('concatenate'); // false
Las fronteras de palabra se definen sobre \w, que por defecto es ASCII. El texto en chino, japonés y coreano no tiene espacios entre palabras, así que \b no detecta los bordes de palabra ahí: necesitas un tokenizador (jieba, MeCab) antes de la regex, no en su lugar.
5.3 Modo multilínea
import re
text = "INFO ok\nERROR fail\nINFO done\n"
re.findall(r'^ERROR.*$', text) # []
re.findall(r'^ERROR.*$', text, re.MULTILINE) # ['ERROR fail']
En JavaScript lo mismo se lee como text.match(/^ERROR.*$/gm). Combina m con g para capturar cada línea que matchea.
6. Grupos, captura y retroreferencias
6.1 Grupos de captura
Los paréntesis cumplen dos funciones: agrupan sub-patrones para los cuantificadores y capturan el match para usarlo después.
'2026-05-13'.match(/(\d{4})-(\d{2})-(\d{2})/);
// ['2026-05-13', '2026', '05', '13', index: 0, ...]
Los grupos se numeran de izquierda a derecha por su paréntesis de apertura, empezando en 1.
6.2 Grupos sin captura
Cuando solo necesitas agrupar, no capturar, usa (?:...). Es más rápido y mantiene los grupos numerados ordenados:
/(?:https?):\/\/(\S+)/.exec('see https://go-tools.org');
// ['https://go-tools.org', 'go-tools.org']
// — el protocolo se agrupa pero no se captura; el grupo 1 es el host
6.3 Grupos con nombre
Nombrar los grupos hace los patrones legibles y seguros frente a refactorings.
// JavaScript (ES2018+)
const m = '2026-05-13'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
m.groups.year; // '2026'
# Python — fíjate en la sintaxis (?P<...>)
import re
m = re.match(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', '2026-05-13')
m.group('year') # '2026'
6.4 Retroreferencias
Una retroreferencia hace que una parte posterior del patrón repita lo que matcheó una captura anterior.
// Encuentra cualquier carácter que se repite de forma consecutiva
'bookkeeper'.match(/(\w)\1/g); // ['oo', 'kk', 'ee']
// Matchea etiquetas HTML emparejadas por nombre
const tag = /<(\w+)>(.*?)<\/\1>/;
'<b>bold</b>'.match(tag);
// ['<b>bold</b>', 'b', 'bold']
En Python, \1 funciona tanto en el patrón como en el reemplazo; las referencias con nombre se escriben (?P=name) en el patrón y \g<name> en los reemplazos de re.sub.
7. Lookarounds: lookahead y lookbehind
Los lookarounds son aserciones de ancho cero. Comprueban una condición sin consumir caracteres, así que puedes encadenarlos.
7.1 Lookahead
// Contraseña: al menos 8 caracteres, un dígito, una mayúscula, una minúscula
const strong = /^(?=.*\d)(?=.*[A-Z])(?=.*[a-z]).{8,}$/;
strong.test('Hunter2!'); // true
strong.test('hunter2!'); // false — sin mayúscula
// Lookahead negativo — nombres de archivo que no son .tmp
/^[\w-]+(?!\.tmp$)\.[a-z]+$/.test('report.csv'); // true
7.2 Lookbehind
El lookbehind es la imagen reflejada: afirma lo que viene antes de la posición actual.
// Extrae un precio después de un símbolo de moneda — quédate con el número, descarta el $
'price: $42.50'.match(/(?<=\$)\d+(\.\d+)?/); // ['42.50', '.50']
// Lookbehind negativo — matchea Bond pero no James Bond
'Mr. Bond'.match(/(?<!James )Bond/); // ['Bond']
'James Bond'.match(/(?<!James )Bond/); // null
7.3 Lookbehind en JavaScript vs Python
Este es uno de los pocos lugares donde los dos motores divergen lo suficiente como para romper un patrón cuando lo portas.
| Motor | Lookbehind de longitud |
|---|---|
| JavaScript (V8, SpiderMonkey, JSC 16.4+) | Ancho variable desde ES2018. (?<=\d+) es válido. |
re de la stdlib de Python | Solo ancho fijo. (?<=\d+) lanza error: look-behind requires fixed-width pattern. |
Paquete regex de PyPI en Python | Soporta ancho variable. import regex; regex.search(r'(?<=\d+)abc', '12abc'). |
Workaround en Python: reescribe el lookbehind con una repetición conocida ((?<=\d{3})) o captura el prefijo y recórtalo después de matchear.
8. Flags y modificadores
8.1 i — sin distinguir mayúsculas/minúsculas
/error/i.test('FATAL ERROR'); // true
re.search(r'error', 'FATAL ERROR', re.IGNORECASE) # <Match span=(6, 11)>
8.2 m y s
m convierte ^ y $ en anclas por línea. s (dotall) hace que . matchee saltos de línea. Son independientes: combínalas cuando quieras ambos efectos.
/<script>(.*?)<\/script>/s.exec('<script>\nalert(1)\n</script>')[1];
// '\nalert(1)\n' — sin s, el . rechazaría los saltos de línea
8.3 g — global
En JavaScript, g cambia la API más que el match en sí. Sin g, String.match devuelve los grupos de captura; con g, devuelve cada string que matcheó. Usa matchAll si quieres conservar los grupos de captura en todas las coincidencias.
const text = 'a=1 b=2 c=3';
text.match(/(\w)=(\d)/); // primera coincidencia con grupos
text.match(/(\w)=(\d)/g); // ['a=1', 'b=2', 'c=3'] — sin grupos
[...text.matchAll(/(\w)=(\d)/g)]; // cada coincidencia, con grupos
Python no usa g. Sus variantes globales son re.findall, re.finditer y re.sub.
8.4 u — Unicode y \p{...}
// Matchea cualquier carácter Han (chino, kanji japonés)
/\p{Script=Han}+/gu.test('Hello 世界'); // true
// Matchea emoji (pictográficos extendidos)
/\p{Extended_Pictographic}/u.test('👋'); // true
En Python, Unicode está activado por defecto, así que re.findall(r'[一-鿿]+', text) es el equivalente para el rango Han. Para los escapes de propiedad Unicode completos, usa el paquete regex de PyPI: regex.findall(r'\p{Script=Han}+', text).
9. Patrones comunes que usarás a diario
9.1 Validación de email
Antes de copiar, decide qué versión necesitas.
// El patrón del 95% — el que usan la mayoría de los validadores de formularios
const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
email.test('a@b.co'); // true
// El patrón "realmente quiero algo cercano al RFC 5322"
const rfc = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
La validación completa de email según RFC 5322 en regex puro tiene unos 6000 caracteres y aun así falla en casos límite. Quédate con la versión corta y manda un email de verificación: esa es la única prueba que realmente funciona.
9.2 Extracción de URLs
const urlPattern = /https?:\/\/[^\s<>"]+/g;
const found = 'See https://example.com/a?b=1 and http://x.io'.match(urlPattern);
// ['https://example.com/a?b=1', 'http://x.io']
Una vez extraída la URL, normalmente quieres inspeccionar su query string. Pégala en nuestro decodificador/codificador de URL y podrás leer los parámetros codificados en porcentaje de un vistazo. Para el panorama completo de cuándo codificar y cuándo decodificar, lee la guía de codificación y decodificación de URL.
9.3 Números de teléfono
// E.164 — internacional, + opcional y código de país de 1 a 3 dígitos
const e164 = /^\+?[1-9]\d{1,14}$/;
e164.test('+14155551234'); // true
// Plan de Numeración Norteamericano con separadores
const nanp = /^(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;
nanp.test('(415) 555-1234'); // true
Para cualquier cosa más allá de “¿esta forma es plausible?”, usa libphonenumber. Una regex no puede validar que un código de área exista.
9.4 IPv4 e IPv6
// IPv4 — estricto, 0-255 por octeto
const ipv4 = /^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$/;
ipv4.test('192.168.1.1'); // true
ipv4.test('999.0.0.1'); // false
// IPv6 — la forma simplificada. El patrón completo según RFC 4291 son ~600 caracteres.
const ipv6simple = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
ipv6simple.test('2001:0db8:85a3:0000:0000:8a2e:0370:7334'); // true
Para IPv6 real con la forma abreviada ::, IPv4 embebido e identificadores de zona, usa isIP() de node:net o ipaddress.ip_address() de Python. Hacerlo en regex pura funciona la primera semana y se convierte en deuda técnica después.
9.5 Fechas y timestamps ISO 8601
// Solo fecha — YYYY-MM-DD
const isoDate = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
isoDate.test('2026-05-13'); // true
// Fecha + hora + fracción de segundos opcional + Z o offset
const iso = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
iso.test('2026-05-13T09:30:00.123Z'); // true
ISO 8601 parece simple y está llena de trampas: segundos intercalares, fechas con semana (2026-W19), fechas ordinales (2026-133). Para segundos vs milisegundos epoch y desplazamientos de zona horaria, mira la guía de Unix timestamp.
10. Flujos de búsqueda/reemplazo con regex
10.1 JavaScript — String.replace con $1
// Reformatea fechas estadounidenses: MM/DD/YYYY -> YYYY-MM-DD
'05/13/2026'.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$1-$2');
// '2026-05-13'
// Usa un callback cuando el reemplazo es condicional
'price 42 dollars'.replace(/(\d+) dollars/, (_, n) => `$${n}`);
// 'price $42'
$1, $2, … referencian grupos numerados. $<name> referencia grupos con nombre. $& es el match completo; $$ es un $ literal.
10.2 Python — re.sub con \1 y callbacks
import re
# El mismo reformateo de fecha que arriba
re.sub(r'(\d{2})/(\d{2})/(\d{4})', r'\3-\1-\2', '05/13/2026')
# '2026-05-13'
# Callback — pasa a mayúsculas cada dirección de email en un string
def upper_email(m):
return m.group(0).upper()
re.sub(r'[\w.-]+@[\w.-]+', upper_email, 'mail me at hi@go-tools.org')
# 'mail me at HI@GO-TOOLS.ORG'
En los reemplazos, Python usa \1 o \g<name>. El prefijo de raw string r'...' importa: sin él, \1 se convierte en un carácter literal.
10.3 CLI: sed, grep, ripgrep, jq
Para refactors en masa desde la línea de comandos, la regex se mueve del script al shell:
# ripgrep — encuentra cada TODO con un nombre adjunto
rg -n '\bTODO\(([^)]+)\)' --replace 'TODO(\1)'
# grep -E con anclas — líneas de login fallido desde auth.log
grep -E '^[A-Z][a-z]{2} +[0-9]+ .*Failed password' /var/log/auth.log
# sed — recorta espacios al final, in-place, en todo un árbol de archivos
find . -name '*.md' -print0 | xargs -0 sed -i -E 's/[[:space:]]+$//'
ripgrep usa el crate regex de Rust (estilo RE2, tiempo lineal, sin lookbehind). grep -E y sed -E usan regex extendida POSIX, que carece de \d: usa [0-9] y [[:digit:]] en su lugar. Cuando el dato es JSON, cambia regex por jq: mira la jq cheat sheet para una tarjeta de referencia paralela.
11. Trampas comunes
11.1 Olvidarse de escapar .
Un bug real que enviamos a producción: un redactor de logs debía enmascarar direcciones IP.
// Mal — también matchea '192a168b1c1'
/(\d+).(\d+).(\d+).(\d+)/.test('192a168b1c1'); // true
// Bien
/(\d+)\.(\d+)\.(\d+)\.(\d+)/.test('192a168b1c1'); // false
Dentro de una clase de caracteres, . ya es literal, así que tanto [.] como \. funcionan. En cualquier otro lugar, escápalo.
11.2 El greedy .* se come demasiado
'<a href="x"><b>bold</b></a>'.match(/<(.*)>/)[1];
// 'a href="x"><b>bold</b></a' — ¡todo entero!
El .* greedy avanza hasta el final del string y luego retrocede hasta que > matchea: el último > del input. O te pasas a lazy (.*?) o, más rápido y claro, usas una clase negada ([^>]*).
11.3 Anclas multilínea
Una confusión frecuente: ^ y $ no matchean caracteres de salto de línea por defecto. Matchean posiciones al inicio y final del input completo. Lo que las convierte en anclas por línea es añadir la flag m. Lo que deja que . cruce saltos de línea es añadir la flag s. Son ortogonales y normalmente quieres ambas para parsear logs.
11.4 ReDoS y cómo desactivarlo
ReDoS (denegación de servicio por regex) es la versión grado-producción del backtracking catastrófico. Las salidas:
- Análisis estático: herramientas como
safe-regex,rechecky la reglano-misleading-character-classde ESLint detectan los patrones peligrosos antes de que lleguen a producción. - Grupos atómicos (Python
regex, PCRE, Ruby, Java):(?>...)impide que el motor vuelva a entrar al grupo durante el backtrack. - Cuantificadores posesivos (
*+,++,?+en PCRE/Java): misma idea, sintaxis más corta. - Cambia a un motor sin backtracking: el
regexpde Go, RE2, el crateregexde Rust y el bindingre2para Python corren todos en tiempo lineal. ripgrep es uno de los usuarios más visibles de RE2. - Valida primero la longitud del input. Una bomba de regex de 10 KB es un bug; un límite de 10 bytes en el input es una línea de código.
Para un inventario más amplio de las herramientas del día a día que se combinan con regex (formatters, decodificadores, conversores), mira nuestra guía de herramientas para desarrolladores.
Antes de mandar a producción un patrón complejo, pruébalo de forma interactiva. regex101.com cambia entre los flavors de PCRE, JavaScript, Python y Go, explica cada token y muestra el backtracking para que puedas detectar patrones catastróficos.
12. FAQ
¿Cuál es la diferencia entre * y + en regex?
* matchea cero o más ocurrencias (puede matchear un string vacío); + matchea una o más (necesita al menos una). a* matchea '', 'a', 'aaaa'. a+ matchea 'a' y 'aaaa' pero no ''.
¿Cómo matcheo a través de múltiples líneas con regex?
Activa la flag multilínea: /.../m en JavaScript, re.MULTILINE en Python, para que ^ y $ se anclen a cada línea. Para que . también cruce saltos de línea, añade la flag dotall (s en JavaScript, re.DOTALL en Python).
¿Es regex igual en JavaScript y Python?
La sintaxis básica (cuantificadores, anclas, clases de caracteres, grupos básicos) es 90% la misma. Las dos diferencias reales: JavaScript (ES2018+) soporta lookbehind de longitud variable y escribe los grupos con nombre como (?<name>...); el re de la stdlib de Python requiere lookbehind de ancho fijo y usa (?P<name>...). Para lookbehind de longitud variable en Python, instala el paquete regex desde PyPI.
¿Por qué mi regex sufre backtracking catastrófico?
Tienes cuantificadores anidados con matches que se solapan, como (a+)+ o (a|a)*. Sobre un input que casi matchea pero falla cerca del final, el motor prueba cada partición del cuantificador interno: un número exponencial de caminos. Arréglalo con un grupo atómico (?>a+)+, un cuantificador posesivo a++, o pasándote a un motor sin backtracking como RE2 o el regexp de Go.
¿Puedo usar lookbehind en JavaScript?
Sí. Lookbehind positivo (?<=...) y negativo (?<!...) están en V8 (Chrome, Node.js), SpiderMonkey (Firefox) y JavaScriptCore (Safari 16.4+) desde ES2018. El lookbehind de longitud variable está soportado. Para Safari más antiguo, transpila con Babel o haz feature-detect con un try/catch alrededor de new RegExp.
¿Cómo matcheo un punto . literal en regex?
Escápalo con una contrabarra: \. matchea un punto literal. Dentro de una clase de caracteres, el punto ya es literal: tanto [.] como [\.] funcionan. Fuera de una clase, un . sin escapar es un metacarácter que significa “cualquier carácter excepto salto de línea” (o cualquier carácter en absoluto con la flag dotall).
¿Qué significa \s en regex?
\s matchea cualquier carácter de espacio en blanco: espacio, tab, salto de línea, retorno de carro, tab vertical, form feed. En modo Unicode también matchea el NBSP. \S es lo contrario.
¿Las expresiones regulares distinguen mayúsculas y minúsculas?
Por defecto, sí. /cat/ no matchea Cat. Usa la flag i en JavaScript (/cat/i) o re.IGNORECASE / (?i) en Python.