Skip to content

Expresiones regulares

Las expresiones regulares (regex o regexp) son patrones que describen conjuntos de cadenas de caracteres. Se utilizan para:

  • 🔍 Búsqueda de patrones en texto
  • Validación de datos (emails, teléfonos, etc.)
  • 🔁 Sustitución de texto
  • 🔎 Extracción de información específica
  • 🧹 Procesamiento y limpieza de datos

Las expresiones regulares son fundamentales en programación porque permiten:

  • Automatizar tareas de procesamiento de texto
  • Validar formularios web
  • Parsear logs y archivos
  • Procesar grandes volúmenes de datos textuales

Una expresión regular es una secuencia de caracteres que forma un patrón de búsqueda. Los caracteres pueden ser:

  • Literales: Coinciden exactamente (a, 1, @)
  • Metacaracteres: Tienen significado especial (. * + ? ^ $ [ ] { } ( ) | \)

Primera Expresión Regular

hello
Terminal window
# COINCIDE
echo "hello world" | grep -e "hello"
# NO COINCIDE
echo "helo world" | grep -e "hello"

Esta expresión busca exactamente la palabra “hello” en el texto.


Coincide con cualquier carácter excepto salto de línea.

h.llo
Terminal window
# COINCIDE
echo "hallo" | grep -e "h.llo"
# NO COINCIDE
echo "hllo" | grep -e "h.llo"

✓ Coincide: hello, hallo, hxllo ✗ No coincide: hllo, hllo

Escapa metacaracteres para usarlos literalmente.

3\.14
Terminal window
# COINCIDE
echo "El valor es 3.14" | grep -e "3\.14"
# NO COINCIDE
echo "El valor es 314" | grep -e "3\.14"

✓ Coincide: 3.14 ✗ No coincide: 3414


Los cuantificadores especifican cuántas veces debe aparecer un elemento.

CuantificadorDescripciónEjemploCoincide
*0 o más vecesab*cac, abc, abbc
+1 o más vecesab+cabc, abbc
?0 o 1 vezab?cac, abc
{n}Exactamente n vecesab{3}cabbbc
{n,}n o más vecesab{2,}cabbc, abbbc
{n,m}Entre n y m vecesab{2,4}cabbc, abbbc, abbbbc
\d{3}-\d{3}-\d{4}
Terminal window
# COINCIDE
echo "Mi teléfono es 123-456-7890" | grep -e "[0-9]\{3\}-[0-9]\{3\}-[0-9]\{4\}"
# NO COINCIDE
echo "Mi teléfono es 12-3456-789" | grep -e "[0-9]\{3\}-[0-9]\{3\}-[0-9]\{4\}"

✓ Coincide: 123-456-7890


Por defecto, intentan coincidir con la mayor cantidad de caracteres posible.

<.+>
Terminal window
# COINCIDE
echo "<div>Hello</div>" | grep -e "<.*>"
# NO COINCIDE
echo "sin etiquetas" | grep -e "<.*>"

En <div>Hello</div>: coincide con <div>Hello</div> (toda la cadena)

Añadiendo ?, intentan coincidir con la menor cantidad de caracteres posible.

<.+?>
Terminal window
# COINCIDE (usa PCRE para lazy)
echo "<div>Hello</div>" | grep -P -o -e "<.+?>"
# NO COINCIDE
echo "sin etiquetas" | grep -P -o -e "<.+?>"

En <div>Hello</div>: coincide con <div> únicamente

GreedyLazyDescripción
**?0 o más (mínimo)
++?1 o más (mínimo)
???0 o 1 (prefiere 0)
{n,m}{n,m}?Entre n y m (mínimo)

ClaseDescripciónEquivalente
\\dDígito[0-9]
\\wCarácter de palabra[a-zA-Z0-9_]
\\sEspacio en blanco[ \t\n\r\f]
\\DNo dígito[^0-9]
\\WNo carácter de palabra[^a-zA-Z0-9_]
\\SNo espacio en blanco[^ \t\n\r\f]
[aeiou] # Cualquier vocal
[0-9] # Cualquier dígito
[a-zA-Z] # Cualquier letra
[^0-9] # Cualquier carácter que NO sea dígito
Terminal window
# COINCIDE (vocales)
echo "hola" | grep -e "[aeiou]"
# NO COINCIDE (vocales)
echo "hll" | grep -e "[aeiou]"
# COINCIDE (dígitos)
echo "2025" | grep -e "[0-9]"
# NO COINCIDE (dígitos)
echo "abcd" | grep -e "[0-9]"
[a-z] # Letras minúsculas
[A-Z] # Letras mayúsculas
[0-9] # Dígitos
[a-fA-F0-9] # Caracteres hexadecimales
Terminal window
# COINCIDE (minúsculas)
echo "abc" | grep -e "[a-z]"
# NO COINCIDE (minúsculas)
echo "ABC" | grep -e "[a-z]"
# COINCIDE (hex)
echo "F1" | grep -e "[a-fA-F0-9]"
# NO COINCIDE (hex)
echo "G" | grep -e "[a-fA-F0-9]"

