알아두면 좋은 내용
어떻게 사용하나요?
- 매칭 — 정규식·flags·테스트 문자열을 넣으면 매치를 하이라이트하고, 매치마다 위치(인덱스)·길이·캡처 그룹·이름 그룹을 카드로 보여 줍니다.
- 치환·분할 — 같은 정규식으로 치환(
$1·$&·$<name>) 또는 split 결과를 보고, JavaScript·Python·Java·PHP 코드 스니펫을 복사합니다. - 패턴 라이브러리 — 한국 데이터 10종(휴대폰·주민등록번호·사업자등록번호·우편번호 등)과 이메일·URL·UUID·HTML·마크다운 등 30여 개. 누르면 바로 적용됩니다.
- 치트시트 — 메타문자·양화 한정자·문자 클래스·그룹·룩어라운드·flags를 한 표로.
정규식·flags·치환 패턴은 이 브라우저에 자동 저장되고, 테스트 문자열은 개인정보가 들어갈 수 있어 저장하지 않습니다. 입력이 바뀌면 200ms 뒤 다시 계산합니다.
정규식 기본 — 메타문자·양화 한정자
정규식은 무엇을(메타문자·문자 클래스) 몇 번(양화 한정자) 찾을지의 조합입니다. 한국어 텍스트를 다룰 때는 \w·\d가 ASCII 전용이라는 점이 가장 흔한 함정입니다.
| 구문 | 의미 | 예시 | 한국어 텍스트 주의점 |
|---|---|---|---|
. | 줄바꿈을 뺀 글자 1개 | .+ → "abc" | u flag 없으면 이모지는 2글자로 취급 |
\d | 숫자 [0-9] | \d{3} → "123" | 전각 숫자 123은 불일치 |
\w | 단어 문자 [A-Za-z0-9_] | \w+@ → "user@" | 한글은 포함되지 않음 (u flag여도 동일) |
\s | 공백·탭·줄바꿈 등 | \s+ → " " | 전각 공백(U+3000)·NBSP도 포함 |
\b | 단어 경계 | \bcat\b | \w 기준이라 한글 단어 경계로 못 씀 |
^ $ | 시작·끝 (m flag면 각 줄) | ^abc$ | — |
* + ? | 0회 이상 · 1회 이상 · 0~1회 | colou?r → "color" | — |
{n,m} | n~m회 | \d{2,4} | — |
*? +? | 가능한 적게 (lazy) | <.*?> → 태그 하나씩 | — |
캡처 그룹 — 번호 그룹 · 이름 그룹 · 비캡처 그룹
괄호로 묶으면 그 부분을 따로 꺼내거나 치환에서 다시 쓸 수 있습니다. 번호는 여는 괄호 (가 나오는 순서대로 1번부터 매겨집니다.
번호 캡처 그룹 (...)
$1, $2 … 로 참조. 패턴 안에서는 \1로 같은 글자 반복을 찾을 수 있습니다.
패턴: (\w+)@(\w+\.\w+) 매치: "hello@example.com" $1 = "hello" $2 = "example.com" 치환 "$2/$1" → "example.com/hello"
이름 캡처 그룹 (?<name>...)
$<name>으로 참조(ES2018+). 그룹을 추가해도 참조가 밀리지 않습니다.
패턴: (?<user>\w+)@(?<domain>\w+\.\w+) 매치: "hello@example.com" groups.user = "hello" groups.domain = "example.com"
비캡처 그룹 (?:...)
묶기만 하고 번호를 매기지 않아, 필요한 그룹만 $1·$2를 차지합니다.
패턴: (?:https?|ftp):\/\/(\w+) 매치: "https://example.com" $1 = "example" (호스트 첫 단어만 캡처)
치환 문자열의 $ 토큰 — 실행 결과로 보기
치환 탭은 JavaScript String.prototype.replace 규칙을 그대로 따릅니다. 아래 표는 입력 abc-123-xyz에 정규식 /(\d)(?<rest>\d+)/를 적용하고 치환 문자열을 [토큰]으로 바꿔 가며 실제로 실행한 결과입니다.
| 토큰 | 의미 | 실행 결과 |
|---|---|---|
$& | 매치 전체 | abc-[123]-xyz |
$1 | 1번 그룹 | abc-[1]-xyz |
$2 | 2번 그룹 (이름 그룹도 번호를 가짐) | abc-[23]-xyz |
$<rest> | 이름 그룹 | abc-[23]-xyz |
$` | 매치 앞부분 전체 | abc-[abc-]-xyz |
$' | 매치 뒷부분 전체 | abc-[-xyz]-xyz |
$$ | 달러 기호 자체 | abc-[$]-xyz |
같은 치환을 다른 언어로 옮기면 참조 표기가 달라집니다. 패턴 (?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2}), 치환 $<y>년 $<m>월 $<d>일, g flag로 치환 탭이 만들어 주는 코드는 다음과 같고, 2026-09-26, 2027-01-05은 2026년 09월 26일, 2027년 01월 05일이 됩니다.
| 언어 | 생성 코드 |
|---|---|
| JavaScript | text.replace(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/g, "$<y>년 $<m>월 $<d>일") |
| Python | re.sub(r'(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})', r'\g<y>년 \g<m>월 \g<d>일', text) |
| Java | text.replaceAll("(?<y>\\d{4})-(?<m>\\d{2})-(?<d>\\d{2})", "${y}년 ${m}월 ${d}일") |
| PHP | preg_replace('/(?<y>\\d{4})-(?<m>\\d{2})-(?<d>\\d{2})/', '${1}년 ${2}월 ${3}일', $text) |
Python은 이름 그룹을 (?P<name>…), 참조를 \g<name>으로 쓰고, PHP의 preg_replace는 치환 문자열에서 이름 참조를 지원하지 않아 번호(${1})로 바뀝니다. Java는 g flag 대신 replaceAll/replaceFirst로 구분합니다.
flags 6종 상세
| flag | 이름 | 설명 | 예시 |
|---|---|---|---|
g | 전역(Global) | 첫 매치에서 멈추지 않고 모든 매치를 찾음 | matchAll·전체 치환 |
i | 대소문자 무시 | A와 a를 같은 글자로 취급 | /hello/i → "HELLO" |
m | 다중행(Multiline) | ^과 $가 문자열 전체가 아니라 각 줄의 시작·끝에 매치 | /^#/gm → 마크다운 제목 줄 |
s | dotAll | .이 줄바꿈까지 매치 | /<p>.*<\/p>/s → 여러 줄 단락 |
u | Unicode | 코드 포인트 단위 처리 + \p{…} 유니코드 속성 | /\p{Script=Hangul}+/u |
y | Sticky | lastIndex 위치에서만 매치 시도 | 토크나이저·파서 |
최신 JavaScript에는 매치 위치를 그룹별로 돌려주는 d(hasIndices, ES2022)와 문자 클래스의 차집합·교집합을 지원하는 v(unicodeSets, ES2024)도 있습니다. 본 도구는 위 6종만 선택할 수 있으므로, 두 flag가 필요한 패턴은 브라우저 개발자 도구 콘솔에서 확인하세요.
한국 데이터 패턴 10종 — 통과해도 틀릴 수 있는 값
패턴 라이브러리의 한국 데이터 패턴은 입력 형식을 거르는 1차 필터입니다. 아래 표의 ‘반례’는 형식은 맞지만 실제로는 유효하지 않은 값으로, 각 패턴에 넣어 빌드 때 직접 실행한 결과입니다. 이런 값까지 막으려면 날짜 검사·검증번호 계산·외부 조회를 코드로 추가해야 합니다.
| 패턴 | 정규식 | 예시 | 반례 | 정규식이 확인하지 않는 것 |
|---|---|---|---|---|
| 한국 휴대폰 | ^010-?\d{3,4}-?\d{4}$ | 010-1234-5678 통과 | 010-12345678 통과 | 010만 허용(01X 옛 번호 거부). 하이픈이 한쪽에만 있는 표기도 통과 |
| 한국 일반전화 | ^0\d{1,2}-?\d{3,4}-?\d{4}$ | 02-1234-5678 통과 | 010-1234-5678 통과 | 휴대폰 번호도 통과(0 + 두 자리 규칙). 1588 같은 전국대표번호는 거부 |
| 주민등록번호 (앞-뒤) | ^\d{6}-[1-4]\d{6}$ | 901231-1234567 통과 | 991399-1234567 통과 | 생년월일 유효성 미검사. 1800년대 출생(9·0)은 거부. 2020.10 이후 번호는 체크섬 규칙 없음 |
| 외국인등록번호 | ^\d{6}-[5-8]\d{6}$ | 901231-5234567 통과 | 000000-5000000 통과 | 생년월일 유효성 미검사 — 자릿수와 7번째 자리(5~8)만 확인 |
| 사업자등록번호 | ^\d{3}-?\d{2}-?\d{5}$ | 123-45-67890 통과 | 000-00-00000 통과 | 마지막 자리 검증번호(앞 9자리로 계산) 미검사 |
| 법인등록번호 | ^\d{6}-?\d{7}$ | 110111-1234567 통과 | 000000-0000000 통과 | 마지막 자리 검증번호 미검사 — 13자리 숫자면 통과 |
| 한국 우편번호 (5자리) | ^\d{5}$ | 03187 통과 | 99999 통과 | 5자리 숫자면 모두 통과 — 실제 구역번호인지는 주소 DB로 확인 |
| 차량번호 (12가1234) | ^\d{2,3}[가-힣]\d{4}$ | 12가1234 통과 | 999힣9999 통과 | 번호판에 쓰지 않는 한글도 통과. 지역명 구형 번호판(서울12가1234)·공백 표기는 거부 |
| 카드번호 (16자리) | ^\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}$ | 4123-4567-8901-2345 통과 | 0000-0000-0000-0001 통과 | Luhn 체크섬 미검사. 16자리 전용이라 15자리 카드(4-6-5)는 거부 |
| 계좌번호 (일반) | ^\d{3,6}-?\d{2,6}-?\d{2,8}$ | 110-123-456789 통과 | 1234567 통과 | 은행마다 자릿수가 달라 7~20자리 숫자면 대부분 통과 — 은행·예금주 조회가 별도로 필요 |
주민등록번호는 2020년 10월부터 새로 부여·변경되는 번호의 뒷자리가 성별 1자리 + 임의번호 6자리로 바뀌었습니다. 예전 번호용 검증번호 공식으로 거르면 새 번호를 잘못 거부하게 됩니다.
Catastrophic Backtracking — 정규식이 멈추는 이유
JavaScript 정규식 엔진은 실패하면 되돌아가(backtrack) 다른 분할을 시도합니다. (a+)+$에 a 25개와 느낌표를 주면, a들을 안쪽·바깥쪽 반복에 나눠 담는 방법이 224가지 넘게 있고 엔진은 그 전부를 시도한 뒤에야 실패를 선언합니다. 입력이 한 글자 늘 때마다 시간이 약 2배가 되므로, 서버에서 사용자 입력에 이런 정규식을 쓰면 서비스 거부(ReDoS) 공격에 노출됩니다. 본 도구는 정규식을 별도 작업 스레드(Web Worker)에서 돌려 1초가 넘으면 자동 중단합니다.
| 위험 패턴 | 문제 | 안전한 대체 |
|---|---|---|
(a+)+$ | 반복 안의 반복 — 분할 경우의 수가 지수적 | a+$ |
(a|aa)+$ | 두 분기가 같은 글자열을 서로 다르게 나눠 가짐 | a+$ |
(\w+\s?)+$ | 공백이 선택적이라 단어 경계를 어디서든 나눌 수 있음 | \w+(?:\s\w+)*$ |
"(.*)*" | 빈 문자열도 매치하는 그룹을 반복 | "[^"]*" |
- lazy로 바꾸는 것은 해결책이 아닙니다 —
(a+?)+?$도 실패하는 입력에서는 같은 경우의 수를 모두 탐색합니다. 탐색 순서만 달라질 뿐입니다. - atomic group 흉내 — JS에는
(?>…)가 없지만(?=(a+))\1처럼 lookahead로 잡은 뒤 역참조로 소비하면 그 부분은 되돌아가지 않습니다. - 입력 길이 제한 — 서버 측 정규식은 입력 길이 상한과 실행 시간 제한을 함께 두세요. 지수 시간 패턴도 입력이 짧으면 문제가 드러나지 않아 테스트에서 놓치기 쉽습니다.
자주 묻는 질문
JavaScript 정규식과 PCRE·Python 정규식은 무엇이 다른가요?
(?<=…): JS는 가변 길이도 허용하지만 Python re는 고정 길이만 받습니다. • atomic group (?>…)·possessive a*+: PCRE·Java·Python 3.11+ re에는 있지만 JS에는 없습니다. 재귀 (?R)는 PCRE(Perl 계열) 전용입니다. • 이름 그룹 표기: JS·PCRE (?<name>…), Python (?P<name>…). • 유니코드 속성 \p{…}: JS는 u(또는 v) flag가 있어야 동작합니다. • 인라인 flag: 패턴 맨 앞의 (?i) 같은 전역 인라인 flag는 Python·Java·PCRE에서만 되고 JS에서는 쓸 수 없습니다(ES2025부터 (?i:…)처럼 일부 구간에만 i·m·s를 거는 문법은 표준이 됐지만, 구형 브라우저에서는 오류). 그래서 치환 탭의 코드 스니펫은 flag를 언어별 표기로 바꿔 줍니다.greedy(.*)와 lazy(.*?)의 차이는?
.*, 기본값)는 가능한 한 길게, lazy(.*?)는 가능한 한 짧게 매치합니다. 입력 <b>hello</b><i>world</i>에서 <.*>는 문자열 전체 1개를, <.*?>는 <b>·</b>·<i>·</i> 4개를 각각 매치합니다. 구분자 사이의 짧은 구간을 잡을 때는 lazy가 맞지만, 중첩된 태그·괄호는 정규식만으로 정확히 파싱할 수 없습니다 — HTML은 DOMParser, JSON은 JSON.parse 같은 파서를 쓰세요.캡처 그룹과 비캡처 그룹의 차이?
(…)은 매치한 부분을 기억해 치환에서 $1·$2로, 패턴 안에서 \1로 다시 참조할 수 있습니다. 비캡처 그룹 (?:…)은 묶기만 하고 기억하지 않습니다. 비캡처 그룹을 쓰면 번호가 꼭 필요한 그룹에만 매겨져 $1이 가리키는 대상이 명확해집니다. 예: (?:https?|ftp):\/\/(\w+) — 프로토콜은 묶기만 하고 호스트 첫 단어만 $1로 캡처합니다.이름 캡처 그룹(named group)은 어떻게 쓰나요?
(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})로 2026-05-15를 매치하면 match.groups.year가 "2026"이 되고, 치환 문자열에서는 $<year>년처럼 씁니다. 이름 그룹도 번호가 매겨지므로 $1로도 참조됩니다. 그룹을 추가·삭제해도 번호가 밀리지 않아 긴 패턴일수록 유지보수가 쉬워집니다.lookahead와 lookbehind는?
(?=…) 뒤가 …일 때, (?!…) 뒤가 …이 아닐 때, (?<=…) 앞이 …일 때, (?<!…) 앞이 …이 아닐 때 매치합니다. 예: \d+(?=원)에 1000원, 2000$를 주면 1000만 매치되고 원은 결과에 포함되지 않습니다. 비밀번호 규칙의 (?=.*\d)(?=.*[A-Z])처럼 조건을 여러 개 겹칠 때도 씁니다.flags g와 y의 차이는?
lastIndex 위치부터 매치를 시도하지만, g는 그 위치에서 실패하면 뒤로 한 칸씩 옮겨 가며 계속 찾고, y(sticky)는 정확히 그 위치에서만 시도합니다. 입력 foo bar에서 lastIndex = 3이면 /bar/g는 4번 위치의 bar를 찾지만 /bar/y는 3번 위치가 공백이라 실패합니다. y는 ‘현재 위치에서 다음 토큰이 무엇인가’를 확인하는 토크나이저·파서 작성에 씁니다.u flag가 왜 필요한가요?
. 하나에 매치되지 않고(😀는 코드 단위 2개), \p{L}은 유니코드 속성이 아니라 글자 p{L}로 해석돼 오류 없이 엉뚱한 결과가 나옵니다. u를 켜면 \p{L}(모든 문자), \p{N}(숫자), \p{Script=Hangul}(한글 문자 체계), \p{Extended_Pictographic}(그림 이모지)를 쓸 수 있습니다. 대신 u 모드는 문법이 엄격해 \- 같은 불필요한 이스케이프를 오류로 처리합니다.한글·이모지 매칭은 어떻게 하나요?
[가-힣](U+AC00~U+D7A3, 11,172자)이 표준입니다. 자음·모음 낱자(ㅋㅋ, ㅠㅠ)는 [ㄱ-ㅎㅏ-ㅣ]로 따로 넣어야 하고, 옛한글까지 넓히려면 \p{Script=Hangul}(u flag)를 씁니다. 주의할 점은 \w와 \b가 한글을 인식하지 못한다는 것입니다 — JS의 \w는 u flag를 켜도 [A-Za-z0-9_]뿐이라 /\b한글\b/는 ‘나는 한글 좋아’에서도 실패합니다. 한글 단어 경계는 (?<![가-힣])한글(?![가-힣])처럼 lookaround로 만드세요. 이모지는 \p{Emoji}가 숫자 0~9·#·*까지 포함하므로 \p{Extended_Pictographic}을 쓰고, 국기·피부톤·ZWJ 조합은 패턴 라이브러리의 이모지 패턴처럼 결합 문자까지 묶어야 한 덩어리로 잡힙니다.주민등록번호·카드번호를 정규식으로 검증하면 충분한가요?
정규식이 너무 느려요 (catastrophic backtracking)
(a+)+$처럼 반복 안에 반복이 있고 같은 글자를 여러 방식으로 나눠 가질 수 있으면, 매치에 실패하는 입력에서 엔진이 가능한 분할을 모두 시도해 시간이 입력 길이에 지수적으로 늘어납니다. 해결책은 ① 중첩 반복 제거 — (a+)+ → a+, ② 분기끼리 겹치지 않게 — (a|aa)+ → a+, ③ 구분자를 빼는 문자 클래스 — ".*" → "[^"]*", ④ JS에 없는 atomic group 대신 (?=(a+))\1처럼 lookahead+역참조로 되돌아가기를 막기입니다. lazy(+?)로 바꾸는 것은 해결책이 아닙니다 — 시도 순서만 바뀔 뿐 실패하는 입력에서는 똑같이 모든 경우를 탐색합니다. 본 도구는 정규식을 별도 작업 스레드(Web Worker)에서 실행해 1초가 넘으면 중단하고, 100ms를 넘기면 경고하며, 입력 100KB·매치 1만 개로 제한합니다.참고 자료
- MDN 정규 표현식 안내서(새 창)developer.mozilla.org
- ECMA-262 (ECMAScript 명세)(새 창)tc39.es
- OWASP ReDoS(새 창)owasp.org
- 개인정보 보호법 시행령 제19조(새 창)law.go.kr
- 행정안전부 주민번호 부여체계 개편(새 창)korea.kr
- 계산 기준
- ECMAScript 정규식(브라우저 내장 엔진) 문법 기준 · 주민등록번호 부여체계 개편(2020.10) · 개인정보 보호법 시행령 제19조
- 오류 제보
- 결과가 다르면 contact@youtil.kr로 입력 조건을 알려 주세요. 평일 기준 1~3일 안에 답변합니다.