본문으로 건너뛰기
yutils

정규표현식 30분 입문 — 기초부터 lookaround·ReDoS 까지

문자 클래스·수량자·그룹·lookaround 와 절대 쓰면 안 되는 catastrophic backtracking 패턴까지 실전 예제로.

약 10분 읽기

정규표현식 (regex) 은 처음에는 외계어처럼 보이지만, 30 분만 투자하면 텍스 트 검증·추출·치환의 80% 를 처리할 수 있다. 이 가이드는 기초 문법부터 그룹·lookaround 같은 중급 기법, 그리고 무엇보다 절대 쓰면 안 되는 패턴 (catastrophic backtracking) 까지 다룬다. 모든 예제 는 정규식 테스터 에 직접 붙여 동작 확인하면서 읽기를 권장한다.

리터럴 vs 메타문자

대부분의 글자는 자기 자신을 의미한다. cat 패턴은 문자열 "cat" 과 매치. 하지만 다음 12 글자는 특별한 의미가 있다.

. ^ $ * + ? ( ) [ ] { } | \

이 문자들을 글자 그대로 매치하려면 \ 로 escape — 예: 점 . 매치는 \..

문자 클래스

기본

패턴의미
.줄바꿈 외 한 글자
\d숫자 (0-9)
\w영숫자 + 언더스코어 ([A-Za-z0-9_])
\s공백 (스페이스, 탭, 줄바꿈)
\D \W \S위 세 가지의 부정 (negation)

대괄호 — 직접 정의

[aeiou]        # 모음 하나
[a-z]          # 소문자 한 글자
[A-Za-z0-9]    # 영숫자
[^abc]         # a, b, c 제외 한 글자 (^ 가 부정)

대괄호 안에서는 대부분의 메타문자가 리터럴이 된다 (단, ] \ ^ - 는 예외).

수량자 (quantifier)

패턴의미
?0 또는 1 번
*0 번 이상
+1 번 이상
{n}정확히 n 번
{n,}n 번 이상
{n,m}n 번 이상 m 번 이하

예: \d{3,4} 는 숫자 3~4 자리. https?:// 는 "http://" 또는 "https://".

greedy vs lazy

수량자는 기본이 greedy — 가능한 한 길게 매치한다. 뒤에 ? 를 붙이면 lazy — 가능한 한 짧게.

<.+>      # greedy: <b>text</b> 전체 매치
<.+?>     # lazy: <b> 만 매치

HTML 태그처럼 "다음 닫는 글자까지만" 매치하려면 lazy 가 거의 항상 정답.

앵커

  • ^ — 줄 시작
  • $ — 줄 끝
  • \b — 단어 경계 ([A-Za-z0-9_] 와 그 외 사이)

예: ^[A-Z] 는 대문자로 시작하는 줄. \bcat\b 는 단어 "cat" — "category" 안의 cat 은 매치 안 함.

그룹과 캡처

괄호로 묶으면 (1) 패턴을 그룹화하고 (2) 매치 결과를 캡처한다.

(\d{4})-(\d{2})-(\d{2})

"2026-05-16" 에 매치하면 group 1 = "2026", group 2 = "05", group 3 ="16". 대부분의 언어에서 match.groups[1] 또는 result[1] 로 접근.

이름 있는 그룹

(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})

JavaScript: match.groups.year. Python: m.group('year').

non-capturing 그룹

(?:https?|ftp)://

그룹화만 하고 캡처 안 함. 결과 배열 인덱스 깨지지 않게 유지.

대안 (alternation)

cat|dog|bird

세 단어 중 하나. 보통 그룹과 같이 쓴다 — (cat|dog|bird).

lookaround

"이 위치 앞/뒤에 X 가 있어야 함, 단 X 자체는 매치에 포함 안 함" 을 표현 한다. 매치 결과는 그대로지만 조건만 검사.

패턴의미
X(?=Y)X 뒤에 Y 가 오면 매치 (lookahead)
X(?!Y)X 뒤에 Y 가 없으면 매치 (negative lookahead)
(?<=Y)XX 앞에 Y 가 있으면 매치 (lookbehind)
(?<!Y)XX 앞에 Y 가 없으면 매치 (negative lookbehind)

예: \d+(?= 원) — "1000 원" 의 "1000" 만 매치 (단위 "원" 은 제외).

플래그

  • i — 대소문자 무시
  • g — 모든 매치 (JavaScript)
  • m — multi-line: ^/$ 가 각 줄에 매치
  • s — dotAll: . 가 줄바꿈도 포함
  • u — Unicode (JavaScript)

실전 예제

이메일 (대충)

^[^\s@]+@[^\s@]+\.[^\s@]+$

완벽한 RFC 5322 매칭은 수십 줄이지만, 위 패턴이면 실용적 검증 95% 처리. 진짜 검증은 "이메일 보내봐서 응답 받기" 가 정답.

URL

https?://[^\s/$.?#].[^\s]*

한글 추출

[가-힣]+

Unicode 플래그를 켜고 \p{Hangul} 가 더 정확.

슬러그 검증

^[a-z0-9]+(-[a-z0-9]+)*$

kebab-case slug — Slug 생성기 가 만드는 형식과 일치.

Catastrophic Backtracking — 절대 피해야 할 패턴

regex 엔진은 매치 실패 시 백트래킹을 한다. 그런데 일부 패턴은 입력 길이 에 대해 지수 시간이 걸려, 100 글자 입력이 수 분~수 시간 걸리거나 서버를 DoS 시키는 사고가 난다 — 이를 ReDoS (Regular expression Denial of Service) 라고 부른다.

위험한 패턴 1: 중첩 수량자

(a+)+$

"aaaaaaaaaaaaaaaaaaaaaaaa!" 같은 입력에 매치 실패하면 엔진이 모든 분할 조합을 시도 — 글자 수에 지수.

위험한 패턴 2: alternation + greedy

(a|a)+$

같은 글자를 두 대안으로 표현 — 매 위치에서 2 가지 선택이 누적되어 지수.

완화 방법

  • 중첩 수량자 금지(a+)+ 같은 패턴 제거. a+ 으로 충분.
  • atomic group / possessive 수량자 — 일부 엔진 (PCRE, Java) 은 (?>a+)+ 또는 a++ 로 백트래킹 금지. JavaScript 는 미지원.
  • 입력 길이 제한 — 사용자 입력 정규식 매치는 길이 상한 (예: 10 KB) 박기.
  • 매치 타임아웃 — Java Pattern.matches 가 타임아웃 옵션 미지원이라 별도 스레드 + interrupt 패턴. Go 의 RE2 는 애초에 지수 패턴이 불가능 (정규 표현식의 제한 버전).
  • 검증 — production 배포 전에 패턴을 ReDoS checker (예: regex101 의 분석 모드) 또는 yutils 의 정규식 테스터 로 시간 측정해 본다.

도구로 직접 보기

요약

  • 메타문자 12 개를 외워라 — 나머진 거의 리터럴.
  • 수량자는 기본 greedy. 닫는 문자까지만 매치하려면 lazy (? 추가).
  • 그룹은 캡처 + 그룹화 두 역할. 캡처가 필요 없으면 (?:…).
  • lookaround 는 "조건만 검사, 매치에는 미포함" — 단위 분리·치환 효율적.
  • 중첩 수량자 (a+)+ 같은 패턴은 절대 금지 — ReDoS.
가이드 목록으로