Expresiones regulares con Go Parte 1

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. Puede extraer las partes y partes importantes del texto con muchas decoraciones o contenido no relacionado. Go tiene un paquete de expresiones regulares fuerte en su biblioteca estándar que le permite cortar y cortar texto con expresiones regulares. 

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

Entender las expresiones regulares

Comencemos con un ejemplo rápido. Tiene algún texto y desea comprobar 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 se separará del resto del texto por espacio. 

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

paquete main import ("os" "regexp" "fmt") func check (error err) si err! = nil fmt.Println (err.Error ()) os.Exit (1) func main ()  correos electrónicos: = [] cadena "brown @ fox", "brown @ fox.", "[email protected]", "br @ own @ fox.com", patrón: = '^ \ w + @ \ w + \ . \ w + $ 'para _, correo electrónico: = rango de correos electrónicos coincidente, err: = regexp.Match (patrón, [] byte (correo electrónico)) marque (err) si coincide fmt.Printf ("√'% s ' un correo electrónico válido \ n ", correo electrónico) else fmt.Printf (" X '% s' no es un correo electrónico válido \ n ", correo electrónico) Salida: X 'brown @ fox' no es un correo electrónico válido X 'brown @ fox'. no es un correo electrónico válido √ '[email protected]' es un correo electrónico válido X 'br @ own @ fox.com' no es un correo electrónico válido

Nuestra expresión regular trabaja 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 fue formateado correctamente. El último candidato tenía dos símbolos @.

Vamos a romper este regex abajo: ^ \ w + @ \ w + \. \ w + $

Personaje / Símbolo Sentido
^ Comienzo del texto de destino.
\ w Cualquier palabra de caracteres [0-9A-Za-z_]
+ Al menos uno de los personajes anteriores.
@ Literalmente el carácter @ 
\. El caracter del punto literal. Debe ser escapado con \
PS Fin del texto de destino

En total, esta expresión regular hará coincidir fragmentos de texto que comiencen con uno o más caracteres de palabra, seguidos del carácter "@", seguidos de nuevo por uno o más caracteres de palabra, seguidos de un punto y seguidos de nuevo por uno o más caracteres de palabra.  

Tratar con personajes especiales

Los siguientes caracteres tienen significados especiales en expresiones regulares: .+*? () | [] ^ $ \. Ya hemos visto muchos de ellos en el ejemplo de correo electrónico. Si queremos hacerlos coincidir literalmente, debemos escapar de ellos con una barra invertida. Vamos a introducir una pequeña función de ayuda llamada partido() Eso nos ahorrará mucho escribir. Toma un patrón y algún texto, usa el regexp.Match () Método para hacer coincidir el patrón con el texto (después de convertir el texto a una matriz de bytes), e imprime los resultados:

func match (cadena de patrón, cadena de texto) matched, _: = regexp.Match (patrón, [] byte (text)) si coincide fmt.Println ("√", pattern, ":", text) else  fmt.Println ("X", patrón, ":", texto)

Aquí hay un ejemplo de hacer coincidir un personaje regular como z vs. emparejar un personaje especial como ?:

func main () text: = "¿Puedo hacer cheezburger?" patrón: = "z" coincidencia (patrón, texto) patrón = "\\?" coincidencia (patrón, texto) patrón = '\?' coincidencia (patrón, texto) Salida: √ z: ¿Puedo hacer cheezburger? √ \? : ¿Puedo hacer cheezburger? √ \? : ¿Puedo hacer cheezburger? 

El regex \? 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 caracteres especiales en las cadenas Go como newline (\norte). Si quieres hacer coincidir el propio carácter de barra invertida, necesitarás cuatro barras diagonales! 

