한 번 컴파일하고 매칭하기
패턴은 백틱으로 감싼 원시 문자열 리터럴로 쓰세요. 일반 큰따옴표 문자열에서는 모든 백슬래시를 두 번 써야 하므로("\\d+") 금방 읽을 수 없게 됩니다.
regexp.MustCompile은 패턴이 잘못되면 패닉을 일으키는데, 소스 코드에 쓴 패턴에는 그게 바람직합니다. 오타가 첫 요청이 아니라 시작 시점에 바로 실패하기 때문입니다. 사용자나 설정에서 오는 패턴에는 regexp.Compile을 쓰고 오류를 처리하세요:
re, err := regexp.Compile(userPattern)
if err != nil {
return fmt.Errorf("bad pattern: %w", err)
}
한 번만 컴파일하세요. 컴파일은 매칭보다 훨씬 비쌉니다. 요청마다 또는 줄마다 실행되는 함수 안의 regexp.MustCompile은 그 작업을 매번 반복합니다. 컴파일된 *regexp.Regexp는 패키지 수준 var나 구조체 필드에 두세요. 컴파일된 정규식은 여러 고루틴에서 동시에 써도 안전합니다.
regexp.MatchString(pattern, s)는 컴파일과 매칭을 한 번에 합니다. 일회성 검사에는 괜찮지만 반복문에서는 낭비입니다.
일치 항목 찾기
메서드 이름은 Find + All? + String? + Submatch? + Index? 패턴을 따릅니다.
| 부분 | 의미 |
|---|---|
All | 겹치지 않는 모든 일치, 제한 n을 받음(전부는 -1) |
String | string에 동작, 없으면 []byte를 받고 반환 |
Submatch | 캡처 그룹도 반환 |
Index | 텍스트 대신 바이트 오프셋 반환 |
FindString은 일치가 없으면 ""를 반환하는데, 빈 문자열과 일치한 경우와 구분할 수 없습니다. 패턴이 빈 텍스트와 일치할 수 있다면 FindStringIndex(일치가 없으면 nil 반환)를 쓰거나 먼저 MatchString을 쓰세요. All 변형은 아무것도 일치하지 않으면 nil을 반환합니다.
캡처 그룹
괄호가 캡처합니다. FindStringSubmatch는 인덱스 0에 전체 일치를, 그다음에 그룹마다 항목 하나씩을 반환합니다:
결과를 인덱싱하기 전에 항상 nil인지 확인하세요. 일치가 없으면 m[1]에서 패닉이 납니다.
(?:...)는 캡처하지 않고 묶어서 선택이나 반복에 씁니다: (?:ab)+. 이름 있는 그룹은 (?P<name>...)로 쓰며, Go 1.22부터는 (?<name>...)도 됩니다.
치환하기
그룹 참조의 전형적인 함정이 있습니다. "$1x"는 존재하지 않는 1x라는 이름의 그룹으로 읽혀서 빈 문자열로 확장됩니다. "${1}x"라고 쓰세요.
고정된 문자열이라면 strings.ReplaceAll, strings.Contains, strings.Split이 정규식보다 단순하고 빠릅니다. 찾는 텍스트가 고정된 값이 아니라 모양을 가질 때 regexp를 꺼내세요.
문법 빠른 참조
Go는 RE2 문법을 쓰며, 익숙한 Perl 스타일 기능을 다룹니다:
| 문법 | 일치하는 것 |
|---|---|
. | 줄바꿈을 제외한 모든 문자((?s)와 함께면 줄바꿈도) |
\d \w \s | 숫자, 단어 문자 [0-9A-Za-z_], 공백(ASCII만) |
\D \W \S | 각각의 부정 |
[abc] [^abc] [a-z] | 문자 클래스 |
\pL \p{Greek} | 유니코드 클래스: 모든 글자, 모든 그리스 문자 |
* + ? {n,m} | 반복, 탐욕적 |
*? +? ?? | 반복, 게으른 |
^ $ | 텍스트의 시작과 끝((?m)과 함께면 줄의 시작과 끝) |
\b | 단어 경계(ASCII) |
a|b | 선택 |
(?i) | 그 지점부터 감싸는 그룹 끝까지 대소문자 무시, 보통 패턴 맨 앞에 씀 |
\d와 \w는 ASCII와만 일치합니다. "어떤 언어든 모든 글자"에는 \pL을, 모든 유니코드 숫자에는 \p{Nd}를 쓰세요. 한글 음절만 찾으려면 \p{Hangul}이 있습니다. regexp.QuoteMeta(s)는 s의 모든 특수 문자를 이스케이프하며, 사용자 입력으로 패턴을 만들 때 필요합니다.
RE2가 할 수 없는 것
Go의 regexp는 매칭이 입력 길이에 선형인 시간이 걸린다고 보장합니다. 이 보장을 깨는 기능은 지원되지 않습니다:
- 전방 탐색과 후방 탐색이 없습니다:
(?=...),(?!...),(?<=...),(?<!...)는 컴파일 오류입니다. Go 1.22부터 이름 있는 그룹에(?<name>...)를 받아들이기 시작했기 때문에, 이제 후방 탐색은 아래 프로그램에서 보듯invalid named capture라는 헷갈리는 메시지로 실패합니다. - 역참조가 없습니다: 같은 글자가 두 번 나오는 것을 찾는
(\w)\1은 불가능합니다. - 소유 수량자나 원자 그룹이 없습니다.
우회책은 거의 항상 같습니다. 필요한 것보다 조금 더 캡처한 뒤 Go에서 걸러 내거나 자르세요. 그 대가로 Go 정규식은 파국적 백트래킹에 빠질 수 없습니다. 짧은 입력으로도 PCRE 엔진을 멈추게 할 수 있는 (a+)+$ 같은 패턴이 여기서는 선형 시간에 실행되며, 패턴이나 입력이 외부에서 올 때 이것이 중요합니다.
흔한 실수
- 반복문이나 핸들러 안에서 컴파일함. 패키지 수준 변수에 한 번 컴파일하세요.
- 큰따옴표 패턴.
"\d"는 올바른 Go 문자열조차 아닙니다. 백틱을 쓰세요. - 검증에서 앵커를 잊음.
\d{5}는"abc123456xyz"안에서도 일치합니다.^\d{5}$를 쓰세요. nil확인 없이 서브매치 결과를 인덱싱함.\w나\b가 ASCII가 아닌 텍스트를 이해하길 기대함.\pL같은 유니코드 클래스를 쓰세요.- 고정된 문자열에 정규식을 씀.
strings패키지가 더 명확하고 빠릅니다.
자주 묻는 질문
Go에서 문자열이 정규식과 일치하는지 어떻게 확인하나요?
regexp.MustCompile로 패턴을 한 번 컴파일합니다. 백슬래시를 이스케이프할 필요가 없도록 백틱으로 감싼 원시 문자열로 쓰고, re.MatchString(s)를 호출하세요. 앵커(^와 $)가 없으면 MatchString은 패턴이 문자열 어디에서든 일치하면 true를 반환합니다.
Go regexp는 전방 탐색과 후방 탐색을 지원하나요?
아니요. Go의 regexp는 RE2 문법을 쓰며, 전방 탐색, 후방 탐색, 역참조가 없습니다. 그 대신 매칭이 입력 크기에 선형인 시간에 실행되므로 악의적인 패턴이나 입력이 프로그램을 멈추게 할 수 없습니다. 대신 캡처 그룹과 약간의 Go 코드를 쓰거나, 검사를 정규식 두 개로 나누세요.
regexp.Compile과 regexp.MustCompile의 차이는 무엇인가요?
Compile은 컴파일된 패턴과 오류를 반환합니다. MustCompile은 대신 잘못된 패턴에서 패닉을 일으킵니다. 소스 코드에 쓴 패턴에는 보통 패키지 수준 변수에 MustCompile을 써서 오타가 시작할 때 실패하게 하세요. 사용자나 설정 파일에서 오는 패턴에는 Compile을 쓰세요.
Go 정규식에서 캡처 그룹은 어떻게 가져오나요?
FindStringSubmatch를 씁니다. 인덱스 0이 전체 일치이고 인덱스 1, 2, ...가 그룹인 슬라이스를 반환하며, 일치가 없으면 nil을 반환합니다. 모든 일치가 필요하면 FindAllStringSubmatch(s, -1)를 쓰세요. 이름 있는 그룹 (?P<year>\d{4})는 re.SubexpIndex("year")로 찾을 수 있습니다.