Регулярные выражения в Go с примерами
Регулярные выражения (regex) — мощный инструмент для поиска, извлечения и замены текста по шаблону. В языке Go (Golang) работа с регулярными выражениями реализована через стандартный пакет regexp. В этой статье мы подробно разберём, как использовать регулярные выражения в Go: от простого поиска до сложных замен с группами захвата.
1. Основы пакета regexp
Пакет regexp предоставляет два основных подхода: быстрое однократное использование (функции уровня пакета) и производительная работа с предварительно скомпилированным шаблоном (структура Regexp). Для многократного использования всегда компилируйте шаблон заранее.
Компиляция регулярного выражения
package main
import (
"fmt"
"regexp"
)
func main() {
// Компиляция шаблона (рекомендуемый способ с обработкой ошибки)
pattern, err := regexp.Compile(`\d+`)
if err != nil {
fmt.Println("Ошибка компиляции:", err)
return
}
// Для простых случаев — без обработки ошибок (паника при невалидном шаблоне)
patternMust := regexp.MustCompile(`[a-z]+`)
fmt.Println(pattern, patternMust)
}
Compile возвращает ошибку, если шаблон невалиден. MustCompile вызывает панику — используйте его только для статических шаблонов.
2. Базовые операции: поиск и проверка
Рассмотрим основные методы для проверки соответствия и поиска подстрок.
Проверка на частичное совпадение
pattern := regexp.MustCompile(`go`)
text := "Я изучаю Golang"
// MatchString — содержит ли строка подстроку, соответствующую шаблону
matched := pattern.MatchString(text)
fmt.Println(matched) // true (так как "go" содержится в "Golang")
Проверка на полное совпадение
// Для проверки, что вся строка соответствует шаблону, используйте якоря ^ и $
fullPattern := regexp.MustCompile(`^[a-zA-Z]+$`)
fmt.Println(fullPattern.MatchString("Hello")) // true
fmt.Println(fullPattern.MatchString("Hello1")) // false
Поиск первого совпадения
datePattern := regexp.MustCompile(`\d{2}\.\d{2}\.\d{4}`)
text := "Сегодня 15.03.2024, а завтра 16.03.2024"
// FindString — возвращает первое совпадение
firstDate := datePattern.FindString(text)
fmt.Println(firstDate) // "15.03.2024"
Поиск всех совпадений
// FindAllString — возвращает срез всех совпадений
allDates := datePattern.FindAllString(text, -1) // -1 — все совпадения
fmt.Println(allDates) // ["15.03.2024" "16.03.2024"]
// Ограничение количества: FindAllString(text, 1) — только первое
3. Группы захвата и подстановки
Группы захвата позволяют извлекать части совпадения. В Go группы обозначаются круглыми скобками ().
Извлечение данных по группам
// Шаблон для извлечения имени и возраста: "Имя: [имя], Возраст: [число]"
personPattern := regexp.MustCompile(`Имя: (\w+), Возраст: (\d+)`)
text := "Имя: Алексей, Возраст: 30"
// FindStringSubmatch — возвращает срез: [всё_совпадение, группа1, группа2, ...]
matches := personPattern.FindStringSubmatch(text)
if len(matches) > 0 {
fmt.Println("Полное совпадение:", matches[0]) // "Имя: Алексей, Возраст: 30"
fmt.Println("Имя:", matches[1]) // "Алексей"
fmt.Println("Возраст:", matches[2]) // "30"
}
Именованные группы (Go 1.21+)
// С версии Go 1.21 можно использовать именованные группы: (?P<имя>шаблон)
pattern := regexp.MustCompile(`(?P<name>\w+)@(?P<domain>\w+\.\w+)`)
text := "test@example.com"
// SubexpNames — получаем имена групп
groupNames := pattern.SubexpNames()
matches := pattern.FindStringSubmatch(text)
// Создаем карту: имя_группы -> значение
result := make(map[string]string)
for i, name := range groupNames {
if i != 0 && name != "" {
result[name] = matches[i]
}
}
fmt.Println(result["name"]) // "test"
fmt.Println(result["domain"]) // "example.com"
Замена с использованием групп
// Замена: переставляем местами фамилию и имя
pattern := regexp.MustCompile(`(\w+) (\w+)`)
text := "Иванов Иван"
// ReplaceAllString — замена всех совпадений
// $1 — первая группа, $2 — вторая группа
result := pattern.ReplaceAllString(text, "$2 $1")
fmt.Println(result) // "Иван Иванов"
4. Продвинутые примеры и оптимизация
Валидация email
emailPattern := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
func isValidEmail(email string) bool {
return emailPattern.MatchString(email)
}
fmt.Println(isValidEmail("user@example.com")) // true
fmt.Println(isValidEmail("invalid-email")) // false
Разбор URL
urlPattern := regexp.MustCompile(`^(https?)://([^/]+)(/.*)?$`)
url := "https://golang.org/pkg/regexp/"
parts := urlPattern.FindStringSubmatch(url)
if len(parts) > 0 {
fmt.Println("Протокол:", parts[1]) // "https"
fmt.Println("Домен:", parts[2]) // "golang.org"
fmt.Println("Путь:", parts[3]) // "/pkg/regexp/"
}
Оптимизация производительности
// Плохо: компиляция при каждом вызове
func slowFindNumbers(text string) []string {
pattern := regexp.MustCompile(`\d+`)
return pattern.FindAllString(text, -1)
}
// Хорошо: компиляция один раз
var numberPattern = regexp.MustCompile(`\d+`)
func fastFindNumbers(text string) []string {
return numberPattern.FindAllString(text, -1)
}
// Для больших объём