Split View: 정규표현식 완전 정복 2025: 기초부터 고급 패턴, 실전 활용까지 — 더 이상 두렵지 않다
정규표현식 완전 정복 2025: 기초부터 고급 패턴, 실전 활용까지 — 더 이상 두렵지 않다
1. 왜 정규표현식은 여전히 중요한가
2025년에도 정규표현식(Regular Expression, regex)은 개발자의 핵심 도구입니다.
입력 검증 — 이메일, URL, 전화번호, 신용카드 번호의 형식을 검증하는 가장 간결한 방법입니다.
로그 분석 — 수 GB의 로그 파일에서 특정 패턴(에러 코드, IP 주소, 타임스탬프)을 추출합니다.
텍스트 변환 — 코드 리팩토링, 데이터 클렌징, 파일 포맷 변환에 활용됩니다.
AI 프롬프트 전처리 — LLM에 입력하기 전에 불필요한 특수문자, HTML 태그 제거에 사용됩니다.
모든 언어에서 지원 — JavaScript, Python, Java, Go, Rust 등 거의 모든 프로그래밍 언어에 내장되어 있습니다.
2. 기초 문법: 리터럴과 메타문자
리터럴 매칭
정규표현식의 가장 기본은 리터럴 매칭입니다. 문자를 그대로 매칭합니다.
import re
# 리터럴 매칭
print(re.findall(r'hello', 'hello world hello'))
# ['hello', 'hello']
핵심 메타문자
| 메타문자 | 의미 | 예시 | 매칭 결과 |
|---|---|---|---|
. | 아무 문자 1개 (개행 제외) | h.t | hat, hit, hot |
^ | 문자열 시작 | ^Hello | "Hello world"의 Hello |
$ | 문자열 끝 | world$ | "Hello world"의 world |
* | 0회 이상 반복 | ab*c | ac, abc, abbc |
+ | 1회 이상 반복 | ab+c | abc, abbc (ac는 X) |
? | 0회 또는 1회 | colou?r | color, colour |
| | OR (또는) | cat|dog | cat 또는 dog |
[] | 문자 클래스 | [aeiou] | 모음 1개 |
() | 그룹화 / 캡처 | (ab)+ | ab, abab |
\ | 이스케이프 | \. | 실제 마침표 |
이스케이프가 필요한 문자
다음 문자들은 메타문자이므로 리터럴로 사용하려면 \를 앞에 붙여야 합니다:
. * + ? ^ $ | [ ] ( ) { } \
# 마침표를 리터럴로 매칭
print(re.findall(r'www\.example\.com', 'visit www.example.com'))
# ['www.example.com']
3. 문자 클래스 (Character Classes)
기본 문자 클래스
# [abc] — a, b, c 중 하나
re.findall(r'[aeiou]', 'hello world')
# ['e', 'o', 'o']
# [^abc] — a, b, c가 아닌 문자
re.findall(r'[^aeiou ]', 'hello world')
# ['h', 'l', 'l', 'w', 'r', 'l', 'd']
# [a-z] — 범위 지정
re.findall(r'[A-Z]', 'Hello World')
# ['H', 'W']
# [0-9a-fA-F] — 16진수 문자
re.findall(r'[0-9a-fA-F]+', 'color: #FF00AB')
# ['FF00AB']
축약 문자 클래스
| 축약 | 동등한 표현 | 의미 |
|---|---|---|
\d | [0-9] | 숫자 |
\D | [^0-9] | 숫자가 아닌 것 |
\w | [a-zA-Z0-9_] | 단어 문자 |
\W | [^a-zA-Z0-9_] | 단어 문자가 아닌 것 |
\s | [ \t\n\r\f\v] | 공백 문자 |
\S | [^ \t\n\r\f\v] | 공백이 아닌 것 |
# 전화번호 추출
text = "전화: 010-1234-5678, 팩스: 02-123-4567"
print(re.findall(r'\d{2,3}-\d{3,4}-\d{4}', text))
# ['010-1234-5678', '02-123-4567']
POSIX 문자 클래스 (일부 언어)
[:alpha:] — 알파벳 문자
[:digit:] — 숫자
[:alnum:] — 알파벳 + 숫자
[:space:] — 공백 문자
[:upper:] — 대문자
[:lower:] — 소문자
[:punct:] — 구두점
POSIX 클래스는 grep, sed 등 UNIX 도구에서 주로 사용되며, Python/JavaScript에서는 \d, \w 등을 사용합니다.
4. 수량자 (Quantifiers)
기본 수량자
# * (0회 이상)
re.findall(r'go*d', 'gd god good goood')
# ['gd', 'god', 'good', 'goood']
# + (1회 이상)
re.findall(r'go+d', 'gd god good goood')
# ['god', 'good', 'goood']
# ? (0 또는 1회)
re.findall(r'colou?r', 'color colour')
# ['color', 'colour']
정확한 횟수 지정
# {n} — 정확히 n회
re.findall(r'\d{4}', '2025 is the year 12345')
# ['2025', '1234']
# {n,m} — n회 이상 m회 이하
re.findall(r'\d{2,4}', '1 12 123 1234 12345')
# ['12', '123', '1234', '1234']
# {n,} — n회 이상
re.findall(r'\d{3,}', '1 12 123 1234')
# ['123', '1234']
탐욕적(Greedy) vs 게으른(Lazy) 매칭
기본적으로 수량자는 탐욕적입니다 — 가능한 한 많이 매칭합니다. ?를 뒤에 붙이면 게으른 모드가 됩니다.
text = '<b>bold</b> and <i>italic</i>'
# 탐욕적 (기본)
print(re.findall(r'<.*>', text))
# ['<b>bold</b> and <i>italic</i>'] — 전체를 하나로 매칭!
# 게으른 (?를 추가)
print(re.findall(r'<.*?>', text))
# ['<b>', '</b>', '<i>', '</i>'] — 각 태그를 개별 매칭
소유적(Possessive) 수량자
소유적 수량자(*+, ++, ?+)는 백트래킹을 하지 않습니다. Java, PCRE에서 지원합니다 (Python 표준 re에서는 미지원, regex 모듈에서 지원).
// Java 예시
// 소유적 수량자 — 백트래킹 없음
String pattern = "a++b"; // 'a'를 최대한 소비 후 돌아가지 않음
5. 그룹 (Groups)
캡처 그룹
# 기본 캡처 그룹
text = "2025-03-23"
match = re.match(r'(\d{4})-(\d{2})-(\d{2})', text)
if match:
print(match.group(0)) # '2025-03-23' (전체 매치)
print(match.group(1)) # '2025' (년)
print(match.group(2)) # '03' (월)
print(match.group(3)) # '23' (일)
비캡처 그룹
(?:...) — 그룹화하되 캡처하지 않습니다. 성능상 이점이 있습니다.
# 비캡처 그룹
text = "http://example.com https://secure.com"
print(re.findall(r'(?:https?://)(\S+)', text))
# ['example.com', 'secure.com'] — 도메인만 캡처
명명된 그룹 (Named Groups)
# 명명된 그룹
text = "2025-03-23"
match = re.match(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', text)
if match:
print(match.group('year')) # '2025'
print(match.group('month')) # '03'
print(match.group('day')) # '23'
역참조 (Backreferences)
# 중복 단어 찾기
text = "the the quick brown fox fox"
print(re.findall(r'\b(\w+)\s+\1\b', text))
# ['the', 'fox']
# HTML 태그 매칭 (같은 태그 이름)
html = "<div>content</div> <span>text</span>"
print(re.findall(r'<(\w+)>.*?</\1>', html))
# ['div', 'span']
6. 앵커와 경계 (Anchors and Boundaries)
기본 앵커
# ^ — 문자열 시작
print(re.findall(r'^Hello', 'Hello World\nHello Again'))
# ['Hello'] — 첫 번째만
# $ — 문자열 끝
print(re.findall(r'end$', 'start to end'))
# ['end']
# 멀티라인 모드
print(re.findall(r'^Hello', 'Hello World\nHello Again', re.MULTILINE))
# ['Hello', 'Hello'] — 각 줄의 시작
단어 경계 (\b)
\b는 단어 문자와 비단어 문자 사이의 위치를 매칭합니다. 제로폭이라 문자를 소비하지 않습니다.
text = "cat catfish concatenate scattered"
# \b 없이
print(re.findall(r'cat', text))
# ['cat', 'cat', 'cat', 'cat']
# \b로 정확한 단어만
print(re.findall(r'\bcat\b', text))
# ['cat']
# 단어 시작에서만
print(re.findall(r'\bcat\w*', text))
# ['cat', 'catfish', 'concatenate']
7. 전방탐색과 후방탐색 (Lookahead and Lookbehind)
전방탐색과 후방탐색은 제로폭 단언(Zero-width Assertions) 입니다. 문자를 소비하지 않고 조건만 확인합니다.
4가지 유형
| 유형 | 문법 | 의미 |
|---|---|---|
| 긍정 전방탐색 | (?=...) | 뒤에 ...이 있는 위치 |
| 부정 전방탐색 | (?!...) | 뒤에 ...이 없는 위치 |
| 긍정 후방탐색 | (?<=...) | 앞에 ...이 있는 위치 |
| 부정 후방탐색 | (?<!...) | 앞에 ...이 없는 위치 |
실용 예시: 비밀번호 검증
def validate_password(password):
"""
비밀번호 규칙:
- 8자 이상
- 대문자 최소 1개
- 소문자 최소 1개
- 숫자 최소 1개
- 특수문자 최소 1개
"""
pattern = r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$'
return bool(re.match(pattern, password))
print(validate_password("Abc123!@")) # True
print(validate_password("abc123")) # False (대문자, 특수문자 없음)
print(validate_password("Short1!")) # False (8자 미만)
금액 포맷팅 (후방탐색 활용)
# 숫자에 쉼표 추가 (천 단위 구분)
def format_number(n):
return re.sub(r'(?<=\d)(?=(\d{3})+(?!\d))', ',', str(n))
print(format_number(1234567890))
# '1,234,567,890'
부정 전방탐색 활용
# .js 파일이지만 .min.js가 아닌 것 찾기
files = ["app.js", "app.min.js", "utils.js", "vendor.min.js"]
pattern = r'^(?!.*\.min\.js$).*\.js$'
for f in files:
if re.match(pattern, f):
print(f)
# app.js
# utils.js
8. 실전 패턴 30선
이메일 관련
# 1. 기본 이메일 검증
email_basic = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
# 2. 이메일에서 도메인 추출
email_domain = r'@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})'
# 3. Gmail 주소만 매칭
gmail_only = r'^[a-zA-Z0-9._%+-]+@gmail\.com$'
URL 관련
# 4. 기본 URL 매칭
url_basic = r'https?://[a-zA-Z0-9.-]+(?:/[^\s]*)?'
# 5. URL에서 도메인 추출
url_domain = r'https?://([^/\s]+)'
# 6. 쿼리 파라미터 추출
query_param = r'[?&]([^=&]+)=([^&]*)'
IP 주소
# 7. IPv4 주소
ipv4 = r'\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b'
# 8. 사설 IP 주소
private_ip = r'\b(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})\b'
전화번호
# 9. 한국 휴대전화
kr_mobile = r'01[016789]-?\d{3,4}-?\d{4}'
# 10. 한국 일반전화
kr_phone = r'0\d{1,2}-?\d{3,4}-?\d{4}'
# 11. 국제 전화번호 (E.164)
intl_phone = r'\+\d{1,3}\d{4,14}'
날짜/시간
# 12. YYYY-MM-DD
date_iso = r'\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])'
# 13. 다양한 날짜 형식
date_multi = r'\d{4}[/.-]\d{1,2}[/.-]\d{1,2}'
# 14. 24시간 시간
time_24h = r'(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?'
# 15. ISO 8601 날짜시간
iso_datetime = r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})'
개발 관련
# 16. HTML 태그
html_tag = r'<\/?[a-zA-Z][a-zA-Z0-9]*(?:\s[^>]*)?\/?>'
# 17. HEX 색상 코드
hex_color = r'#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b'
# 18. 시맨틱 버전 (SemVer)
semver = r'\bv?(\d+)\.(\d+)\.(\d+)(?:-([\w.]+))?(?:\+([\w.]+))?\b'
# 19. JSON 키
json_key = r'"([^"\\]*)"\s*:'
# 20. CSS 클래스 셀렉터
css_class = r'\.[a-zA-Z_][\w-]*'
한국어/일본어
# 21. 한글만 (자모 포함)
hangul = r'[\uAC00-\uD7AF\u1100-\u11FF\u3130-\u318F]+'
# 22. 한글 이름 (2-5자)
kr_name = r'[\uAC00-\uD7AF]{2,5}'
# 23. 일본어 히라가나
hiragana = r'[\u3040-\u309F]+'
# 24. 일본어 가타카나
katakana = r'[\u30A0-\u30FF]+'
# 25. 한자 (CJK 통합)
kanji = r'[\u4E00-\u9FFF]+'
보안/검증
# 26. 강한 비밀번호
strong_password = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$'
# 27. SQL 인젝션 의심 패턴
sql_injection = r"(?i)(?:union\s+select|or\s+1\s*=\s*1|drop\s+table|insert\s+into)"
# 28. XSS 의심 패턴
xss_pattern = r'(?i)<script[^>]*>.*?</script>'
기타 유용한 패턴
# 29. 공백 정규화 (연속 공백을 하나로)
text = "too many spaces"
print(re.sub(r'\s+', ' ', text))
# 'too many spaces'
# 30. CSV 필드 추출 (쉼표 구분, 따옴표 지원)
csv_field = r'(?:"([^"]*)"|([^,]*))'
9. 언어별 정규표현식 차이
JavaScript
// 리터럴 방식
const re1 = /hello/gi;
// 생성자 방식
const re2 = new RegExp('hello', 'gi');
// 주요 메서드
'hello world'.match(/hello/); // ['hello']
'hello world'.replace(/hello/, 'hi'); // 'hi world'
/hello/.test('hello world'); // true
// Named Groups (ES2018+)
const match = '2025-03-23'.match(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
);
console.log(match.groups.year); // '2025'
// matchAll (ES2020+)
const text = 'cat bat sat';
for (const m of text.matchAll(/[a-z]at/g)) {
console.log(m[0], m.index);
}
// Lookbehind (ES2018+)
'$100 200 $300'.match(/(?<=\$)\d+/g); // ['100', '300']
Python
import re
# 기본 메서드
re.match(r'^hello', 'hello world') # 시작에서만 매칭
re.search(r'hello', 'say hello') # 어디서든 매칭
re.findall(r'\d+', 'a1 b2 c3') # 모든 매칭 리스트
re.sub(r'\d+', 'N', 'a1 b2') # 치환: 'aN bN'
# 컴파일 (반복 사용 시 성능 향상)
pattern = re.compile(r'\d{3}-\d{4}')
pattern.findall('010-1234-5678')
# 플래그
re.IGNORECASE # 대소문자 무시
re.MULTILINE # ^ $를 각 줄에 적용
re.DOTALL # .이 개행도 매칭
re.VERBOSE # 주석과 공백 허용
# VERBOSE 예시
phone_re = re.compile(r'''
(\d{3}) # 지역번호
[-.\s]? # 구분자
(\d{3,4}) # 중간 번호
[-.\s]? # 구분자
(\d{4}) # 마지막 번호
''', re.VERBOSE)
Java
import java.util.regex.*;
// 기본 사용
Pattern pattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
Matcher matcher = pattern.matcher("Date: 2025-03-23");
if (matcher.find()) {
System.out.println(matcher.group()); // "2025-03-23"
}
// Named Groups
Pattern datePattern = Pattern.compile(
"(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})"
);
Matcher m = datePattern.matcher("2025-03-23");
if (m.find()) {
System.out.println(m.group("year")); // "2025"
}
// String 메서드
"hello world".matches("hello.*"); // true
"a1b2c3".replaceAll("\\d", "N"); // "aNbNcN"
"a,b,,c".split(",", -1); // ["a", "b", "", "c"]
Go
package main
import (
"fmt"
"regexp"
)
func main() {
// 컴파일
re := regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)
// 매칭 확인
fmt.Println(re.MatchString("2025-03-23")) // true
// 찾기
fmt.Println(re.FindString("Date: 2025-03-23")) // "2025-03-23"
// 모두 찾기
fmt.Println(re.FindAllString("2025-03-23 and 2025-12-31", -1))
// Named Groups
re2 := regexp.MustCompile(
`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`,
)
match := re2.FindStringSubmatch("2025-03-23")
for i, name := range re2.SubexpNames() {
if name != "" {
fmt.Printf("%s: %s\n", name, match[i])
}
}
}
언어별 기능 비교
| 기능 | JavaScript | Python | Java | Go |
|---|---|---|---|---|
| Lookahead | O (ES2018+) | O | O | X |
| Lookbehind | O (ES2018+) | O | O | X |
| Named Groups | O (ES2018+) | O | O | O |
| Possessive | X | X (regex 모듈) | O | X |
| Atomic Groups | X | X (regex 모듈) | O | O |
| Unicode 카테고리 | O | O | O | O |
| 재귀 패턴 | X | X (regex 모듈) | X | X |
| VERBOSE 모드 | X | O | O (COMMENTS) | X |
10. 성능 최적화와 ReDoS 방지
백트래킹의 이해
정규표현식 엔진(NFA)은 매칭 실패 시 백트래킹으로 다른 경로를 시도합니다.
# 정상적인 백트래킹
# 패턴: a*b
# 입력: "aaac"
# 시도: "aaa" + b? 실패 → "aa" + b? 실패 → "a" + b? 실패 → "" + b? 실패
# 총 4번 백트래킹 — 문제 없음
재앙적 백트래킹 (Catastrophic Backtracking)
import time
# 위험한 패턴!
dangerous_pattern = r'(a+)+b'
safe_pattern = r'a+b'
# 짧은 입력 — 둘 다 빠름
text_short = 'a' * 10 + 'c'
# 긴 입력 — 위험한 패턴은 지수적 시간 소요
text_long = 'a' * 25 + 'c'
start = time.time()
re.match(safe_pattern, text_long)
print(f"안전한 패턴: {time.time() - start:.4f}s")
# 주의: 아래 코드는 매우 오래 걸릴 수 있음!
# start = time.time()
# re.match(dangerous_pattern, text_long)
# print(f"위험한 패턴: {time.time() - start:.4f}s")
ReDoS 취약 패턴 목록
# 1. 중첩된 수량자
r'(a+)+' # 위험!
r'(a*)*' # 위험!
r'(a+)*' # 위험!
# 2. 겹치는 대안
r'(a|a)+' # 위험!
r'(\d+|\d+\.)+' # 위험!
# 3. 안전한 대체 패턴
r'a+' # (a+)+ 대신
r'a*' # (a*)* 대신
r'\d+\.?' # (\d+|\d+\.)+ 대신
ReDoS 방지 가이드라인
- 중첩 수량자 금지 —
(a+)+,(a*)*패턴을 피합니다 - 대안의 겹침 제거 — OR 대안이 같은 문자를 매칭하지 않도록 합니다
- 원자 그룹 사용 — 지원하는 엔진에서
(?>...)사용 - 입력 길이 제한 — 정규표현식 적용 전에 입력 길이를 검증합니다
- 타임아웃 설정 — 정규표현식 실행에 시간 제한을 둡니다
# Python에서 타임아웃 설정
import signal
def timeout_handler(signum, frame):
raise TimeoutError("Regex timeout!")
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(1) # 1초 타임아웃
try:
re.match(r'(a+)+b', 'a' * 30 + 'c')
except TimeoutError:
print("정규표현식 실행이 시간 초과되었습니다.")
finally:
signal.alarm(0)
11. 도구 활용
regex101.com
온라인 정규표현식 테스터로 가장 인기 있는 도구입니다.
주요 기능:
- 실시간 매칭 하이라이팅
- 패턴 설명 자동 생성
- JavaScript, Python, Go, Java 등 다양한 엔진 지원
- 매칭 과정 단계별 디버거
- 패턴 저장 및 공유
grep과 ripgrep
# grep 기본 사용
grep -E 'error|warning' /var/log/syslog
grep -P '(?<=user:)\w+' access.log # Perl 호환 정규식
# ripgrep (rg) — 더 빠른 대안
rg 'TODO|FIXME' --type py
rg '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' access.log
rg -e 'pattern1' -e 'pattern2' . # 여러 패턴
sed와 awk
# sed — 스트림 편집기
# 날짜 형식 변환: 2025/03/23 → 2025-03-23
echo "2025/03/23" | sed 's/\//-/g'
# 이메일 주소 마스킹
echo "user@example.com" | sed 's/\(.\).*@/\1***@/'
# u***@example.com
# awk — 패턴 매칭 + 처리
# 200 상태코드인 로그만 출력
awk '/HTTP\/[0-9.]+" 200/' access.log
# IP별 요청 수 집계
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head
IDE 활용
대부분의 IDE(VS Code, IntelliJ, Vim 등)에서 정규표현식 검색/치환을 지원합니다.
VS Code 팁:
Ctrl+H(또는Cmd+H)로 치환 모드- 정규표현식 아이콘(
.*) 클릭하여 활성화 - 캡처 그룹은
$1,$2로 역참조
12. 면접 질문 10선
Q1. .*과 .*?의 차이는?
.*은 탐욕적(greedy) — 가능한 한 많은 문자를 매칭합니다. .*?은 게으른(lazy) — 가능한 한 적은 문자를 매칭합니다.
Q2. ^와 $는 멀티라인 모드에서 어떻게 달라지나요?
기본 모드에서 ^는 문자열의 시작, $는 문자열의 끝만 매칭합니다. 멀티라인 모드(re.MULTILINE)에서는 각 줄의 시작과 끝을 매칭합니다.
Q3. 전방탐색(Lookahead)과 후방탐색(Lookbehind)의 차이는?
전방탐색 (?=...) 은 현재 위치 뒤의 패턴을 확인하고, 후방탐색 (?<=...) 은 현재 위치 앞의 패턴을 확인합니다. 둘 다 제로폭 단언으로 문자를 소비하지 않습니다.
Q4. ReDoS란 무엇이고 어떻게 방지하나요?
ReDoS는 비효율적 정규표현식이 지수적 백트래킹을 일으켜 CPU를 과도하게 사용하는 공격입니다. 중첩 수량자를 피하고, 입력 길이를 제한하며, 타임아웃을 설정하여 방지합니다.
Q5. 캡처 그룹과 비캡처 그룹의 차이는?
캡처 그룹 (...) 은 매칭된 문자열을 저장하여 후에 참조할 수 있습니다. 비캡처 그룹 (?:...) 은 그룹화만 하고 저장하지 않아 성능이 약간 더 좋습니다.
Q6. \b의 역할은?
단어 경계(word boundary)를 매칭합니다. 단어 문자(\w)와 비단어 문자 사이의 위치, 또는 문자열의 시작/끝과 단어 문자 사이를 매칭합니다.
Q7. 이메일 검증에 정규표현식을 사용할 때의 한계는?
RFC 5322 표준을 완벽히 구현하려면 매우 복잡한 패턴이 필요합니다. 실무에서는 기본적인 형식 검증만 정규표현식으로 하고, 실제 존재 여부는 인증 이메일 전송으로 확인합니다.
Q8. Python의 re.match와 re.search의 차이는?
re.match는 문자열의 시작에서만 매칭을 시도하고, re.search는 문자열 전체에서 첫 번째 매칭을 찾습니다.
Q9. 정규표현식에서 백슬래시를 매칭하려면?
패턴에서 \\\\를 사용합니다. 문자열 이스케이프와 정규표현식 이스케이프가 각각 필요합니다. Python에서는 raw string(r'\\')을 사용하면 \\만으로 충분합니다.
Q10. 정규표현식으로 HTML을 파싱하면 안 되는 이유는?
HTML은 정규 언어가 아닌 문맥 자유 언어입니다. 중첩된 태그, 주석, CDATA 섹션 등을 정규표현식으로 올바르게 처리할 수 없습니다. HTML 파서(BeautifulSoup, Cheerio 등)를 사용해야 합니다.
13. 퀴즈
Q1. 패턴 \d{3}-\d{4}는 "010-1234-5678"에서 무엇을 매칭하나요?
"010-1234"와 "234-5678" 중 첫 번째인 "010-1234"를 매칭합니다. re.findall을 사용하면 두 개 모두 찾을 수 있습니다. 전체 전화번호를 매칭하려면 \d{3}-\d{4}-\d{4} 패턴을 사용해야 합니다.
Q2. 왜 (a+)+b는 위험한 패턴인가요?
입력이 "aaaaac"처럼 b로 끝나지 않으면, 엔진이 내부 그룹과 외부 그룹의 모든 조합을 시도하며 지수적 백트래킹이 발생합니다. a가 25개만 되어도 수천만 번의 시도가 필요합니다. 단순히 a+b로 대체하면 해결됩니다.
Q3. (?:...)와 (?=...)의 차이는?
(?:...)는 비캡처 그룹으로, 문자를 소비하면서 그룹화합니다 (캡처만 하지 않음). (?=...)는 전방탐색으로, 문자를 소비하지 않고 조건만 확인하는 제로폭 단언입니다.
Q4. Go 언어에서 Lookahead를 사용할 수 없는 이유는?
Go의 regexp 패키지는 RE2 엔진을 사용하는데, RE2는 선형 시간 보장을 위해 백트래킹을 사용하지 않습니다. Lookahead는 백트래킹이 필요한 기능이므로 RE2에서 지원하지 않습니다.
Q5. 다음 중 "abc"에 매칭되는 패턴은? a) [abc]+ b) [^abc]+ c) a.c d) a\bc
a) [abc]+ — "abc" 전체에 매칭됩니다. b) [^abc]+ — a, b, c가 아닌 문자만 매칭하므로 실패. c) a.c — .이 b를 매칭하므로 "abc"에 매칭. d) a\bc — \b는 단어 경계인데, a와 b 사이에는 단어 경계가 없으므로 실패. 정답은 a와 c입니다.
14. 참고 자료
- Mastering Regular Expressions (Jeffrey Friedl) — 정규표현식의 바이블
- Regular-Expressions.info — https://www.regular-expressions.info/
- regex101 — https://regex101.com/ — 온라인 정규표현식 테스터
- Regexr — https://regexr.com/ — 시각적 정규표현식 학습
- MDN Web Docs: Regular Expressions — JavaScript 정규표현식 레퍼런스
- Python re module docs — https://docs.python.org/3/library/re.html
- RE2 Syntax — https://github.com/google/re2/wiki/Syntax
- ReDoS Prevention — OWASP 가이드
- ripgrep (rg) — https://github.com/BurntSushi/ripgrep
- Debuggex — https://www.debuggex.com/ — 정규표현식 시각화
- RegExp Playground — https://regexper.com/ — 철도 다이어그램
- Automata Theory — 정규 언어와 유한 오토마타 이론
Regular Expressions Complete Guide 2025: From Basics to Advanced Patterns and Real-World Applications
1. Why Regex Still Matters
In 2025, regular expressions remain a core developer tool.
Input validation — The most concise way to validate email, URL, phone number, and credit card formats.
Log analysis — Extract specific patterns (error codes, IP addresses, timestamps) from multi-GB log files.
Text transformation — Used for code refactoring, data cleansing, and file format conversion.
AI prompt preprocessing — Remove unnecessary special characters and HTML tags before feeding input to LLMs.
Universal support — Built into virtually every programming language: JavaScript, Python, Java, Go, Rust, and more.
2. Basics: Literals and Metacharacters
Literal Matching
The most basic form of regex is literal matching — characters match themselves.
import re
# Literal matching
print(re.findall(r'hello', 'hello world hello'))
# ['hello', 'hello']
Core Metacharacters
| Metachar | Meaning | Example | Matches |
|---|---|---|---|
. | Any single char (except newline) | h.t | hat, hit, hot |
^ | Start of string | ^Hello | Hello in "Hello world" |
$ | End of string | world$ | world in "Hello world" |
* | 0 or more repetitions | ab*c | ac, abc, abbc |
+ | 1 or more repetitions | ab+c | abc, abbc (not ac) |
? | 0 or 1 occurrence | colou?r | color, colour |
| | OR (alternation) | cat|dog | cat or dog |
[] | Character class | [aeiou] | one vowel |
() | Grouping / Capture | (ab)+ | ab, abab |
\ | Escape | \. | literal period |
Characters That Need Escaping
These metacharacters must be preceded by \ when used as literals:
. * + ? ^ $ | [ ] ( ) { } \
# Match a literal period
print(re.findall(r'www\.example\.com', 'visit www.example.com'))
# ['www.example.com']
3. Character Classes
Basic Character Classes
# [abc] — one of a, b, or c
re.findall(r'[aeiou]', 'hello world')
# ['e', 'o', 'o']
# [^abc] — any character NOT a, b, or c
re.findall(r'[^aeiou ]', 'hello world')
# ['h', 'l', 'l', 'w', 'r', 'l', 'd']
# [a-z] — range
re.findall(r'[A-Z]', 'Hello World')
# ['H', 'W']
# [0-9a-fA-F] — hex characters
re.findall(r'[0-9a-fA-F]+', 'color: #FF00AB')
# ['FF00AB']
Shorthand Character Classes
| Shorthand | Equivalent | Meaning |
|---|---|---|
\d | [0-9] | Digit |
\D | [^0-9] | Non-digit |
\w | [a-zA-Z0-9_] | Word character |
\W | [^a-zA-Z0-9_] | Non-word character |
\s | [ \t\n\r\f\v] | Whitespace |
\S | [^ \t\n\r\f\v] | Non-whitespace |
# Extract phone numbers
text = "Call 555-1234 or 800-555-6789"
print(re.findall(r'\d{3}-\d{3,4}(?:-\d{4})?', text))
# ['555-1234', '800-555-6789']
POSIX Character Classes (Selected Languages)
[:alpha:] — Alphabetic characters
[:digit:] — Digits
[:alnum:] — Alphanumeric
[:space:] — Whitespace
[:upper:] — Uppercase letters
[:lower:] — Lowercase letters
[:punct:] — Punctuation
POSIX classes are mainly used in UNIX tools like grep and sed. In Python and JavaScript, use \d, \w, etc.
4. Quantifiers
Basic Quantifiers
# * (0 or more)
re.findall(r'go*d', 'gd god good goood')
# ['gd', 'god', 'good', 'goood']
# + (1 or more)
re.findall(r'go+d', 'gd god good goood')
# ['god', 'good', 'goood']
# ? (0 or 1)
re.findall(r'colou?r', 'color colour')
# ['color', 'colour']
Exact Count Specifiers
# {n} — exactly n times
re.findall(r'\d{4}', '2025 is the year 12345')
# ['2025', '1234']
# {n,m} — between n and m times
re.findall(r'\d{2,4}', '1 12 123 1234 12345')
# ['12', '123', '1234', '1234']
# {n,} — n or more times
re.findall(r'\d{3,}', '1 12 123 1234')
# ['123', '1234']
Greedy vs Lazy Matching
By default, quantifiers are greedy — they match as much as possible. Appending ? makes them lazy.
text = '<b>bold</b> and <i>italic</i>'
# Greedy (default)
print(re.findall(r'<.*>', text))
# ['<b>bold</b> and <i>italic</i>'] — matches everything!
# Lazy (add ?)
print(re.findall(r'<.*?>', text))
# ['<b>', '</b>', '<i>', '</i>'] — matches each tag
Possessive Quantifiers
Possessive quantifiers (*+, ++, ?+) never backtrack. Supported in Java and PCRE (not standard Python re; available in the regex module).
// Java example
// Possessive quantifier — no backtracking
String pattern = "a++b"; // Consumes all 'a's, never gives back
5. Groups
Capturing Groups
# Basic capturing group
text = "2025-03-23"
match = re.match(r'(\d{4})-(\d{2})-(\d{2})', text)
if match:
print(match.group(0)) # '2025-03-23' (full match)
print(match.group(1)) # '2025' (year)
print(match.group(2)) # '03' (month)
print(match.group(3)) # '23' (day)
Non-Capturing Groups
(?:...) — Groups without capturing. Provides a performance benefit.
# Non-capturing group
text = "http://example.com https://secure.com"
print(re.findall(r'(?:https?://)(\S+)', text))
# ['example.com', 'secure.com'] — captures only the domain
Named Groups
# Named groups
text = "2025-03-23"
match = re.match(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', text)
if match:
print(match.group('year')) # '2025'
print(match.group('month')) # '03'
print(match.group('day')) # '23'
Backreferences
# Find duplicate words
text = "the the quick brown fox fox"
print(re.findall(r'\b(\w+)\s+\1\b', text))
# ['the', 'fox']
# Match HTML tags (same tag name)
html = "<div>content</div> <span>text</span>"
print(re.findall(r'<(\w+)>.*?</\1>', html))
# ['div', 'span']
6. Anchors and Boundaries
Basic Anchors
# ^ — start of string
print(re.findall(r'^Hello', 'Hello World\nHello Again'))
# ['Hello'] — only the first
# $ — end of string
print(re.findall(r'end$', 'start to end'))
# ['end']
# Multiline mode
print(re.findall(r'^Hello', 'Hello World\nHello Again', re.MULTILINE))
# ['Hello', 'Hello'] — start of each line
Word Boundary (\b)
\b matches the position between a word character and a non-word character. It is zero-width and consumes no characters.
text = "cat catfish concatenate scattered"
# Without \b
print(re.findall(r'cat', text))
# ['cat', 'cat', 'cat', 'cat']
# With \b for exact word
print(re.findall(r'\bcat\b', text))
# ['cat']
# Word start only
print(re.findall(r'\bcat\w*', text))
# ['cat', 'catfish', 'concatenate']
7. Lookahead and Lookbehind
Lookahead and lookbehind are zero-width assertions. They check conditions without consuming characters.
Four Types
| Type | Syntax | Meaning |
|---|---|---|
| Positive Lookahead | (?=...) | Position followed by ... |
| Negative Lookahead | (?!...) | Position NOT followed by ... |
| Positive Lookbehind | (?<=...) | Position preceded by ... |
| Negative Lookbehind | (?<!...) | Position NOT preceded by ... |
Practical Example: Password Validation
def validate_password(password):
"""
Password rules:
- At least 8 characters
- At least 1 uppercase letter
- At least 1 lowercase letter
- At least 1 digit
- At least 1 special character
"""
pattern = r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$'
return bool(re.match(pattern, password))
print(validate_password("Abc123!@")) # True
print(validate_password("abc123")) # False (no uppercase, no special)
print(validate_password("Short1!")) # False (under 8 chars)
Number Formatting (Lookbehind)
# Add commas to numbers (thousands separator)
def format_number(n):
return re.sub(r'(?<=\d)(?=(\d{3})+(?!\d))', ',', str(n))
print(format_number(1234567890))
# '1,234,567,890'
Negative Lookahead Usage
# Find .js files that are NOT .min.js
files = ["app.js", "app.min.js", "utils.js", "vendor.min.js"]
pattern = r'^(?!.*\.min\.js$).*\.js$'
for f in files:
if re.match(pattern, f):
print(f)
# app.js
# utils.js
8. 30 Practical Patterns
Email Patterns
# 1. Basic email validation
email_basic = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
# 2. Extract domain from email
email_domain = r'@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})'
# 3. Match Gmail addresses only
gmail_only = r'^[a-zA-Z0-9._%+-]+@gmail\.com$'
URL Patterns
# 4. Basic URL matching
url_basic = r'https?://[a-zA-Z0-9.-]+(?:/[^\s]*)?'
# 5. Extract domain from URL
url_domain = r'https?://([^/\s]+)'
# 6. Extract query parameters
query_param = r'[?&]([^=&]+)=([^&]*)'
IP Addresses
# 7. IPv4 address
ipv4 = r'\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b'
# 8. Private IP addresses
private_ip = r'\b(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})\b'
Phone Numbers
# 9. US phone number
us_phone = r'(?:\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}'
# 10. International E.164 format
intl_phone = r'\+\d{1,3}\d{4,14}'
# 11. UK phone number
uk_phone = r'(?:\+44|0)\d{4}[\s-]?\d{6}'
Date and Time
# 12. YYYY-MM-DD
date_iso = r'\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])'
# 13. Multiple date formats
date_multi = r'\d{4}[/.-]\d{1,2}[/.-]\d{1,2}'
# 14. 24-hour time
time_24h = r'(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?'
# 15. ISO 8601 datetime
iso_datetime = r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})'
Development Patterns
# 16. HTML tags
html_tag = r'<\/?[a-zA-Z][a-zA-Z0-9]*(?:\s[^>]*)?\/?>'
# 17. HEX color codes
hex_color = r'#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b'
# 18. Semantic versioning (SemVer)
semver = r'\bv?(\d+)\.(\d+)\.(\d+)(?:-([\w.]+))?(?:\+([\w.]+))?\b'
# 19. JSON keys
json_key = r'"([^"\\]*)"\s*:'
# 20. CSS class selectors
css_class = r'\.[a-zA-Z_][\w-]*'
CJK Characters
# 21. Korean characters (Hangul)
hangul = r'[\uAC00-\uD7AF\u1100-\u11FF\u3130-\u318F]+'
# 22. Japanese Hiragana
hiragana = r'[\u3040-\u309F]+'
# 23. Japanese Katakana
katakana = r'[\u30A0-\u30FF]+'
# 24. CJK Unified Ideographs (Kanji/Hanzi)
cjk = r'[\u4E00-\u9FFF]+'
# 25. Any CJK character
cjk_all = r'[\u3000-\u9FFF\uAC00-\uD7AF]+'
Security and Validation
# 26. Strong password
strong_password = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$'
# 27. SQL injection suspicious patterns
sql_injection = r"(?i)(?:union\s+select|or\s+1\s*=\s*1|drop\s+table|insert\s+into)"
# 28. XSS suspicious patterns
xss_pattern = r'(?i)<script[^>]*>.*?</script>'
Other Useful Patterns
# 29. Normalize whitespace (collapse multiple spaces)
text = "too many spaces"
print(re.sub(r'\s+', ' ', text))
# 'too many spaces'
# 30. CSV field extraction (comma-separated, quoted support)
csv_field = r'(?:"([^"]*)"|([^,]*))'
9. Language-Specific Differences
JavaScript
// Literal syntax
const re1 = /hello/gi;
// Constructor syntax
const re2 = new RegExp('hello', 'gi');
// Key methods
'hello world'.match(/hello/); // ['hello']
'hello world'.replace(/hello/, 'hi'); // 'hi world'
/hello/.test('hello world'); // true
// Named Groups (ES2018+)
const match = '2025-03-23'.match(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
);
console.log(match.groups.year); // '2025'
// matchAll (ES2020+)
const text = 'cat bat sat';
for (const m of text.matchAll(/[a-z]at/g)) {
console.log(m[0], m.index);
}
// Lookbehind (ES2018+)
'$100 200 $300'.match(/(?<=\$)\d+/g); // ['100', '300']
Python
import re
# Core methods
re.match(r'^hello', 'hello world') # Match at start only
re.search(r'hello', 'say hello') # Match anywhere
re.findall(r'\d+', 'a1 b2 c3') # All matches as list
re.sub(r'\d+', 'N', 'a1 b2') # Substitution: 'aN bN'
# Compilation (performance boost for repeated use)
pattern = re.compile(r'\d{3}-\d{4}')
pattern.findall('555-1234-5678')
# Flags
re.IGNORECASE # Case-insensitive
re.MULTILINE # ^ and $ match line boundaries
re.DOTALL # . matches newlines too
re.VERBOSE # Allow comments and whitespace
# VERBOSE example
phone_re = re.compile(r'''
(\d{3}) # area code
[-.\s]? # separator
(\d{3,4}) # middle digits
[-.\s]? # separator
(\d{4}) # last digits
''', re.VERBOSE)
Java
import java.util.regex.*;
// Basic usage
Pattern pattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
Matcher matcher = pattern.matcher("Date: 2025-03-23");
if (matcher.find()) {
System.out.println(matcher.group()); // "2025-03-23"
}
// Named Groups
Pattern datePattern = Pattern.compile(
"(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})"
);
Matcher m = datePattern.matcher("2025-03-23");
if (m.find()) {
System.out.println(m.group("year")); // "2025"
}
// String methods
"hello world".matches("hello.*"); // true
"a1b2c3".replaceAll("\\d", "N"); // "aNbNcN"
"a,b,,c".split(",", -1); // ["a", "b", "", "c"]
Go
package main
import (
"fmt"
"regexp"
)
func main() {
// Compile
re := regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)
// Match check
fmt.Println(re.MatchString("2025-03-23")) // true
// Find
fmt.Println(re.FindString("Date: 2025-03-23")) // "2025-03-23"
// Find all
fmt.Println(re.FindAllString("2025-03-23 and 2025-12-31", -1))
// Named Groups
re2 := regexp.MustCompile(
`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`,
)
match := re2.FindStringSubmatch("2025-03-23")
for i, name := range re2.SubexpNames() {
if name != "" {
fmt.Printf("%s: %s\n", name, match[i])
}
}
}
Feature Comparison
| Feature | JavaScript | Python | Java | Go |
|---|---|---|---|---|
| Lookahead | Yes (ES2018+) | Yes | Yes | No |
| Lookbehind | Yes (ES2018+) | Yes | Yes | No |
| Named Groups | Yes (ES2018+) | Yes | Yes | Yes |
| Possessive | No | No (regex module) | Yes | No |
| Atomic Groups | No | No (regex module) | Yes | Yes |
| Unicode Categories | Yes | Yes | Yes | Yes |
| Recursive Patterns | No | No (regex module) | No | No |
| VERBOSE Mode | No | Yes | Yes (COMMENTS) | No |
10. Performance Optimization and ReDoS Prevention
Understanding Backtracking
NFA regex engines use backtracking to try alternative paths when a match fails.
# Normal backtracking
# Pattern: a*b
# Input: "aaac"
# Tries: "aaa" + b? fail -> "aa" + b? fail -> "a" + b? fail -> "" + b? fail
# 4 backtracks total — no problem
Catastrophic Backtracking
import time
# Dangerous pattern!
dangerous_pattern = r'(a+)+b'
safe_pattern = r'a+b'
# Short input — both fast
text_short = 'a' * 10 + 'c'
# Long input — dangerous pattern takes exponential time
text_long = 'a' * 25 + 'c'
start = time.time()
re.match(safe_pattern, text_long)
print(f"Safe pattern: {time.time() - start:.4f}s")
# WARNING: The following may take a very long time!
# start = time.time()
# re.match(dangerous_pattern, text_long)
# print(f"Dangerous pattern: {time.time() - start:.4f}s")
Vulnerable ReDoS Patterns
# 1. Nested quantifiers
r'(a+)+' # Dangerous!
r'(a*)*' # Dangerous!
r'(a+)*' # Dangerous!
# 2. Overlapping alternatives
r'(a|a)+' # Dangerous!
r'(\d+|\d+\.)+' # Dangerous!
# 3. Safe replacements
r'a+' # Instead of (a+)+
r'a*' # Instead of (a*)*
r'\d+\.?' # Instead of (\d+|\d+\.)+
ReDoS Prevention Guidelines
- No nested quantifiers — Avoid
(a+)+,(a*)*patterns - Eliminate overlapping alternatives — Ensure OR alternatives do not match the same characters
- Use atomic groups — Use
(?>...)in engines that support it - Limit input length — Validate input length before applying regex
- Set timeouts — Put time limits on regex execution
# Timeout in Python
import signal
def timeout_handler(signum, frame):
raise TimeoutError("Regex timeout!")
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(1) # 1 second timeout
try:
re.match(r'(a+)+b', 'a' * 30 + 'c')
except TimeoutError:
print("Regex execution timed out.")
finally:
signal.alarm(0)
11. Tools
regex101.com
The most popular online regex tester.
Key features:
- Real-time match highlighting
- Auto-generated pattern explanation
- Supports JavaScript, Python, Go, Java, and more
- Step-by-step match debugger
- Save and share patterns
grep and ripgrep
# grep basics
grep -E 'error|warning' /var/log/syslog
grep -P '(?<=user:)\w+' access.log # Perl-compatible regex
# ripgrep (rg) — faster alternative
rg 'TODO|FIXME' --type py
rg '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' access.log
rg -e 'pattern1' -e 'pattern2' . # Multiple patterns
sed and awk
# sed — stream editor
# Convert date format: 2025/03/23 -> 2025-03-23
echo "2025/03/23" | sed 's/\//-/g'
# Mask email address
echo "user@example.com" | sed 's/\(.\).*@/\1***@/'
# u***@example.com
# awk — pattern matching + processing
# Print only 200 status code logs
awk '/HTTP\/[0-9.]+" 200/' access.log
# Count requests per IP
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head
IDE Usage
Most IDEs (VS Code, IntelliJ, Vim, etc.) support regex search and replace.
VS Code tips:
Ctrl+H(orCmd+H) for replace mode- Click the regex icon (
.*) to enable - Use
$1,$2for backreferences in replacement
12. Interview Questions
Q1. What is the difference between .* and .*??
.* is greedy — it matches as many characters as possible. .*? is lazy — it matches as few characters as possible.
Q2. How do ^ and $ behave differently in multiline mode?
In default mode, ^ matches the start of the string and $ matches the end. In multiline mode (re.MULTILINE), they match the start and end of each line.
Q3. What is the difference between lookahead and lookbehind?
Lookahead (?=...) checks the pattern after the current position, while lookbehind (?<=...) checks the pattern before it. Both are zero-width assertions that do not consume characters.
Q4. What is ReDoS and how do you prevent it?
ReDoS is when an inefficient regex causes exponential backtracking, consuming excessive CPU. Prevent it by avoiding nested quantifiers, limiting input length, and setting execution timeouts.
Q5. What is the difference between capturing and non-capturing groups?
Capturing groups (...) store the matched string for later reference. Non-capturing groups (?:...) group without storing, providing a slight performance benefit.
Q6. What does \b do?
It matches a word boundary — the position between a word character (\w) and a non-word character, or between a word character and the start/end of the string.
Q7. What are the limitations of using regex for email validation?
Fully implementing the RFC 5322 standard requires an extremely complex pattern. In practice, use regex for basic format validation only and verify actual existence through confirmation emails.
Q8. What is the difference between re.match and re.search in Python?
re.match only attempts matching at the start of the string, while re.search scans the entire string for the first match.
Q9. How do you match a literal backslash in regex?
Use \\\\ in the pattern (double escaping for both string and regex). In Python, raw strings (r'\\') require only \\.
Q10. Why should you not parse HTML with regex?
HTML is a context-free language, not a regular language. Nested tags, comments, CDATA sections, and other features cannot be correctly handled by regex. Use an HTML parser (BeautifulSoup, Cheerio, etc.) instead.
13. Quiz
Q1. What does the pattern \d{3}-\d{4} match in "010-1234-5678"?
It matches "010-1234" (the first occurrence). Using re.findall would find both "010-1234" and "234-5678". To match the full phone number, use \d{3}-\d{4}-\d{4}.
Q2. Why is (a+)+b a dangerous pattern?
When the input does not end with 'b' (e.g., "aaaaac"), the engine tries every combination of inner and outer group repetitions, causing exponential backtracking. With just 25 'a' characters, millions of attempts are needed. Simply replacing it with a+b solves the problem.
Q3. What is the difference between (?:...) and (?=...)?
(?:...) is a non-capturing group that consumes characters while grouping (it just does not capture). (?=...) is a lookahead that does not consume characters at all — it is a zero-width assertion that only checks a condition.
Q4. Why can Go not use lookahead?
Go's regexp package uses the RE2 engine, which guarantees linear time execution by not using backtracking. Lookahead requires backtracking, so RE2 does not support it.
Q5. Which of the following patterns match "abc"? a) [abc]+ b) [^abc]+ c) a.c d) a\bc
a) [abc]+ — matches all of "abc". b) [^abc]+ — matches characters NOT in a, b, c, so it fails. c) a.c — the . matches b, so it matches "abc". d) a\bc — \b is a word boundary, but there is no word boundary between 'a' and 'b', so it fails. The answers are a and c.
14. References
- Mastering Regular Expressions (Jeffrey Friedl) — The definitive regex book
- Regular-Expressions.info — https://www.regular-expressions.info/
- regex101 — https://regex101.com/ — Online regex tester
- Regexr — https://regexr.com/ — Visual regex learning
- MDN Web Docs: Regular Expressions — JavaScript regex reference
- Python re module docs — https://docs.python.org/3/library/re.html
- RE2 Syntax — https://github.com/google/re2/wiki/Syntax
- ReDoS Prevention — OWASP Guide
- ripgrep (rg) — https://github.com/BurntSushi/ripgrep
- Debuggex — https://www.debuggex.com/ — Regex visualization
- RegExp Playground — https://regexper.com/ — Railroad diagrams
- Automata Theory — Regular languages and finite automata