Rutas
/
Go
Go
/
Ejercicios
/
Análisis de archivos de registro
Análisis de archivos de registro

Análisis de archivos de registro

Ejercicio de aprendizaje

Introducción

El paquete regexp ofrece soporte para expresiones regulares en Go.

Sintaxis

La sintaxis de las expresiones regulares aceptadas es la misma sintaxis general que utilizan Perl, Python y otros lenguajes.

Tanto los patrones de búsqueda como los textos de entrada se interpretan como UTF-8.

Cuando usas comillas invertidas (\) para crear strings, las barras invertidas (\) no tienen ningún significado especial y no marcan el inicio de caracteres especiales como los tabuladores \t o los saltos de línea \n:

"\t\n" // regular string literal with 2 characters: a tab and a newline
`\t\n`// raw string literal with 4 characters: two backslashes, a 't', and an 'n'

Por esto, es recomendable usar comillas invertidas para crear expresiones regulares, porque así no necesitamos escapar las barras invertidas:

"\\" // string with a single backslash
`\\` // string with 2 backslashes

Compilar patrones: el tipo RegExp

Para usar una expresión regular, primero debemos compilar el patrón de string. Aquí, compilar significa tomar el patrón de string de la expresión regular y convertirlo en una representación interna con la que sea más fácil trabajar. Solo necesitamos compilar cada patrón una vez; después, podemos usar la versión compilada de la expresión regular muchas veces. El tipo regexp.Regexp representa una expresión regular compilada. Podemos compilar un patrón de string en un regexp.Regexp usando la función regexp.Compile. Esta función devuelve nil y un error si la compilación falla:

re, err := regexp.Compile(`(a|b)+`)
fmt.Println(re, err) // => (a|b)+ <nil>
re, err = regexp.Compile(`a|b)+`)
fmt.Println(re, err) // => <nil> error parsing regexp: unexpected ): `a|b)+`

La función MustCompile es una alternativa cómoda a Compile:

re = regexp.MustCompile(`[a-z]+\d*`)

Con esta función, no es necesario gestionar un error.

Caution

MustCompile solo se debe usar cuando sabemos con seguridad que el patrón se compila; de lo contrario, el programa entrará en pánico.

Métodos de expresiones regulares

Hay 16 métodos de Regexp que coinciden con una expresión regular e identifican el texto coincidente. Sus nombres coinciden con esta expresión regular:

Find(All)?(String)?(Submatch)?(Index)?
  • Si All está presente, el método encuentra coincidencias sucesivas no superpuestas de toda la expresión.
  • Si String está presente, el argumento es un string; en caso contrario, es un slice de bytes; los valores devueltos se ajustan según corresponda.
  • Si Submatch está presente, el valor devuelto es un slice que identifica las subcoincidencias sucesivas de la expresión.
  • Si Index está presente, las coincidencias y subcoincidencias se identifican mediante pares de índices de bytes dentro del string de entrada.

También hay métodos para:

  • reemplazar las coincidencias de expresiones regulares por strings de reemplazo, y
  • dividir strings separados por expresiones regulares.

En total, el paquete regexp define más de 40 funciones y métodos. A continuación, mostraremos el uso de algunos métodos. Consulta la documentación de la API para ver los detalles de estas y otras funciones.

Ejemplos de MatchString

El método MatchString indica si un string contiene alguna coincidencia de una expresión regular.

re = regexp.MustCompile(`[a-z]+\d*`)
b = re.MatchString("[a12]")       // => true
b = re.MatchString("12abc34(ef)") // => true
b = re.MatchString(" abc!")       // => true
b = re.MatchString("123 456")     // => false

Ejemplos de FindString

El método FindString devuelve un string que contiene el texto de la coincidencia más a la izquierda de la expresión regular.

re = regexp.MustCompile(`[a-z]+\d*`)
s = re.FindString("[a12]")       // => "a12"
s = re.FindString("12abc34(ef)") // => "abc34"
s = re.FindString(" abc!")       // => "abc"
s = re.FindString("123 456")     // => ""

Ejemplos de FindStringSubmatch

El método FindStringSubmatch devuelve un slice de strings que contiene el texto de la coincidencia más a la izquierda de la expresión regular y las coincidencias, si las hay, de sus subexpresiones. Esto se puede usar para identificar los strings que coinciden con grupos de captura. Un valor devuelto de nil indica que no hay coincidencia.

re = regexp.MustCompile(`[a-z]+(\d*)`)
sl = re.FindStringSubmatch("[a12]")       // => []string{"a12","12"}
sl = re.FindStringSubmatch("12abc34(ef)") // => []string{"abc34","34"}
sl = re.FindStringSubmatch(" abc!")       // => []string{"abc",""}
sl = re.FindStringSubmatch("123 456")     // => <nil>

Ejemplos de ReplaceAllString

El método re.ReplaceAllString(src,repl) devuelve una copia de src, reemplazando las coincidencias de la expresión regular re por el string de reemplazo repl.

re = regexp.MustCompile(`[a-z]+\d*`)
s = re.ReplaceAllString("[a12]", "X")       // => "[X]"
s = re.ReplaceAllString("12abc34(ef)", "X") // => "12X(X)"
s = re.ReplaceAllString(" abc!", "X")       // => " X!"
s = re.ReplaceAllString("123 456", "X")     // => "123 456"

Ejemplos de Split

