Expresiones regulares en Go

El paquete regexp viene con Go y usa la sintaxis RE2, que tiene una garantía poco común: siempre se ejecuta en tiempo lineal, así que no existen los patrones que se cuelgan. A cambio no admite algunas cosas, como las retrorreferencias.

Compilar el patrón

re := regexp.MustCompile(`[\w.]+@[\w.]+\.\w+`)

Dos detalles de esa línea. Las comillas invertidas evitan tener que duplicar cada barra: con comillas normales habría que escribir "\\w". Y MustCompile revienta el programa si el patrón está mal, lo cual es exactamente lo que quieres cuando el patrón es una constante: el fallo aparece al arrancar y no a mitad de la ejecución.

Compila una vez, reutiliza siempre. Compilar es caro. Lo habitual es declarar la expresión como variable de paquete, fuera de las funciones, para que se compile una sola vez al arrancar: var reCorreo = regexp.MustCompile(…).

¿Coincide?

fmt.Println(regexp.MustCompile(`\d+`).MatchString("hay 42 unidades"))
salidatrue

Extraer

texto := "Contactos: ana@ejemplo.com (Madrid), luis.perez@correo.es (Lima), soporte@decodigo.com"

fmt.Println(re.FindString(texto))          // solo la primera

for _, m := range re.FindAllString(texto, -1) {   // todas
	fmt.Println(m)
}
salidaana@ejemplo.com
ana@ejemplo.com
luis.perez@correo.es
soporte@decodigo.com

Ese -1 de FindAllString significa «todas las que haya». Si pones un número, se para ahí. Y si no encuentra nada, FindString devuelve la cadena vacía y FindAllString devuelve nil, que se puede recorrer sin problema.

Grupos con nombre

rg := regexp.MustCompile(`(?P<usuario>[\w.]+)@(?P<dominio>[\w.]+)`)

m := rg.FindStringSubmatch("ana@ejemplo.com")
for i, nombre := range rg.SubexpNames() {
	if i > 0 && nombre != "" {
		fmt.Printf("%-8s %s\n", nombre+":", m[i])
	}
}
salidausuario: ana
dominio: ejemplo.com

FindStringSubmatch devuelve un slice donde la posición 0 es la coincidencia entera y las siguientes son los grupos. SubexpNames() da los nombres en el mismo orden, y por eso se recorren a la vez. La posición 0 se salta porque no tiene nombre.

Sustituir

fmt.Println(re.ReplaceAllString(texto, "[correo oculto]"))
salidaContactos: [correo oculto] (Madrid), [correo oculto] (Lima), [correo oculto]

Y usando los grupos en el reemplazo, con $nombre:

fmt.Println(rg.ReplaceAllString("ana@ejemplo.com", "$usuario (en $dominio)"))
salidaana (en ejemplo.com)

Partir una cadena

sep := regexp.MustCompile(`\s*[,;]\s*`)
fmt.Printf("%q\n", sep.Split("uno, dos ;tres,cuatro", -1))
salida["uno" "dos" "tres" "cuatro"]

Útil cuando el separador varía o lleva espacios alrededor. Para un separador fijo y simple es mejor strings.Split, que no necesita compilar nada.

Cuando el patrón está mal

if _, err := regexp.Compile(`[a-`); err != nil {
	fmt.Println(err)
}
salidaerror parsing regexp: missing closing ]: `[a-`

Esa es la diferencia entre las dos funciones: Compile devuelve un error y MustCompile provoca un panic. Usa MustCompile con patrones escritos por ti, y Compile cuando el patrón venga de fuera, de una configuración o del usuario.

Lo que RE2 no hace

Esto no existe en Go Por qué
Retrorreferencias, (\w)\1 Obligarían a retroceder y romperían la garantía de tiempo lineal
Lookahead y lookbehind, (?=…) Lo mismo
Patrones que se cuelgan Justamente: no los hay. RE2 nunca entra en una explosión exponencial

En la práctica se nota poco, y lo que se gana es que ningún patrón, venga de donde venga, puede tumbar el servicio.

Todo el código se compiló y ejecutó con Go 1.25 antes de publicarla; las salidas están copiadas de esa ejecución, incluido el error del patrón mal escrito. Documentación oficial: paquete regexp y su sintaxis.