Scroll to top
This post is part of a series called Regular Expressions With Go.
Regular Expressions With Go: Part 2

Spanish (Español) translation by steven (you can also view the original English article)

Visión general

Las expresiones regulares (AKA regex) son un lenguaje formal que define una secuencia de caracteres con algún patrón. En el mundo real, se pueden utilizar para resolver muchos problemas con texto semiestructurado. Puedes extraer los fragmentos importantes del texto con muchas decoraciones o contenido no relacionado. Go tiene un paquete de expresiones regulares fuerte en su biblioteca estándar que te permite cortar y cortar texto con expresiones regulares.

En esta serie de dos partes, aprenderás qué son las expresiones regulares y cómo usarlas de manera efectiva en Go para realizar muchas tareas comunes. Si no estás familiarizado con las expresiones regulares, hay muchos tutoriales excelentes. Aquí hay uno bueno.

Entendiendo las expresiones regulares

Comencemos con un ejemplo rápido. Tienes algo de texto y deseas verificar si contiene una dirección de correo electrónico. Una dirección de correo electrónico se especifica rigurosamente en RFC 822. En resumen, tiene una parte local seguida de un símbolo @ seguido de un dominio. La dirección de correo estará separada del resto del texto por un espacio.

Para averiguar si contiene una dirección de correo electrónico, la siguiente expresión regular servirá: ^\w+@\w+\.\w+$. Ten en cuenta que esta expresión regular es un poco permisiva y permitirá el paso de algunas direcciones de correo electrónico no válidas. Pero es lo suficientemente bueno para demostrar el concepto. Probémoslo en un par de posibles direcciones de correo electrónico antes de explicar cómo funciona:

1
package main
2
3
import (
4
    "os"
5
    "regexp"
6
    "fmt"
7
)
8
9
10
func check(err error) {
11
    if err != nil {
12
        fmt.Println(err.Error())
13
        os.Exit(1)
14
    }
15
}
16
17
func main() {
18
    emails := []string{
19
        "brown@fox",
20
        "brown@fox.",
21
        "brown@fox.com",
22
        "br@own@fox.com",
23
    }
24
25
    pattern := `^\w+@\w+\.\w+$`
26
    for _, email := range emails {
27
        matched, err := regexp.Match(pattern, []byte(email))
28
        check(err)
29
        if matched {
30
            fmt.Printf("√ '%s' is a valid email\n", email)
31
        } else {
32
            fmt.Printf("X '%s' is not a valid email\n", email)
33
        }
34
    }
35
}
36
37
Output:
38
39
X 'brown@fox' is not a valid email
40
X 'brown@fox.' is not a valid email
41
√ 'brown@fox.com' is a valid email
42
X 'br@own@fox.com' is not a valid email

Nuestra expresión regular funciona en esta pequeña muestra. Las dos primeras direcciones fueron rechazadas porque el dominio no tenía un punto o no tenía ningún carácter después del punto. El tercer correo electrónico se formateó correctamente. El último candidato tenía dos símbolos @.

Analicemos esta expresión regular: ^\w+@\w+\.\w+$

Carácter/Símbolo Significado
^ Comienzo del texto objetivo
\w Caracteres de cualquier palabra [0-9A-Za-z_]
+ Al menos uno de los caracteres anteriores
@ Literalmente el carácter @
\. Literal el carácter de punto. Debe escaparse con \
$ Fin del texto objetivo

En total, esta expresión regular coincidirá con fragmentos de texto que comiencen con una o más palabras, seguido del carácter "@", seguido de nuevo por una o más palabras, seguido de un punto y seguido de nuevo por una o más palabras.

Tratar con caracteres especiales

Los siguientes caracteres tienen significados especiales en expresiones regulares: .+*?()|[]{}^$\. Ya hemos visto muchos de ellos en el ejemplo del correo electrónico. Si queremos emparejarlos literalmente, debemos escapar de ellos con una barra invertida. Introduzcamos una pequeña función auxiliar llamada match() que nos ahorrará mucha escritura. Toma un patrón y algo de texto, usa el método regexp.Match() para hacer coincidir el patrón con el texto (después de convertir el texto en una matriz de bytes) e imprime los resultados:

1
func match(pattern string, text string) {
2
    matched, _ := regexp.Match(pattern, []byte(text))
3
	if matched {
4
		fmt.Println("√", pattern, ":", text)
5
	} else {
6
		fmt.Println("X", pattern, ":", text)
7
	}
8
}