AnclaDescripciónEjemplo
^Inicio de línea^Hello
$Final de líneamundo$
\\bLímite de palabra\\bcat\\b
\\BNo límite de palabra\\Bcat\\B
^[A-Z] # Línea que empieza con mayúscula
\.$ # Línea que termina con punto
\bpython\b # La palabra "python" completa
Terminal window
# COINCIDE (empieza con mayúscula)
echo "Hola mundo" | grep -e "^[A-Z]"
# NO COINCIDE (empieza con mayúscula)
echo "hola mundo" | grep -e "^[A-Z]"
# COINCIDE (termina con punto)
echo "Esto es una frase." | grep -e "\.$"
# NO COINCIDE (termina con punto)
echo "Esto no tiene punto" | grep -e "\.$"
# COINCIDE (palabra completa usando -w)
echo "me gusta python y más" | grep -w -e "python"
# NO COINCIDE (palabra completa usando -w)
echo "pythonic" | grep -w -e "python"

Los paréntesis () crean grupos que capturan el texto coincidente.

(\d{4})-(\d{2})-(\d{2})
Terminal window
# COINCIDE
echo "Fecha: 2023-12-25" | grep -e "[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}"
# NO COINCIDE
echo "Fecha: 23-12-25" | grep -e "[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}"

En 2023-12-25:

  • Grupo 1: 2023
  • Grupo 2: 12
  • Grupo 3: 25

(?:...) agrupa sin capturar el contenido.

(?:https?|ftp)://\S+

Terminal window
# COINCIDE (usa PCRE para grupos no capturantes y \S)
echo "Visita http://ejemplo.com/pagina" | grep -P -o -e "(?:https?|ftp)://\S+"
# NO COINCIDE
echo "texto sin url" | grep -P -o -e "(?:https?|ftp)://\S+"

Permiten asignar nombres a los grupos.

(?P<año>\d{4})-(?P<mes>\d{2})-(?P<día>\d{2})
Terminal window
# COINCIDE (grupos nombrados con PCRE)
echo "2023-12-25" | grep -P -e "(?P<año>\d{4})-(?P<mes>\d{2})-(?P<día>\d{2})"
# NO COINCIDE
echo "23-12-25" | grep -P -e "(?P<año>\d{4})-(?P<mes>\d{2})-(?P<día>\d{2})"

TipoSintaxisDescripción
Positivo(?=...)Debe seguir el patrón
Negativo(?!...)NO debe seguir el patrón
# Contraseña que debe tener al menos un dígito
^(?=.*\d).{8,}$
# Palabra seguida de vocal
\w+(?=[aeiou])
Terminal window
# COINCIDE (lookahead, requiere PCRE)
echo "Abcdef1g" | grep -P -e "^(?=.*\d).{8,}$"
# NO COINCIDE (lookahead, requiere PCRE)
echo "Abcdefgh" | grep -P -e "^(?=.*\d).{8,}$"
# COINCIDE (palabra seguida de vocal, extracción con -o)
echo "textoe" | grep -P -o -e "\\w+(?=[aeiou])"
# NO COINCIDE
echo "txtb" | grep -P -o -e "\\w+(?=[aeiou])"
TipoSintaxisDescripción
Positivo(?<=...)Debe preceder el patrón
Negativo(?<!...)NO debe preceder el patrón
# Número precedido por símbolo $
(?<=\$)\d+
# Palabra NO precedida por "no "
(?<!no )\w+
Terminal window
# COINCIDE (lookbehind, requiere PCRE)
echo "$100" | grep -P -o -e "(?<=\$)\d+"
# NO COINCIDE (lookbehind, requiere PCRE)
echo "100" | grep -P -o -e "(?<=\$)\d+"
# COINCIDE (palabra no precedida por "no ")
echo "esto no es" | grep -P -o -e "(?<!no )\w+" # ejemplo simple
# NO COINCIDE
echo "no permitir" | grep -P -o -e "(?<!no )\w+"

Permite elegir entre múltiples patrones.

(gato|perro|pez)

