정규표현식 — 패턴으로 문자열을 찾기
정규표현식 — 패턴으로 문자열을 찾기
"이 텍스트에서 이메일만 뽑아 줘" 같은 요청이 들어오면 가장 먼저 떠오르는 도구가 정규표현식 (regex). 짧은 문자열로 강한 표현력을 발휘하지만, 같은 이유로 처음 익힐 때는 외계 문자처럼 보입니다. 이 글은 정규표현식의 출자 · 핵심 메타문자 · 자주 쓰는 패턴 · 언어별 차이 · "완벽한 정규식" 의 함정.
1. 정규표현식에 대한 이야기
뿌리는 1956 년 수학자 Stephen Kleene 의 정규 집합 (regular set) 표기. 1968 년 Bell Labs 의 Ken Thompson 이 텍스트 에디터 QED 에 처음 정규식 검색을 넣었고, 같은 해 발표한 논문이 정규식 → NFA 변환 알고리즘의 표준이 됐습니다. 이후 1973 년 grep, 1980 년대 sed · awk, 1987 년 Perl 이 정규식을 보편화.
오늘날 정규식 문법은 크게 두 갈래:
| 갈래 | 출자 | 어디서 |
|---|---|---|
| POSIX BRE / ERE | 1986 POSIX 표준 | 전통 grep · sed |
| PCRE (Perl Compatible) | 1987 Perl, 1997 PCRE 라이브러리 | 거의 모든 모던 도구 |
JS · Python · Java · .NET · Go · Rust · Ruby 모두 PCRE 와 비슷하지만 미묘한 차이. 같은 패턴이 한 언어에서 동작하고 다른 언어에서 안 동작하는 일이 흔합니다.
2. 메타문자
| 문자 | 의미 |
|---|---|
. |
줄바꿈을 제외한 임의의 한 글자 |
^ · $ |
줄 (또는 문자열) 의 시작 · 끝 |
* |
직전 토큰 0회 이상 |
+ |
직전 토큰 1회 이상 |
? |
직전 토큰 0회 또는 1회 |
{n} · {n,m} |
정확히 n회 · n~m회 |
[abc] |
문자 클래스 (셋 중 하나) |
[^abc] |
부정 문자 클래스 (셋이 아닌 것) |
[a-z] |
범위 |
\d · \D |
숫자 · 비숫자 |
\w · \W |
단어 문자 ([A-Za-z0-9_]) · 그 외 |
\s · \S |
공백 · 비공백 |
\b · \B |
단어 경계 · 비경계 |
() |
그룹 (캡처) |
(?:) |
비캡처 그룹 |
(?<name>) |
이름 있는 그룹 |
| |
또는 (alternation) |
\ |
이스케이프 |
3. 욕심 · 게으름
* · + · ? 는 기본적으로 욕심 (greedy). 가능한 한 길게 매칭. *? · +? · ?? 를 쓰면 게으름 으로 바뀌어 가능한 한 짧게:
"<b>hello</b>"
<.*> → <b>hello</b> (전체)
<.*?> → <b> (가장 짧은 한 묶음)
HTML 태그처럼 가까운 짝을 잡고 싶을 때 *? · +? 가 자주 등장.
4. 그룹과 역참조
패턴: (\w+)\s+\1
대상: "ho ho"
매칭: "ho ho" (\1 이 첫 그룹과 같은 텍스트)
치환 시에도 그룹을 참조:
"2025-04-25".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1");
// "25/04/2025"
5. Lookahead · Lookbehind
매칭은 하지 않고 조건만 검사하는 도구.
| 표기 | 의미 |
|---|---|
(?=X) |
뒤에 X 가 옴 (긍정 lookahead) |
(?!X) |
뒤에 X 가 안 옴 (부정 lookahead) |
(?<=X) |
앞에 X 가 옴 (긍정 lookbehind) |
(?<!X) |
앞에 X 가 안 옴 (부정 lookbehind) |
패턴: \d+(?=원)
대상: "1500원"
매칭: "1500" (뒤에 "원" 이 있는 숫자만, "원" 자체는 매칭에서 제외)
lookbehind 는 일부 언어가 가변 길이를 허용하지 않음. JS 는 2018 년부터 지원, Python re 는 고정 길이만, regex 라이브러리는 가변 허용.
6. 플래그
| 플래그 | 의미 |
|---|---|
i |
대소문자 무시 |
g |
전역 매칭 (모든 출현) |
m |
다중 줄. ^ · $ 가 줄 단위로 |
s |
dotall. . 이 줄바꿈도 매칭 |
u |
유니코드. 코드포인트 단위 |
x |
확장 (공백 · 주석 허용, 파이썬 · PCRE) |
JS g 는 String.prototype.replaceAll · matchAll 같은 모든-매칭 동작에 필요.
7. 다른 길
같은 일을 정규식 외 도구로:
- CSV / JSON 파싱 — 구조화된 형식은 정규식보다 전용 파서가 항상 안전.
- HTML / XML 파싱 — 정규식으로 잘라 쓰면 무너짐. cheerio · BeautifulSoup · DOMParser 같은 파서.
- 자연어 검색 — 길어지는 정규식보다 검색엔진 (Elasticsearch · Postgres 풀텍스트) 이 더 적절한 자리.
- PEG · 파서 컴비네이터 — 더 복잡한 문법은 정규식의 영역이 아님.
8. 자주 쓰는 모양
기본 이메일 · URL · 전화번호 패턴은 "대충" 만 잡습니다. 완벽한 정규식은 거의 불가능에 가까움.
간단한 이메일 (대략):
^[\w.+-]+@[\w-]+\.[\w.-]+$
URL (대략):
\bhttps?://[^\s)]+
한국 휴대폰 (대략):
01[016789]-?\d{3,4}-?\d{4}
YYYY-MM-DD:
\b\d{4}-\d{2}-\d{2}\b
빈 줄:
^\s*$
ANSI 색상 코드 제거:
\x1b\[[0-9;]*m
언어별:
// JavaScript
const re = /^\d{4}-\d{2}-\d{2}$/;
re.test("2025-04-25"); // true
"a-1, b-2".match(/[a-z]-\d/g); // ["a-1", "b-2"]
"hello".replace(/l/g, "L"); // "heLLo"
# Python
import re
re.match(r"^\d{4}-\d{2}-\d{2}$", "2025-04-25")
re.findall(r"[a-z]-\d", "a-1, b-2")
re.sub(r"l", "L", "hello")
pat = re.compile(r"\d+", re.IGNORECASE)
// Java
Pattern p = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
Matcher m = p.matcher("2025-04-25");
m.find();
"hello".replaceAll("l", "L");
# grep · sed (mac · Linux)
grep -E "^[a-z]+@[a-z]+\.[a-z]+$" emails.txt
sed -E 's/[0-9]+/N/g' file.txt
# Windows PowerShell
Get-Content file.txt | Select-String -Pattern "ERROR"
"hello" -replace "l", "L"
9. 자주 걸리는 자리
이메일 / URL 의 완벽한 정규식은 사실상 불가능 — RFC 5321 / 5322 에 따른 진짜 이메일 정규식은 수백 자. "대략 잡고 실제 발송 또는 라이브러리 검증" 이 실용적.
HTML 정규식 파싱 — Stack Overflow 의 한 답변이 이 함정을 잘 풍자. 파서 사용.
catastrophic backtracking — (a+)+$ 같은 중첩 quantifier 가 입력에 따라 지수 시간 폭발. ReDoS 보안 취약점이 자주 여기서. 가능한 한 lookbehind / atomic group / 소유 quantifier 또는 RE2 같은 선형 시간 엔진.
. 의 줄바꿈 — 기본은 매칭 안 함. 다중 줄 텍스트를 .* 로 잡으려면 s 플래그 또는 [\s\S]*.
유니코드 — \w 가 ASCII 만이라 한글이 빠짐. JS 는 u 플래그, Python 은 기본이 유니코드, Java 는 (?U) 또는 Pattern.UNICODE_CHARACTER_CLASS.
이스케이프 — 언어 문자열 리터럴에서 한 번, 정규식에서 한 번. 두 번 이스케이프해야 하는 경우. Python 은 raw string r"\d", JS 는 정규식 리터럴 /\d/.
POSIX vs PCRE 차이 — \d 가 POSIX BRE 에서 안 통함. 전통 grep 에서는 [0-9] 또는 grep -E 의 ERE 에서 [[:digit:]].
하고픈 말
정규식은 짧은 문자열로 큰 표현력을 주는 도구. 단 "완벽한" 패턴을 추구하면 catastrophic backtracking · RFC 충돌 · 유니코드 · 언어별 차이의 늪. 대충 잡고 실제 검증은 라이브러리 가 운영 안전한 기본 자세. HTML · 자연어 · 깊은 구조에는 정규식 대신 파서 또는 검색엔진.
Next
- git-submodule-lfs
- (tools 끝)
regex101.com · regexr.com · RegexLearn · MDN Regular Expressions · Python re 문서 · Java Pattern · Russ Cox — Regular Expression Matching Can Be Simple And Fast · RE2 · OWASP ReDoS 를 참고합니다.