Percursos
/
Go
Go
/
Exercícios
/
Análise de ficheiros de registo
Análise de ficheiros de registo

Análise de ficheiros de registo

Exercício de aprendizagem

Introdução

O pacote regexp oferece suporte para expressões regulares em Go.

Sintaxe

A sintaxe das expressões regulares aceites é a mesma sintaxe geral usada por Perl, Python e outras linguagens.

Tanto os padrões de pesquisa como os textos de entrada são interpretados como UTF-8.

Quando usas backticks (`) para criar strings, as barras invertidas (\) não têm qualquer significado especial e não marcam o início de carateres especiais como as tabulações \t ou as novas linhas \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 isso, é preferível usar backticks para criar expressões regulares, porque assim não precisamos de escapar as barras invertidas:

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

Compilar padrões - o tipo RegExp

Para usar uma expressão regular, temos primeiro de compilar o padrão, que é uma string. Compilar, aqui, significa pegar no padrão da expressão regular e convertê-lo numa representação interna com que é mais fácil trabalhar. Só precisamos de compilar cada padrão uma vez; a partir daí, podemos usar a versão compilada da expressão regular tantas vezes quantas quisermos. O tipo regexp.Regexp representa uma expressão regular compilada. Podemos compilar um padrão em string para um regexp.Regexp com a função regexp.Compile. Esta função devolve nil e um erro se a compilação falhar:

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)+`

A função MustCompile é uma alternativa prática a Compile:

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

Com esta função, não é preciso tratar o erro.

Caution

MustCompile só deve ser usado quando temos a certeza de que o padrão compila, porque, caso contrário, o programa vai entrar em pânico.

Métodos das expressões regulares

Há 16 métodos de Regexp que fazem corresponder uma expressão regular e identificam o texto correspondente. Os seus nomes correspondem a esta expressão regular:

Find(All)?(String)?(Submatch)?(Index)?
  • Se All estiver presente, o método encontra correspondências sucessivas e sem sobreposição da expressão inteira.
  • Se String estiver presente, o argumento é uma string; caso contrário, é uma fatia de bytes; os valores devolvidos são ajustados em conformidade.
  • Se Submatch estiver presente, o valor devolvido é uma fatia que identifica as subcorrespondências sucessivas da expressão.
  • Se Index estiver presente, as correspondências e as subcorrespondências são identificadas por pares de índices de bytes dentro da string de entrada.

Há também métodos para:

  • substituir correspondências de expressões regulares por strings de substituição e
  • dividir strings separadas por expressões regulares.

No total, o pacote regexp define mais de 40 funções e métodos. Vamos demonstrar a utilização de alguns métodos a seguir. Consulta a documentação da API para mais detalhes sobre estas e outras funções.

Exemplos de MatchString

O método MatchString indica se uma string contém alguma correspondência da expressão 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

Exemplos de FindString

O método FindString devolve uma string com o texto da correspondência mais à esquerda da expressão 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")     // => ""

Exemplos de FindStringSubmatch

O método FindStringSubmatch devolve uma fatia de strings com o texto da correspondência mais à esquerda da expressão regular e as correspondências, se existirem, das suas subexpressões. Isto pode ser usado para identificar as strings que correspondem a grupos de captura. Um valor devolvido nil indica que não há correspondência.

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>

Exemplos de ReplaceAllString

O método re.ReplaceAllString(src,repl) devolve uma cópia de src, substituindo as correspondências da expressão regular re pela string de substituição 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"

Exemplos de Split

O método re.Split(s,n) divide um texto s em substrings separadas pela expressão e devolve uma fatia com as substrings que ficam entre essas correspondências da expressão. O valor n determina o número máximo de substrings a devolver. Se n<0, o método devolve todas as substrings.

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"}

Instruções

Este exercício aborda a análise de ficheiros de log.

Após uma auditoria de segurança recente, pediram-te que limpasses os ficheiros de log arquivados da organização.

Todas as strings passadas às funções são garantidamente não nulas e sem espaços no início nem no fim.

1. Identifica linhas de log corrompidas

Precisas de ter uma ideia de quantas linhas de log do teu arquivo não cumprem os padrões atuais. Acreditas que um teste simples revela se uma linha de log é válida. Para ser considerada válida, uma linha tem de começar com uma das seguintes strings:

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

Implementa a função IsValidLine de modo a devolver false se uma string não for válida e true caso contrário.

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

2. Divide a linha de log

Uma nova equipa juntou-se à organização e descobriste que os ficheiros de log dessa equipa usam um separador estranho para os «campos». Em vez de algo sensato como dois pontos «:», usam uma string como «<--->» ou «<=>» (porque é mais bonito), na verdade qualquer string cujo primeiro caráter seja «<» e o último seja «>», com qualquer combinação dos seguintes carateres pelo meio: «~», «*», «=» e «-».

Implementa a função SplitLogLine, que recebe uma linha e devolve um array de strings, cada uma delas com um campo.

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

3. Conta o número de linhas que contêm password em texto entre aspas

A equipa precisa de saber que referências a palavras-passe existem em texto entre aspas, para as poder examinar manualmente.

Implementa a função CountQuotedPasswords para teres uma ideia da provável dimensão do trabalho manual.

Identifica as linhas de log em que a string «password», que pode estar em qualquer combinação de maiúsculas e minúsculas, está rodeada por aspas. Deves ter em conta a possibilidade de existir conteúdo adicional entre as aspas, antes e depois de «password». Cada linha terá, no máximo, duas aspas.

As linhas passadas à rotina podem ser válidas ou não, segundo a definição da tarefa 1. Processamo-las da mesma forma, sejam válidas ou não.

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. Remove artefactos do log

Descobriste que algum processamento anterior dos logs tem vindo a espalhar pelos logs o texto «end-of-line» seguido de um número de linha (sem espaço pelo meio).

Implementa a função RemoveEndOfLineText, que recebe uma string, remove o texto end-of-line e devolve uma string «limpa».

As linhas que não contenham texto end-of-line devem ser devolvidas sem alterações.

Limita-te a remover a string end-of-line. Não tentes ajustar os espaços em branco.

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

5. Etiqueta linhas com nomes de utilizador

Reparaste que algumas das linhas de log incluem frases que se referem a utilizadores. Essas frases contêm sempre a string "User", seguida de um ou mais espaços e depois de um nome de utilizador. Decidiste etiquetar essas linhas.

Implementa uma função TagWithUserName que processa linhas de log:

  • As linhas que não contêm a string "User " ficam inalteradas.
  • Nas linhas que contêm a string "User ", acrescenta no início da linha [USR] seguido do nome de utilizador.

Por exemplo:

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."
// }

Podes assumir que:

  • Os nomes de utilizador são seguidos de, pelo menos, um espaço em branco no log.
  • Existe, no máximo, uma ocorrência da string "User " em cada linha.
  • Os nomes de utilizador são strings não vazias e sem espaços em branco.
Editar via GitHub A ligação abre numa nova janela ou separador
Go Exercism

Estás pronto para começar Análise de ficheiros de registo?

Inscreve-te no Exercism para aprenderes e dominares Go com 34 conceitos165 exercícios, e mentoria humana real, tudo grátis.