정규 표현식(Regular Expression)
정규 표현식(Regex, Regular Expression)은 문자열에서 특정 패턴을 검색·검증·치환하기 위한 강력한 도구입니다. JavaScript, Python, Java, SQL 등 다양한 언어에서 입력값 검증, 문자열 치환, 데이터 필터링 등에 폭넓게 사용됩니다.
1) 기본 문법 요약
- . : 임의의 한 문자
예) a.b → "acb", "a1b" - ^ / $ : 문자열의 시작 / 끝
예) ^hello, world$ - * / + / ? : 0회 이상 / 1회 이상 / 0 또는 1회
예) a*, a+, a? - {n} / {n,} / {n,m} : 정확히 n회 / 최소 n회 / n~m회
예) a{3}, a{2,}, a{2,4} - [abc] / [^abc] : 집합 포함 / 제외
예) [aeiou], [^aeiou] - \d / \D : 숫자 / 숫자 아님 ([0-9], [^0-9])
- \s / \S : 공백 / 공백 아님
- \w / \W : 영문자·숫자·밑줄 / 그 외
- \b / \B : 단어 경계 / 비경계
- | : OR (대안, alternation)
- ( ) : 캡처 그룹 / (?: ) : 비캡처 그룹
중요한 주의점
- ^/$는 기본적으로 문자열 전체의 시작/끝을 의미합니다. m 플래그를 켜면 각 줄의 시작/끝으로 해석됩니다.
- 문자 클래스([ ]) 안의 -는 범위 지정으로 인식됩니다. 범위가 아닐 때는 이스케이프 하세요. 예) [-_.] 또는 [\-_.].
2) 자주 쓰는 패턴
2.1 이메일
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
const email = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
email.test("user@example.com"); // true
email.test("invalid-email@com"); // false
2.2 숫자(정수)만
^\d+$
/^\d+$/.test("12345"); // true
/^\d+$/.test("123a"); // false
2.3 한글만
^[가-힣]+$
/^[가-힣]+$/.test("안녕하세요"); // true
/^[가-힣]+$/.test("Hello안녕"); // false
2.4 비밀번호(영문+숫자, 8~16자)
^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$
/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$/.test("pass1234"); // true
2.5 휴대폰 번호(한국, 010만 허용)
^010-?\d{4}-?\d{4}$
/^010-?\d{4}-?\d{4}$/.test("010-1234-5678"); // true
/^010-?\d{4}-?\d{4}$/.test("011-1234-5678"); // false
2.6 특정 단어 포함 여부
apple
/apple/.test("I like apple pie."); // true
3) 활용 예시
3.1 문자열에서 모든 숫자 찾기
const str = "My phone number is 010-1234-5678.";
const numbers = str.match(/\d+/g); // ["010", "1234", "5678"]
3.2 전화번호 마스킹(뒷 네 자리)
const phone = "010-1234-5678";
const masked = phone.replace(/\d{4}-\d{4}$/, "****-****");
// "010-****-****"
4) 플래그(Flags)
- g : 전역 검색 (여러 매치)
- i : 대소문자 구분 없음
- m : 멀티라인 앵커(^/$를 각 줄 기준)
- s : dotAll (.이 줄바꿈도 포함)
- u : 유니코드 모드 (\u{1F600}, \p{…} 등 사용)
- y : sticky(접착, 현재 인덱스부터만 매치)
- (선택) d : 매치의 인덱스 정보 반환(최신 JS)
"안\n녕".match(/^./g); // ["안"]
"안\n녕".match(/^./gm); // ["안","녕"]
"a\nb".match(/a.+b/s); // ["a\nb"]
5) 탐욕적 vs 게으른 수량자
- 탐욕적(기본): 가능한 한 많이 → .*
- 게으른: 가능한 한 적게 → .*?, +?, ??, {n,m}?
"<a>1</a><a>2</a>".match(/<a>.*<\/a>/);
// ["<a>1</a><a>2</a>"]
"<a>1</a><a>2</a>".match(/<a>.*?<\/a>/g);
// ["<a>1</a>", "<a>2</a>"]
6) 룩어라운드(Lookaround)
- 전방: (?=...)(긍정), (?!...)(부정)
- 후방: (?<=...)(긍정), (?<!...)(부정) — 현대 JS 지원
// ₩ 기호 뒤의 숫자만 추출
"₩1000 2000".match(/(?<=₩)\d+/g); // ["1000"]
// 'http'로 시작하지 않는 도메인 토큰(간단 예)
"url.com http://a.com".match(/(?
7) 그룹과 역참조
- 캡처 그룹: ( … )
- 비캡처 그룹: (?: … )
- 이름 있는 그룹: (?<name> … ) → 역참조 \k<name>
const re = /(?<id>\d{3})-(?<seq>\d{2})/;
const m = "123-45".match(re);
m.groups.id; // "123"
"abcabc".replace(/(abc)/, "[$1]"); // "[abc]abc"
8) 유니코드 프로퍼티(u 플래그)
한글/다국어를 정확히 다루려면 \p{…}가 유용합니다.
- 범주: \p{L}(문자), \p{N}(숫자), \p{Zs}(공백) 등
- 스크립트: \p{Script=Hangul}, \p{Script=Latin}
/^\p{Script=Hangul}+$/u.test("안녕하세요"); // true
주의: \b(단어 경계)는 영문/숫자 기준이라 한글 경계에는 부정확할 수 있습니다. 한글 경계 판별이 필요하면 \p{…}와 룩어라운드를 조합하세요.
9) JavaScript 메서드 차이
- re.test(str) : 불리언
- re.exec(str) : 하나의 매치와 그룹 반환
- str.match(re) :
- g 없음 → 첫 매치(그룹 포함)
- g 있음 → 모든 매치(그룹 정보 없음)
- str.matchAll(re) : g와 함께 모든 매치 + 그룹(이터레이터)
- str.replace(re, rep) / replaceAll : $&(전체), $1…(그룹), $<name>(이름 그룹) 가능. 함수형 치환 지원
- str.split(re), str.search(re) : 정규식 지원
const re = /(\d{2})-(\d{2})/g;
"11-22 33-44".match(re); // ["11-22","33-44"]
[..."11-22 33-44".matchAll(re)][0]; // 첫 매치(그룹 접근 가능)
"abc123".replace(/\d+/, m => `[${m}]`); // "abc[123]"
10) 동적 패턴을 안전하게 만들기(escape)
사용자 입력을 정규식으로 만들 때는 메타문자 이스케이프가 필수입니다.
function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
const user = "a.b+c";
const re = new RegExp(escapeRegExp(user), "g"); // /a\.b\+c/g
11) 성능·안전(ReDoS) 팁
- 중첩 가변 수량자(예: (a+)+)는 피하세요 — catastrophic backtracking 위험.
- 가능하면 앵커(^…$)로 범위를 좁히고, 명확한 클래스를 사용합니다.
- 복잡한 대안(alternation)은 빈도 높은 패턴을 먼저 배치합니다.
- 반복 사용 패턴은 리터럴이나 한 번만 new RegExp하여 재사용하세요.
12) 실무 레시피
12.1 공백 정규화(여러 공백 → 하나, 앞뒤 공백 제거)
const normalizeSpaces = s => s.trim().replace(/\s+/g, " ");
12.2 한국 휴대폰 포맷팅(숫자만 → 하이픈 삽입)
const fmtPhone = s => s
.replace(/[^\d]/g, "")
.replace(/^(010)(\d{4})(\d{4})$/, "$1-$2-$3");
12.3 숫자만 남기기
"₩12,345원".replace(/[^\d]/g, ""); // "12345"
12.4 단어 경계 없이 “정확히 그 단어” 찾기(한글 포함)
// '사과'가 다른 한글 문자와 붙어있지 않을 때만
const word = /(?<!\p{L})사과(?!\p{L})/u;
word.test("사과 쥬스"); // true
word.test("사과쥬스"); // false
12.5 URL 러프 탐지(완전 검증용 아님)
const urlish = /\bhttps?:\/\/[^\s/$.?#].[^\s]*\b/i;
주의: URL/E-mail의 완전한 RFC 수준 검증은 정규식 하나로 안전하게 커버하기 어렵습니다. “간단 필터” 용도로만 쓰고, 엄격 검증은 파서/라이브러리를 고려하세요.
13) 디버깅·학습 팁
- 온라인/IDE 정규식 도구로 플래그·그룹·경계를 켜고 끄며 시각적으로 확인하세요.
- 작은 테스트 코드(jest/vitest 등)를 두어 회귀를 막으세요.
- 복잡한 정규식은 비캡처 그룹 (?: ), 이름 그룹 (?<name>)을 사용해 의도를 드러내고 유지보수를 쉽게 만드세요.
'JavaScript' 카테고리의 다른 글
| npm 라이브러리 만들고 사용하기 (1) | 2025.06.13 |
|---|---|
| JavaScript의 Object 객체와 주요 메서드 (1) | 2025.03.21 |
| Bun (1) | 2025.02.11 |
| [JavaScript] 구조 분해 할당 (4) | 2024.12.22 |
| [JavaScript] Reduce (5) | 2024.12.22 |