COINCIDE -> echo “Tengo un perro” | grep -e “(gato|perro|pez)” -e “gato|perro|pez” NO COINCIDE -> echo “Tengo un hamster” | grep -e “gato|perro|pez”

Coincide con gato, perro o pez.


import re
# Compilar expresión regular
pattern = re.compile(r'\d{3}-\d{3}-\d{4}')
# Buscar coincidencias
text = "Mi teléfono es 123-456-7890"
match = pattern.search(text)
if match:
print(f"Encontrado: {match.group()}")
# Encontrar todas las coincidencias
matches = pattern.findall(text)
# Sustituir texto
nuevo_texto = pattern.sub("XXX-XXX-XXXX", text)
# Grupos de captura
fecha_pattern = r'(\d{4})-(\d{2})-(\d{2})'
match = re.search(fecha_pattern, "2023-12-25")
if match:
año, mes, día = match.groups()
print(f"Año: {año}, Mes: {mes}, Día: {día}")
// Crear expresión regular
const pattern = /\d{3}-\d{3}-\d{4}/g;
// Buscar en texto
const text = "Mi teléfono es 123-456-7890";
const match = text.match(pattern);
// Test de coincidencia
if (pattern.test(text)) {
console.log("Patrón encontrado");
}
// Sustituir texto
const newText = text.replace(pattern, "XXX-XXX-XXXX");
// Grupos de captura
const datePattern = /(\d{4})-(\d{2})-(\d{2})/;
const dateMatch = "2023-12-25".match(datePattern);
if (dateMatch) {
const [full, year, month, day] = dateMatch;
console.log(`Año: ${year}, Mes: ${month}, Día: ${day}`);
}
// Usar exec() para múltiples coincidencias
const globalPattern = /\d+/g;
let result;
while ((result = globalPattern.exec("123 456 789")) !== null) {
console.log(`Encontrado: ${result[0]} en posición ${result.index}`);
}
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class RegexExample {
public static void main(String[] args) {
// Compilar patrón
Pattern pattern = Pattern.compile("\\d{3}-\\d{3}-\\d{4}");
String text = "Mi teléfono es 123-456-7890";
Matcher matcher = pattern.matcher(text);
// Buscar coincidencia
if (matcher.find()) {
System.out.println("Encontrado: " + matcher.group());
}
// Reemplazar texto
String newText = matcher.replaceAll("XXX-XXX-XXXX");
// Grupos de captura
Pattern datePattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher dateMatcher = datePattern.matcher("2023-12-25");
if (dateMatcher.find()) {
String year = dateMatcher.group(1);
String month = dateMatcher.group(2);
String day = dateMatcher.group(3);
System.out.println("Año: " + year + ", Mes: " + month + ", Día: " + day);
}
}
}
<?php
// Buscar coincidencias
$pattern = '/\d{3}-\d{3}-\d{4}/';
$text = 'Mi teléfono es 123-456-7890';
if (preg_match($pattern, $text, $matches)) {
echo "Encontrado: " . $matches[0] . "\n";
}
// Encontrar todas las coincidencias
preg_match_all('/\d+/', 'Los números son 123, 456 y 789', $allMatches);
print_r($allMatches[0]);
// Sustituir texto
$newText = preg_replace($pattern, 'XXX-XXX-XXXX', $text);
// Grupos de captura con nombres
$datePattern = '/(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})/';
if (preg_match($datePattern, '2023-12-25', $matches)) {
echo "Año: " . $matches['year'] . "\n";
echo "Mes: " . $matches['month'] . "\n";
echo "Día: " . $matches['day'] . "\n";
}
// Validar email
function validarEmail($email) {
$pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
return preg_match($pattern, $email);
}
?>
#!/bin/bash
# Usando grep con regex
text="Mi teléfono es 123-456-7890"
echo "$text" | grep -E '\\d{3}-\\d{3}-\\d{4}'
# Usando sed para sustituir
echo "$text" | sed 's/[0-9]\{3\}-[0-9]\{3\}-[0-9]\{4\}/XXX-XXX-XXXX/'
# Validación con condicionales
email="usuario@ejemplo.com"
if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "Email válido"
else
echo "Email inválido"
fi
# Extraer información de un archivo
# Buscar direcciones IP en un archivo de log
grep -oE '\\b([0-9]{1,3}\\.){3}[0-9]{1,3}\\b' /var/log/access.log
# Extraer dominios de URLs
echo "https://www.ejemplo.com/pagina" | grep -oE 'https?://([^/]+)'
# Capturar grupos con grep
# Extraer fecha del formato DD-MM-YYYY
echo "Fecha: 25-12-2023" | grep -oE '[0-9]{2}-[0-9]{2}-[0-9]{4}'

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