La solución es usar Go Raw Strings con el backtick (') en lugar de comillas dobles. Por supuesto, si desea hacer coincidir el carácter de nueva línea, debe 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 intenta 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 algún lugar. Regexes admite este caso de uso con el punto  . Personaje especial que simboliza a cualquier personaje. los * el carácter especial repite el carácter (o grupo) anterior cero o más veces. Si los combinas, como en .*, luego se hace coincidir cualquier cosa porque simplemente significa cero o más caracteres. los + es muy similar a *, pero coincide con uno o más de los personajes o grupos anteriores. Asi que .+ coincidirá con cualquier texto no vacío.

Usando Límites

Hay tres tipos de límites: el inicio del texto denotado por ^, el final del texto denotado por PS, y la palabra límite denotado por \segundo. Por ejemplo, considera este texto de la película clásica. La novia princesa: "Mi nombre es Iñigo Montoya. Mataste a mi padre. Prepárate para morir". Si coincide solo con "padre" obtendrá una coincidencia, pero si busca "padre" al final del texto, debe agregar el PS personaje, y entonces no habrá ninguna coincidencia. Por otro lado, emparejar "Hola" al principio funciona bien.

func main () text: = "Hola, mi nombre es Iñigo Montoya, usted mató a mi padre, prepárese para morir". patrón: = "padre" coincidencia (patrón, texto) patrón = "padre $" coincidencia (patrón, texto) patrón = "^ Hola" coincidencia (patrón, texto) Salida: √ padre: Hola, mi nombre es Iñigo Montoya, Tú mataste a mi padre, prepárate para morir. X padre $: Hola, mi nombre es Iñigo Montoya, mataste a mi padre, prepárate para morir. √ ^ Hola: Hola, mi nombre es Iñigo Montoya, mataste a mi padre, prepárate para morir. 

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

func main () text: = 'Hola, mi nombre es Iñigo Montoya, usted mató a mi padre, prepárese para morir'. patrón: = 'matar' coincidencia (patrón, texto) patrón = '\ bkill' coincidencia (patrón, texto) patrón = 'matar \ b' coincidir (patrón, texto) patrón = '\ bkill \ b' coincidir (patrón, texto) ) pattern = '\ bkilled \ b' match (patrón, texto) pattern = '\ bMontoya, \ b' match (patrón, texto) Salida: √ kill: Hola, mi nombre es Iñigo Montoya, mataste a mi padre, prepárate morir. √ \ bkill: Hola, mi nombre es Iñigo Montoya, mataste a mi padre, prepárate para morir. X kill \ b: Hola, mi nombre es Iñigo Montoya, mataste a mi padre, prepárate para morir. X \ bkill \ b: Hola, mi nombre es Iñigo Montoya, tú mataste a mi padre, prepárate para morir. √ \ bkilled \ b: Hola, mi nombre es Iñigo Montoya, mataste a mi padre, prepárate para morir. X \ bMontoya, \ b: Hola, mi nombre es Iñigo Montoya, mataste a mi padre, prepárate para morir.

Usando clases

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

Clase de personaje Sentido
[: alnum:]
alfanumérico (≡ [0-9A-Za-z])
[:alfa:]
alfabético (≡ [A-Za-z])
[: ascii:] 
ASCII (≡ [\ x00- \ x7F])
[:blanco:] 
en blanco (≡ [\ t])
[: cntrl:]
control (≡ [\ x00- \ x1F \ x7F])
[:dígito:]
dígitos (≡ [0-9])
[:grafico:]
gráfico (≡ [! - ~] == [A-Za-z0-9! "# $% & '() * +, \ -. / :;<=>?@ [\\\] ^ _ '| ~])
[:inferior:] 
minúscula (≡ [a-z])
[:impresión:] 
imprimible (≡ [- ~] == [[: graph:]])
[: punteado:]
puntuación (≡ [! - /: - @ [- '- ~])
[:espacio:]
espacio en blanco (≡ [\ t \ n \ v \ f \ r])
[:Superior:]
mayúscula (≡ [A-Z])
[:palabra:]
caracteres de la palabra (≡ [0-9A-Za-z_])
[: xdigit:]
dígito hexadecimal (≡ [0-9A-Fa-f])

En el siguiente ejemplo, usaré el [:dígito:] Clase 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.

func main () text: = 'La respuesta a la vida, el universo y todo es 42. "patrón: =" [[: dígito:]] 3 "coincidencia (patrón, texto) patrón =" [[: dígito: ]] 2 "coincidencia (patrón, texto) Salida: X [[: dígito:]] 3: La respuesta a la vida, el universo y todo es 42. √ [[: dígito:]] 2: La respuesta a la vida, al universo y todo es 42.. 

También puedes definir tus propias clases poniendo los caracteres entre corchetes. Por ejemplo, si desea comprobar si algún texto es una secuencia de ADN válida que contiene solo los caracteres ACGT luego usa el ^ [ACGT] * $ expresiones regulares

func main () text: = "AGGCGTTGGGAACGTT" pattern: = "^ [ACGT] * $" match (pattern, text) text = "No es exactamente una secuencia de ADN" match (pattern, text) Output: √ ^ [ACGT ] * $: AGGCGTTGGGAACGTT X ^ [ACGT] * $: No es exactamente una secuencia de ADN

Usando Alternativas

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

func main () pattern: = '(http) | (https): // \ w + \. \ w 2,' match (pattern, "http://tutsplus.com") match (pattern, "https : //tutsplus.com ") coincidencia (patrón," htt: //tutsplus.com ") Resultado: √ (http) | (https): // \ w + \. \ w 2,: http: / /tutsplus.com √ (http) | (https): // \ w + \. \ w 2,: https://tutsplus.com X (http) | (https): // \ w + \. \ w 2,: htt: //tutsplus.com

Conclusión

En esta parte del tutorial, cubrimos mucho terreno y aprendimos mucho sobre 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 utilizando expresiones regulares. 

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