El método re.Split(s,n) divide un texto s en subcadenas separadas por la expresión y devuelve un slice de las subcadenas entre esas coincidencias de la expresión. El recuento n determina el número máximo de subcadenas que se devuelven. Si n<0, el método devuelve todas las subcadenas.

re = regexp.MustCompile(`[a-z]+\d*`)
sl = re.Split("[a12]", -1)      // => []string{"[","]"}
sl = re.Split("12abc34(ef)", 2) // => []string{"12","(ef)"}
sl = re.Split(" abc!", -1)      // => []string{" ","!"}
sl = re.Split("123 456", -1)    // => []string{"123 456"}

Instrucciones

Este ejercicio aborda el análisis de archivos de registro.

Tras una revisión de seguridad reciente, se te ha pedido que limpies los archivos de registro archivados de la organización.

Se garantiza que todos los strings que se pasan a las funciones no son nulos y no tienen espacios al principio ni al final.

1. Identifica las líneas de registro corruptas

Necesitas hacerte una idea de cuántas líneas de registro de tu archivo no cumplen los estándares actuales. Crees que una prueba sencilla revela si una línea de registro es válida. Para considerarse válida, una línea debe empezar por uno de los siguientes strings:

  • [TRC]
  • [DBG]
  • [INF]
  • [WRN]
  • [ERR]
  • [FTL]

Implementa la función IsValidLine para que devuelva false si el string no es válido y true en caso contrario.

IsValidLine("[ERR] A good error here")
// => true
IsValidLine("Any old [ERR] text")
// => false
IsValidLine("[BOB] Any old text")
// => false

2. Divide la línea de registro

Un equipo nuevo se ha incorporado a la organización y descubres que sus archivos de registro usan un separador extraño para los «campos». En lugar de algo sensato como dos puntos «:», usan un string como «<--->» o «<=>» (porque es más bonito); de hecho, cualquier string que tenga «<» como primer carácter y «>» como último carácter, y cualquier combinación de los caracteres «~», «*», «=» y «-» en medio.

Implementa la función SplitLogLine, que toma una línea y devuelve un array de strings, cada uno de los cuales contiene un campo.

SplitLogLine("section 1<*>section 2<~~~>section 3")
// => []string{"section 1", "section 2", "section 3"},

3. Cuenta el número de líneas que contienen password en texto entre comillas

El equipo necesita saber qué referencias a contraseñas aparecen entre comillas para poder examinarlas manualmente.

Implementa la función CountQuotedPasswords para darte una idea de la probable magnitud del trabajo manual.

Identifica las líneas de registro en las que el string «password», que puede estar en cualquier combinación de mayúsculas y minúsculas, está rodeado de comillas. Debes tener en cuenta la posibilidad de que haya contenido adicional entre las comillas, antes y después de «password». Cada línea contendrá como máximo dos comillas.

Las líneas que se pasan a la rutina pueden ser válidas o no según lo definido en la tarea 1. Las procesamos del mismo modo, tanto si son válidas como si no.

lines := []string{
    `[INF] passWord`, // contains 'password' but not surrounded by quotation marks
    `"passWord"`,  // count this one
    `[INF] User saw error message "Unexpected Error" on page load.`, // does not contain 'password'
    `[INF] The message "Please reset your password" was ignored by the user`, // count this one
}
// => 2

4. Elimina los artefactos del registro

Has descubierto que cierto procesamiento previo de los registros ha ido dispersando por ellos el texto «end-of-line» seguido de un número de línea (sin un espacio intermedio).

Implementa la función RemoveEndOfLineText, que toma un string, elimina el texto «end-of-line» y devuelve un string «limpio».

Las líneas que no contengan el texto «end-of-line» deben devolverse sin modificar.

Limítate a eliminar el string «end-of-line». No intentes ajustar los espacios en blanco.

RemoveEndOfLineText("[INF] end-of-line23033 Network Failure end-of-line27")
// => "[INF]  Network Failure "

5. Etiqueta las líneas con nombres de usuario

Te has dado cuenta de que algunas líneas de registro incluyen frases que se refieren a usuarios. Estas frases siempre contienen el string "User", seguido de uno o más espacios y, a continuación, un nombre de usuario. Decides etiquetar esas líneas.

Implementa una función TagWithUserName que procese líneas de registro:

  • Las líneas que no contengan el string "User " permanecen sin cambios.
  • En las líneas que contengan el string "User ", antepón a la línea [USR] seguido del nombre de usuario.

Por ejemplo:

result := TagWithUserName([]string{
    "[WRN] User James123 has exceeded storage space.",
	"[WRN] Host down. User   Michelle4 lost connection.",
	"[INF] Users can login again after 23:00.",
	"[DBG] We need to check that user names are at least 6 chars long.",
})
// => []string {
//  "[USR] James123 [WRN] User James123 has exceeded storage space.",
//  "[USR] Michelle4 [WRN] Host down. User   Michelle4 lost connection.",
//  "[INF] Users can login again after 23:00.",
//  "[DBG] We need to check that user names are at least 6 chars long."
// }

Puedes dar por sentado que:

  • Los nombres de usuario van seguidos de al menos un espacio en blanco en el registro.
  • Hay como máximo una aparición del string "User " en cada línea.
  • Los nombres de usuario son strings no vacíos que no contienen espacios en blanco.
Editar en GitHub El enlace se abre en una ventana o pestaña nueva
Go Exercism

¿Listo para empezar Análisis de archivos de registro?

Regístrate en Exercism para aprender y dominar Go con 34 conceptos165 ejercicios y mentoría humana real, todo gratis.