Пакет 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*`)
Використовуючи цю функцію, не потрібно обробляти помилку.
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 і не мають пробілів на початку та в кінці.
Потрібно хоч приблизно уявити, скільки рядків журналу в архіві не відповідають поточним стандартам. Ми вважаємо, що проста перевірка виявляє, чи є рядок журналу коректним. Щоб рядок вважався коректним, він має починатися з одного з таких рядків тексту:
Реалізуйте функцію IsValidLine, яка повертає false, якщо рядок тексту некоректний, і true в іншому разі.
IsValidLine("[ERR] A good error here")
// => true
IsValidLine("Any old [ERR] text")
// => false
IsValidLine("[BOB] Any old text")
// => false
До організації приєдналася нова команда, і в її файлах журналу виявився дивний розділювач для «полів». Замість чогось розумного, як-от двокрапка ":", вони використовують рядок тексту на кшталт "<--->" або "<=>" (бо так гарніше), а насправді будь-який рядок тексту, у якого перший символ "<", останній символ ">", а між ними може стояти будь-яка комбінація таких символів: "~", "*", "=" і "-".
Реалізуйте функцію SplitLogLine, яка приймає рядок і повертає масив рядків тексту, кожен з яких містить одне поле.
SplitLogLine("section 1<*>section 2<~~~>section 3")
// => []string{"section 1", "section 2", "section 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
Виявилося, що деяка попередня обробка журналів розкидала по них текст "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 "
Ми помітили, що деякі рядки журналу містять речення про користувачів.
Ці речення завжди містять рядок тексту "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 ".Зареєструйтеся на Exercism, щоб вивчати й опановувати Go, а також 34 концепції165 вправ та справжнє наставництво від людей, і все це безкоштовно.