Треки
/
Go
Go
/
Вправи
/
Розбір лог-файлів
Розбір лог-файлів

Розбір лог-файлів

Навчальна вправа

Вступ

Пакет regexp надає підтримку регулярних виразів у Go.

Синтаксис

Синтаксис регулярних виразів, які тут приймаються, збігається із загальним синтаксисом, який використовують Perl, Python та інші мови.

І шаблони пошуку, і вхідні тексти інтерпретуються як UTF-8.

Коли ми створюємо рядки тексту (англ. string) за допомогою зворотних лапок (`) to make strings, backslashes (\) don't have any special meaning and don't mark the beginning of special characters like tabs \t or newlines \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'

Через це для створення регулярних виразів бажано використовувати зворотні лапки, бо тоді не потрібно екранувати зворотні слеші:

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

Компіляція шаблонів - тип RegExp

Щоб скористатися регулярним виразом, спочатку треба скомпілювати шаблон, заданий рядком тексту. Компіляція тут означає, що ми беремо шаблон регулярного виразу, заданий рядком тексту, і перетворюємо його на внутрішнє представлення, з яким простіше працювати. Кожен шаблон достатньо скомпілювати лише один раз, а потім ми можемо багаторазово використовувати скомпільовану версію регулярного виразу. Тип regexp.Regexp представляє скомпільований регулярний вираз. Ми можемо скомпілювати шаблон, заданий рядком тексту, у regexp.Regexp за допомогою функції regexp.Compile. Ця функція повертає nil та помилку, якщо компіляція не вдалася:

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

Функція MustCompile - зручна альтернатива Compile:

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

Використовуючи цю функцію, не потрібно обробляти помилку.

Caution

MustCompile варто використовувати лише тоді, коли ми точно знаємо, що шаблон справді компілюється, інакше програма запанікує.

Методи регулярних виразів

Існує 16 методів Regexp, які знаходять збіги за регулярним виразом і визначають відповідний текст. Їхні назви відповідають такому регулярному виразу:

Find(All)?(String)?(Submatch)?(Index)?
  • Якщо присутній All, метод знаходить послідовні збіги всього виразу, що не перекриваються.
  • Якщо присутній String, аргумент є рядком тексту; інакше це зріз байтів; повернені значення змінюються відповідним чином.
  • Якщо присутній Submatch, повернене значення - зріз, який визначає послідовні підзбіги виразу.
  • Якщо присутній Index, збіги та підзбіги визначаються парами байтових індексів усередині вхідного рядка тексту.

Також є методи для:

  • заміни збігів регулярних виразів на рядки заміни та
  • розбиття рядків тексту, розділених регулярними виразами.

Загалом пакет regexp визначає понад 40 функцій і методів. Нижче ми продемонструємо використання кількох методів. Деталі цих та інших функцій можна знайти в документації API.

Приклади MatchString

Метод MatchString повідомляє, чи містить рядок тексту будь-який збіг регулярного виразу.

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

Приклади FindString

Метод FindString повертає рядок тексту, що містить текст найлівішого збігу регулярного виразу.

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")     // => ""

Приклади FindStringSubmatch

Метод FindStringSubmatch повертає зріз рядків тексту, що містять текст найлівішого збігу регулярного виразу та збіги (якщо вони є) його підвиразів. Це можна використати, щоб визначити рядки тексту, які відповідають групам захоплення. Повернене значення nil означає відсутність збігу.

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>

Приклади ReplaceAllString

Метод re.ReplaceAllString(src,repl) повертає копію src, замінюючи збіги регулярного виразу re на рядок заміни 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"

Приклади Split

Метод re.Split(s,n) розбиває текст s на підрядки, розділені виразом, і повертає зріз підрядків між цими збігами виразу. Число n визначає максимальну кількість підрядків, які треба повернути. Якщо n<0, метод повертає всі підрядки.

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

Вказівки

Ця вправа присвячена синтаксичному аналізу файлів журналу.

Після нещодавньої перевірки безпеки нас попросили впорядкувати архівні файли журналів організації.

Усі рядки тексту (англ. string), які передаються функціям, гарантовано не є null і не мають пробілів на початку та в кінці.

1. Визначте спотворені рядки журналу

Потрібно хоч приблизно уявити, скільки рядків журналу в архіві не відповідають поточним стандартам. Ми вважаємо, що проста перевірка виявляє, чи є рядок журналу коректним. Щоб рядок вважався коректним, він має починатися з одного з таких рядків тексту:

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

Реалізуйте функцію IsValidLine, яка повертає false, якщо рядок тексту некоректний, і true в іншому разі.

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

2. Розділіть рядок журналу

До організації приєдналася нова команда, і в її файлах журналу виявився дивний розділювач для «полів». Замість чогось розумного, як-от двокрапка ":", вони використовують рядок тексту на кшталт "<--->" або "<=>" (бо так гарніше), а насправді будь-який рядок тексту, у якого перший символ "<", останній символ ">", а між ними може стояти будь-яка комбінація таких символів: "~", "*", "=" і "-".

Реалізуйте функцію SplitLogLine, яка приймає рядок і повертає масив рядків тексту, кожен з яких містить одне поле.

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

3. Порахуйте кількість рядків, що містять password у тексті в лапках

Команді потрібно знати про згадки паролів у тексті в лапках, щоб їх можна було перевірити вручну.

Реалізуйте функцію CountQuotedPasswords, яка дасть уявлення про ймовірний обсяг ручної роботи.

Визначте рядки журналу, у яких рядок тексту "password", записаний у будь-якій комбінації великих і малих літер, узятий у лапки. Врахуйте можливість додаткового вмісту між лапками перед і після "password". Кожен рядок містить щонайбільше два знаки лапок.

Рядки, передані функції, можуть бути як коректними, так і некоректними за визначенням із завдання 1. Ми опрацьовуємо їх однаково, незалежно від того, коректні вони чи ні.

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. Вилучіть артефакти з журналу

Виявилося, що деяка попередня обробка журналів розкидала по них текст "end-of-line" із номером рядка одразу після нього (без пробілу між ними).

Реалізуйте функцію RemoveEndOfLineText, яка приймає рядок тексту, вилучає з нього текст end-of-line і повертає «чистий» рядок тексту.

Рядки, які не містять тексту end-of-line, повертаються без змін.

Просто вилучте рядок тексту end-of-line. Не намагайтеся виправляти пробільні символи.

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

5. Позначте рядки іменами користувачів

Ми помітили, що деякі рядки журналу містять речення про користувачів. Ці речення завжди містять рядок тексту "User", після якого йде один або більше пробільних символів, а потім імʼя користувача. Тож ми вирішуємо позначати такі рядки.

Реалізуйте функцію TagWithUserName, яка опрацьовує рядки журналу:

  • Рядки, які не містять рядка тексту "User ", залишаються без змін.
  • У рядках, які містять рядок тексту "User ", додайте на початок [USR], а потім імʼя користувача.

Наприклад:

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

Можна припустити, що:

  • Після імені користувача в журналі стоїть щонайменше один пробільний символ.
  • У кожному рядку трапляється щонайбільше одне входження рядка тексту "User ".
  • Імена користувачів - це непорожні рядки тексту, які не містять пробільних символів.
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
Go Exercism

Час розпочати Розбір лог-файлів?

Зареєструйтеся на Exercism, щоб вивчати й опановувати Go, а також 34 концепції165 вправ та справжнє наставництво від людей, і все це безкоштовно.