정규식 — 두려움 없이 패턴 매칭하기
이 토픽을 마치면
정규식의 기본 문법을 읽을 수 있고, 이메일·전화번호·날짜 같은 패턴을 직접 작성하며, "이 정규식이 뭘 하는 건지" 해석할 수 있습니다.
왜 무섭게 생겼을까
회원가입 폼에서 이메일 형식을 검증해야 합니다. if문으로 하면:
function isEmail(str) {
if (!str.includes("@")) return false;
const parts = str.split("@");
if (parts.length !== 2) return false;
if (parts[0].length === 0) return false;
if (!parts[1].includes(".")) return false;
// ... 더 있음
}정규식으로 하면 한 줄:
const isEmail = str => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);짧아진 대신 읽기 어려워졌습니다. 정규식이 무서운 이유는 압축된 문법 때문입니다. 하지만 기호 몇 개만 익히면 대부분의 패턴을 읽고 쓸 수 있습니다.
5분이면 되는 핵심 문법
. 아무 문자 1개
\d 숫자 (0-9)
\w 단어 문자 (a-z, A-Z, 0-9, _)
\s 공백 (스페이스, 탭, 줄바꿈)
[abc] a 또는 b 또는 c
[^abc] a, b, c가 아닌 것횟수:
* 0번 이상 (없어도 됨)
+ 1번 이상 (최소 1개)
? 0번 또는 1번 (있어도 되고 없어도 됨)
{3} 정확히 3번
{2,5} 2~5번위치:
^ 문자열의 시작
$ 문자열의 끝이게 전부입니다. 이 기호들의 조합으로 거의 모든 패턴을 표현합니다.
실전 패턴 5가지
1. 전화번호 (010-1234-5678)
const phoneRegex = /^010-\d{4}-\d{4}$/;
phoneRegex.test("010-1234-5678"); // true
phoneRegex.test("02-123-4567"); // false\d{4}는 "숫자 4개"입니다.
2. 비밀번호 (8자 이상, 숫자 포함)
const pwRegex = /^(?=.*\d).{8,}$/;
pwRegex.test("hello123"); // true
pwRegex.test("helloworld"); // false (숫자 없음)
pwRegex.test("hi1"); // false (8자 미만)(?=.*\d)는 전방탐색입니다. "어딘가에 숫자가 있는지" 확인만 하고 실제로 소비하지 않습니다.
3. HTML 태그 제거
const stripped = "<b>안녕</b>하세요".replace(/<[^>]+>/g, "");
// "안녕하세요"<[^>]+>는 "< 로 시작하고, > 가 아닌 문자가 1개 이상, > 로 끝나는 것"입니다.
4. 날짜 추출 (YYYY-MM-DD)
import retext = "회의는 2026-07-05에 예정입니다. 마감은 2026-08-01."dates = re.findall(r'\d{4}-\d{2}-\d{2}', text)# ['2026-07-05', '2026-08-01']5. 캡처 그룹 — 부분 추출
import rematch = re.search(r'(\d{4})-(\d{2})-(\d{2})', "오늘은 2026-07-05입니다")year, month, day = match.group(1), match.group(2), match.group(3)# '2026', '07', '05'소괄호 ()로 감싼 부분이 그룹입니다. group(1), group(2)로 각각 꺼낼 수 있습니다.
정규식 읽는 법
/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/이걸 해석해봅시다:
^ 문자열 시작
[A-Za-z0-9._%+-]+ 영문/숫자/특수문자 1개 이상 (로컬 파트)
@ 골뱅이
[A-Za-z0-9.-]+ 영문/숫자/점/하이픈 1개 이상 (도메인)
\. 점 (이스케이프)
[A-Za-z]{2,} 영문 2자 이상 (TLD: com, kr, ...)
$ 문자열 끝왼쪽에서 오른쪽으로, 한 토큰씩 읽으면 됩니다.
흔한 실수
// ❌ 탐욕적 매칭 — 가장 긴 것을 잡음
"<b>볼드</b>와 <i>이탤릭</i>".match(/<.+>/);
// "<b>볼드</b>와 <i>이탤릭</i>" — 전부 매칭됨
// ✅ 비탐욕적 매칭 — 가장 짧은 것을 잡음
"<b>볼드</b>와 <i>이탤릭</i>".match(/<.+?>/);
// "<b>" — 첫 태그만 매칭
// ✅ 또는 부정 문자 클래스
"<b>볼드</b>와 <i>이탤릭</i>".match(/<[^>]+>/);
// "<b>"+와 *는 기본적으로 탐욕적(greedy)입니다. 최대한 많이 잡으려 합니다. ?를 붙이면 비탐욕적(lazy)으로 바뀌어 최소한만 잡습니다.
정규식은 만능이 아니다
정규식으로 하면 안 되는 것:
- HTML/XML 파싱: 중첩 태그는 정규식으로 정확히 처리 불가. DOM 파서를 쓰세요.
- 복잡한 문법 검증: JSON, 프로그래밍 언어 구문 등은 파서가 필요합니다.
- 성능이 중요한 대량 처리: ReDoS(정규식 서비스 거부) 취약점에 주의.
정규식은 "패턴이 간단하고, 입력이 한 줄짜리"일 때 가장 강력합니다.
핵심 한 줄: 정규식은
\d(숫자),+(1개 이상),[](문자 클래스) 세 가지만 알면 실무 패턴의 80%를 커버합니다. 무섭게 생겼지만 왼쪽에서 오른쪽으로 한 토큰씩 읽으면 됩니다.