A continuación, se muestra un ejemplo de cómo hacer coincidir un carácter regular como z frente a hacer coincidir un carácter especial como ?:

1
func main() {
2
    text := "Can I haz cheezburger?"
3
	pattern := "z"
4
	match(pattern, text)
5
6
	pattern = "\\?"
7
	match(pattern, text)
8
9
	pattern = `\?`
10
	match(pattern, text)
11
}
12
13
Output:
14
15
 z : Can I haz cheezburger?
16
 \? : Can I haz cheezburger?
17
 \? : Can I haz cheezburger?

El patrón de expresiones regulares \? contiene una barra invertida que debe escaparse con otra barra invertida cuando se representa como una cadena Go normal. La razón es que la barra invertida también se usa para escapar de los caracteres especiales en cadenas de Go como nueva línea (\n). Si deseas hacer coincidir el carácter de barra invertida, necesitarás cuatro barras.

La solución es usar cadenas de Go sin formato con la comilla invertida (`) en lugar de comillas dobles. Por supuesto, si deseas hacer coincidir el carácter de nueva línea, debes volver a las cadenas normales y lidiar con múltiples escapes de barra invertida.

Marcadores de posición y repeticiones

En la mayoría de los casos, no intentas hacer coincidir literalmente una secuencia de caracteres específicos como "abc", sino una secuencia de longitud desconocida con quizás algunos caracteres conocidos inyectados en alguna parte. Las expresiones regulares admiten este caso de uso con el punto . carácter especial que representa cualquier carácter. El carácter especial * repite el carácter (o grupo) anterior cero o más veces. Si los combinas, como en .*, entonces coincide con cualquier cosa porque simplemente significa cero o más caracteres. El + es muy similar a *, pero coincide con uno o más de los caracteres o grupos anteriores. Entonces .+ Coincidirá con cualquier texto que no esté vacío.

Usando límites

Hay tres tipos de límites: el comienzo del texto denotado por ^, el final del texto denotado por $ y el límite de la palabra denotado por \b. Por ejemplo, considera este texto de la película clásica La princesa prometida: "Mi nombre es Iñigo Montoya. Mataste a mi padre. Prepárate para morir". Si solo coincides con "padre", obtendrás una coincidencia, pero si estás buscando "padre"al final del texto, debes agregar el carácter $, y entonces no habrá ninguna coincidencia. Por otro lado, hacer coincidir "Hola" al principio funciona bien.

1
func main() {
2
    text := "Hello, my name is Inigo Montoya, you killed my father, prepare to die."
3
	pattern := "father"
4
	match(pattern, text)
5
6
	pattern = "father$"
7
	match(pattern, text)
8
9
	pattern = "^Hello"
10
	match(pattern, text)
11
}
12
13
Output:
14
15
 father  : Hello, my name is Inigo Montoya, 
16
            you killed my father, prepare to die.
17
X father$ : Hello, my name is Inigo Montoya, 
18
            you killed my father, prepare to die.
19
 ^Hello :  Hello, my name is Inigo Montoya, 
20
            you killed my father, prepare to die.

Los límites de las palabras miran cada palabra. Puedes iniciar y/o finalizar un patrón con \b. Ten en cuenta que los signos de puntuación como las comas se consideran un límite y no parte de la palabra. Aquí están algunos ejemplos:

1
func main() {
2
    text := `Hello, my name is Inigo Montoya, 
3
	         you killed my father, prepare to die.`
4
	pattern := `kill`
5
	match(pattern, text)
6
7
	pattern = `\bkill`
8
	match(pattern, text)
9
10
	pattern = `kill\b`
11
	match(pattern, text)
12
13
	pattern = `\bkill\b`
14
	match(pattern, text)
15
16
	pattern = `\bkilled\b`
17
	match(pattern, text)
18
19
	pattern = `\bMontoya,\b`
20
	match(pattern, text)
21
}
22
23
Output:
24
25
 kill :         Hello, my name is Inigo Montoya, 
26
                 you killed my father, prepare to die.
27
 \bkill :       Hello, my name is Inigo Montoya, 
28
                 you killed my father, prepare to die.
29
X kill\b :       Hello, my name is Inigo Montoya, 
30
                 you killed my father, prepare to die.
31
X \bkill\b :     Hello, my name is Inigo Montoya, 
32
                 you killed my father, prepare to die.
33
 \bkilled\b :   Hello, my name is Inigo Montoya, 
34
                 you killed my father, prepare to die.
35
X \bMontoya,\b : Hello, my name is Inigo Montoya, 
36
                 you killed my father, prepare to die.

Usando clases

A menudo es útil tratar todos los grupos de caracteres juntos como todos los dígitos, espacios en blanco o todos los caracteres alfanuméricos. Golang admite las clases POSIX, que son:

Clase de caracteres Significado
[:alnum:] alfanumérico (≡ [0-9A-Za-z])
[:alpha:] alfabético (≡ [A-Za-z])
[:ascii:] ASCII (≡ [\x00-\x7F])
[:blank:] en blanco (≡ [\t ])
[:cntrl:] control (≡ [\x00-\x1F\x7F])
[:digit:] dígitos (≡ [0-9])
[:graph:] gráfico (≡ [!-~] == [A-Za-z0-9!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])
[:lower:] minúsculas (≡ [a-z])
[:print:] imprimible (≡ [ -~] == [ [:graph:]])
[:punct:] puntuación (≡ [!-/:-@[-`{-~])
[:space:] espacio en blanco (≡ [\t\n\v\f\r ])
[:upper:] mayúsculas (≡ [A-Z])
[:word:] caracteres de palabras (≡ [0-9A-Za-z_])
[:xdigit:] dígito hexadecial (≡ [0-9A-Fa-f])

