обзор
Регулярные выражения (AKA regex) — это формальный язык, который определяет последовательность символов с некоторым шаблоном. В реальном мире их можно использовать для решения множества проблем с полуструктурированным текстом. Вы можете извлечь важные фрагменты из текста с большим количеством украшений или несвязанного содержимого. Go имеет в своей стандартной библиотеке надежный пакет регулярных выражений, который позволяет вырезать и вырезать текст с помощью регулярных выражений.
В этой серии из двух частей вы узнаете, что такое регулярные выражения и как эффективно использовать регулярные выражения в Go для решения многих общих задач. Если вы совсем не знакомы с регулярными выражениями, есть много отличных уроков. Вот хороший .
Понимание регулярных выражений
Давайте начнем с быстрого примера. У вас есть текст, и вы хотите проверить, содержит ли он адрес электронной почты. Адрес электронной почты строго указан в RFC 822 . Короче говоря, он имеет локальную часть, за которой следует символ @, за которым следует домен. Почтовый адрес будет отделен от остального текста пробелом.
Чтобы выяснить, содержит ли он адрес электронной почты, подойдет следующее регулярное выражение: ^\w+@\w+\.\w+$
. Обратите внимание, что это регулярное выражение немного допустимо и пропустит некоторые недействительные адреса электронной почты. Но это достаточно хорошо, чтобы продемонстрировать концепцию. Давайте попробуем это на паре потенциальных адресов электронной почты, прежде чем объяснить, как это работает:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
package main
import (
«os»
«regexp»
«fmt»
)
func check(err error) {
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
func main() {
emails := []string{
«brown@fox»,
«brown@fox.»,
«br@[email protected]»,
}
pattern := `^\w+@\w+\.\w+$`
for _, email := range emails {
matched, err := regexp.Match(pattern, []byte(email))
check(err)
if matched {
fmt.Printf(«√ ‘%s’ is a valid email\n», email)
} else {
fmt.Printf(«X ‘%s’ is not a valid email\n», email)
}
}
}
Output:
X ‘brown@fox’ is not a valid email
X ‘brown@fox.’
√ ‘[email protected]’ is a valid email
X ‘br@[email protected]’ is not a valid email
|
Наше регулярное выражение работает на этом маленьком образце. Первые два адреса были отклонены, поскольку в домене не было точки или не было символов после точки. Третье письмо было отформатировано правильно. У последнего кандидата было два символа @.
Давайте разберем это регулярное выражение: ^\w+@\w+\.\w+$
Символ / Symbol | Смысл |
---|---|
^ | Начало целевого текста |
\ ш | Любые символы слова [0-9A-Za-z_] |
+ | Хотя бы один из предыдущих персонажей |
@ | Буквально символ @ |
\. | Буквенный точечный символ. Нужно сбежать с \ |
$ | Конец целевого текста |
В целом, это регулярное выражение будет соответствовать фрагментам текста, которые начинаются с одного или нескольких символов слова, после которых следует символ «@», затем снова один или несколько символов слова, затем точка и затем снова один или несколько символов слова.
Работа со специальными персонажами
Следующие символы имеют специальные значения в регулярных выражениях:. .+*?()|[]{}^$\
. Мы уже видели многих из них в примере электронной почты. Если мы хотим сопоставить их буквально, нам нужно избежать обратной косой черты. Давайте представим небольшую вспомогательную функцию под названием match()
, которая сэкономит нам много печатания. Он берет шаблон и некоторый текст, использует метод regexp.Match()
для сопоставления шаблона с текстом (после преобразования текста в байтовый массив) и печатает результаты:
1
2
3
4
5
6
7
8
|
func match(pattern string, text string) {
matched, _ := regexp.Match(pattern, []byte(text))
if matched {
fmt.Println(«√», pattern, «:», text)
} else {
fmt.Println(«X», pattern, «:», text)
}
}
|
Вот пример соответствия обычного символа, такого как z
и соответствия специального символа, такого как ?
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
func main() {
text := «Can I haz cheezburger?»
pattern := «z»
match(pattern, text)
pattern = «\\?»
match(pattern, text)
pattern = `\?`
match(pattern, text)
}
Output:
√ z : Can I haz cheezburger?
√ \?
√ \?
|
Шаблон регулярного выражения \?
содержит обратную косую черту, которая должна быть экранирована другой обратной косой чертой, когда она представлена в виде обычной строки Go. Причина в том, что обратная косая черта также используется для экранирования специальных символов в строках Go, таких как символ новой строки ( \n
). Если вы хотите сопоставить сам символ обратной косой черты, вам понадобится четыре слеша!
Решение состоит в том, чтобы использовать строки Go raw с обратным кавычком ( `
) вместо двойных кавычек. Конечно, если вы хотите сопоставить символ новой строки, вы должны вернуться к обычным строкам и иметь дело с множественными символами обратной косой черты.
Заполнители и повторы
В большинстве случаев вы не пытаетесь буквально сопоставить последовательность определенных символов, таких как «abc», но последовательность неизвестной длины с, возможно, некоторыми известными символами, вставленными где-то. Регулярные выражения поддерживают этот вариант использования с точкой .
специальный символ, который обозначает любого персонажа. Специальный символ *
повторяет предыдущий символ (или группу) ноль или более раз. Если вы объединяете их, как в .*
, То вы сопоставляете что угодно, потому что это просто означает ноль или более символов Знак +
очень похож на *
, но соответствует одному или нескольким предыдущим символам или группам. Итак .+
Будет соответствовать любому непустому тексту.
Использование границ
Существует три типа границ: начало текста, обозначенного ^
, конец текста, обозначенного $
, и граница слова, обозначенная \b
. Например, рассмотрим текст из классического фильма «Принцесса-невеста» : «Меня зовут Иниго Монтойя. Ты убил моего отца. Приготовься умереть». Если вы сопоставляете просто «папа», вы получаете совпадение, но если вы ищете «папа» в конце текста, вам нужно добавить символ $
, и тогда совпадения не будет. С другой стороны, соответствие «Hello» в начале работает хорошо.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
func main() {
text := «Hello, my name is Inigo Montoya, you killed my father, prepare to die.»
pattern := «father»
match(pattern, text)
pattern = «father$»
match(pattern, text)
pattern = «^Hello»
match(pattern, text)
}
Output:
√ father : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
X father$ : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
√ ^Hello : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
|
Границы слова смотрят на каждое слово. Вы можете начать и / или закончить шаблон с помощью \b
. Обратите внимание, что знаки препинания, такие как запятые, считаются границей, а не частью слова. Вот несколько примеров:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
func main() {
text := `Hello, my name is Inigo Montoya,
you killed my father, prepare to die.`
pattern := `kill`
match(pattern, text)
pattern = `\bkill`
match(pattern, text)
pattern = `kill\b`
match(pattern, text)
pattern = `\bkill\b`
match(pattern, text)
pattern = `\bkilled\b`
match(pattern, text)
pattern = `\bMontoya,\b`
match(pattern, text)
}
Output:
√ kill : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
√ \bkill : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
X kill\b : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
X \bkill\b : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
√ \bkilled\b : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
X \bMontoya,\b : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
|
Использование классов
Часто полезно рассматривать все группы символов вместе как все цифры, пробельные символы или все буквенно-цифровые символы. Golang поддерживает классы POSIX, которые:
Класс персонажа | Смысл |
---|---|
[Цифра, буква] | буквенно-цифровой (≡ [0-9A-Za-z]) |
[:альфа:] | буквенный (≡ [A-Za-z]) |
[: ASCII:] | ASCII (≡ [\ x00- \ x7F]) |
[: Пусто:] | пусто (≡ [\ t]) |
[: CNTRL:] | управление (≡ [\ x00- \ x1F \ x7F]) |
[: Цифры:] | цифры (≡ [0-9]) |
[: График:] | графический (≡ [! — ~] == [A-Za-z0-9! «# $% & ‘() * +, \ -. / :; <=>? @ [\\\] ^ _` { |} ~]) |
[: Опустить:] | строчные буквы (az [аз]) |
[:Распечатать:] | для печати (≡ [- ~] == [[: graph:]]) |
[Пунктуатор] | пунктуация (! [! — /: — @ [- `{- ~]) |
[:Космос:] | пробел (≡ [\ t \ n \ v \ f \ r]) |
[: Верхняя:] | верхний регистр (AZ [AZ]) |
[:слово:] | символы слова (≡ [0-9A-Za-z_]) |
[: Xdigit:] | шестнадцатеричная цифра (≡ [0-9A-Fa-f]) |
В следующем примере я буду использовать класс [:digit:]
для поиска чисел в тексте. Также я покажу здесь, как искать точное количество символов, добавляя запрошенное число в фигурных скобках.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
func main() {
text := `The answer to life, universe and
everything is 42 .»
pattern := «[[:digit:]]{3}»
match(pattern, text)
pattern = «[[:digit:]]{2}»
match(pattern, text)
}
Output:
X [[:digit:]]{3} : The answer to life, universe and
everything is 42.
√ [[:digit:]]{2} : The answer to life, universe and
everything is 42.
|
Вы также можете определять свои собственные классы, помещая символы в квадратные скобки. Например, если вы хотите проверить, является ли какой-либо текст допустимой последовательностью ДНК, содержащей только символы ACGT
используйте регулярное выражение ^[ACGT]*$
:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
func main() {
text := «AGGCGTTGGGAACGTT»
pattern := «^[ACGT]*$»
match(pattern, text)
text = «Not exactly a DNA sequence»
match(pattern, text)
}
Output:
√ ^[ACGT]*$ : AGGCGTTGGGAACGTT
X ^[ACGT]*$ : Not exactly a DNA sequence
|
Использование альтернатив
В некоторых случаях существует несколько жизнеспособных альтернатив. Соответствующие URL-адреса HTTP могут характеризоваться схемой протокола, которая является либо http://
либо https://
. Символ трубы |
позволяет выбирать между альтернативами. Вот регулярное выражение, которое отсортирует их: (http)|(https)://\w+\.\w{2,}
. Он переводится в строку, которая начинается с http://
или https://
за которым следует хотя бы один символ слова, за которым следует точка, за которой следуют как минимум два слова.
01
02
03
04
05
06
07
08
09
10
11
12
|
func main() {
pattern := `(http)|(https)://\w+\.\w{2,}`
match(pattern, «http://tutsplus.com»)
match(pattern, «https://tutsplus.com»)
match(pattern, «htt://tutsplus.com»)
}
Output:
√ (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
|
Вывод
В этой части руководства мы рассмотрели много вопросов и узнали много нового о регулярных выражениях, используя практические примеры с использованием библиотеки регулярных выражений Golang. Мы сосредоточились на чистом сопоставлении и на том, как выразить свои намерения с помощью регулярных выражений.
Во второй части мы сосредоточимся на использовании регулярных выражений для работы с текстом, включая нечеткое нахождение, замены, группирование и работу с новыми строками.