Explicación:

  • ^ - Inicio de cadena
  • [a-zA-Z0-9._%+-]+ - Uno o más caracteres válidos para el usuario
  • @ - Símbolo arroba literal
  • [a-zA-Z0-9.-]+ - Uno o más caracteres válidos para el dominio
  • \. - Punto literal
  • [a-zA-Z]{2,} - Al menos 2 letras para la extensión
  • $ - Final de cadena
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$

Explicación:

  • (?=.*[a-z]) - Al menos una minúscula
  • (?=.*[A-Z]) - Al menos una mayúscula
  • (?=.*\d) - Al menos un dígito
  • (?=.*[@$!%*?&]) - Al menos un carácter especial
  • [A-Za-z\d@$!%*?&]{8,} - Mínimo 8 caracteres de los tipos permitidos
https?://(?:[-\w.])+(?:[:\d]+)?(?:/(?:[\w/_.])*)?(?:\?(?:[\w&=%.])*)?(?:#(?:[\w.])*)?
^\+?[1-9]\d{1,14}$
#[a-zA-Z0-9_]+
^(0[1-9]|[12][0-9]|3[01])/(0[1-9]|1[0-2])/([0-9]{4})$
\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}\\b
^[0-4][0-9]{4}$|^5[0-2][0-9]{3}$

Crear una expresión regular que valide un DNI español (8 dígitos + 1 letra).

Solución
^[0-9]{8}[TRWAGMYFPDXBNJZSQVHLCKE]$

Extraer todas las menciones (@usuario) de un texto.

Solución
@[a-zA-Z0-9_]+

Validar números de tarjeta de crédito (16 dígitos, pueden estar separados por espacios o guiones).

Solución
^[0-9]{4}[\s\-]?[0-9]{4}[\s\-]?[0-9]{4}[\s\-]?[0-9]{4}$

Reemplazar múltiples espacios con un solo espacio.

Solución
\s+

Reemplazar con: (un espacio)


Terminal window
# Extraer IPs y códigos de estado de logs de Apache
grep -oE '\\b([0-9]{1,3}\\.){3}[0-9]{1,3}\\b.*" [0-9]{3} ' access.log
# Limpiar números de teléfono
import re
phone = "+1 (555) 123-4567"
clean_phone = re.sub(r'[^\d+]', '', phone)
// Validar formulario de registro
function validarFormulario(email, password) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
return emailRegex.test(email) && passwordRegex.test(password);
}

  1. Usa raw strings en Python: r"patron"
  2. Compila patrones que uses repetidamente
  3. Usa grupos no capturadores (?:...) cuando no necesites el contenido
  4. Escapa metacaracteres cuando los uses literalmente
  5. Documenta regex complejas con comentarios
  6. Usa herramientas online para probar patrones
  1. No escapar metacaracteres especiales
  2. Usar cuantificadores greedy cuando necesitas lazy
  3. No anclar patrones cuando es necesario
  4. Regex demasiado complejas - divídelas en partes
  5. No considerar casos edge como cadenas vacías
  1. Evita backtracking excesivo
  2. Usa clases de caracteres en lugar de alternación cuando sea posible
  3. Ancla patrones al inicio ^ cuando sepas que debe empezar ahí
  4. Usa quantificadores posesivos cuando no necesites backtracking

  • VS Code: Regex Previewer, Regex Explain
  • Sublime Text: RegReplace
  • Vim: Regex plugins

ElementoDescripciónEjemplo
.Cualquier caráctera.cabc, axc
*0 o másab*cac, abc
+1 o másab+cabc, abbc
?0 o 1ab?cac, abc
^Inicio^abc → inicio con abc
$Finalabc$ → termina con abc
[]Clase de caracteres[abc]a, b, o c
()Grupo(abc)+abc, abcabc
|Alternacióna|ba o b
\\dDígito\\d+ → uno o más dígitos
\\wCarácter de palabra\\w+ → una o más letras
\\sEspacio\\s+ → uno o más espacios

Las expresiones regulares son una herramienta poderosa para el procesamiento de texto. Aunque pueden parecer intimidantes al principio, con práctica se vuelven intuitivas y extremadamente útiles.

  1. Practica con ejercicios simples
  2. Experimenta con herramientas online
  3. Aplica regex en tus proyectos
  4. Estudia casos de uso específicos de tu área

¡Recuerda: la maestría en regex viene con la práctica constante! 🎯