En el siguiente ejemplo, usaré la clase [:digit:] para buscar números en el texto. Además, muestro aquí cómo buscar un número exacto de caracteres agregando el número solicitado entre llaves.

1
func main() {
2
    text := `The answer to life, universe and
3
             everything is 42 ."

4
	pattern := "[[:digit:]]{3}"
5
	match(pattern, text)
6
7
	pattern = "[[:digit:]]{2}"
8
	match(pattern, text)
9
}
10
11
Output:
12
13
X [[:digit:]]{3} : The answer to life, universe and 
14
                   everything is 42.
15
 [[:digit:]]{2} : The answer to life, universe and
16
                   everything is 42.

También puedes definir tus propias clases poniendo caracteres entre corchetes. Por ejemplo, si deseas verificar si algún texto es una secuencia de ADN válida que contiene solo los caracteres ACGT, usa la expresión regular ^[ACGT]*$:

1
func main() {
2
    text := "AGGCGTTGGGAACGTT"
3
	pattern := "^[ACGT]*$"
4
	match(pattern, text)
5
6
	text = "Not exactly a DNA sequence"
7
	match(pattern, text)
8
}
9
10
Output:
11
12
 ^[ACGT]*$ : AGGCGTTGGGAACGTT
13
X ^[ACGT]*$ : Not exactly a DNA sequence

Uso de alternativas

En algunos casos, existen múltiples alternativas viables. Las URL HTTP coincidentes se pueden caracterizar por un esquema de protocolo, que es https:// o https://. El carácter de la pipa | te permite elegir entre alternativas. Aquí hay una expresión regular que los resolverá: (http)|(https)://\w+\.\w{2,}. Se traduce en una cadena que comienza con http:// o https:// seguida de al menos un carácter de palabra seguido de un punto seguido de al menos dos caracteres de palabra.

1
func main() {
2
    pattern := `(http)|(https)://\w+\.\w{2,}`

3
	match(pattern, "http://tutsplus.com")
4
	match(pattern, "https://tutsplus.com")
5
	match(pattern, "htt://tutsplus.com")
6
}
7
8
Output:
9
10
 (http)|(https)://\w+\.\w{2,} : http://tutsplus.com

11
 (http)|(https)://\w+\.\w{2,} : https://tutsplus.com

12
X (http)|(https)://\w+\.\w{2,} : htt://tutsplus.com

Conclusión

En esta parte del tutorial, cubrimos mucho terreno y aprendimos mucho sobre las expresiones regulares, con ejemplos prácticos utilizando la biblioteca de expresiones regulares de Golang. Nos centramos en la coincidencia pura y en cómo expresar nuestras intenciones mediante expresiones regulares.

En la segunda parte, nos centraremos en el uso de expresiones regulares para trabajar con texto, incluida la búsqueda difusa, los reemplazos, la agrupación y el tratamiento de nuevas líneas.