정규식 테스터

현재 입력을 예제 데이터로 바꿉니다.

예상 결과: 2026-10-02와 2026-11-15 두 날짜가 일치합니다. 치환 결과는 Releases: 02/10/2026, 15/11/2026.입니다.

제한 사항: PCRE 대신 JavaScript RegExp를 사용합니다. 이 패턴은 날짜 모양만 찾으며 실제 달력 날짜를 검증하지 않습니다. Worker 실행 제한은 1.5초입니다.

입력한 내용은 저장, 기록, 공유되지 않습니다.

패턴을 입력하면 일치하는 부분이 실시간으로 강조되고 캡처 그룹과 위치도 함께 표시됩니다. 매칭은 백그라운드 스레드에서 실행되므로 파국적 백트래킹이 발생해도 페이지가 멈추지 않습니다.

사용 방법

  1. 패턴을 입력하고 필요한 플래그를 선택합니다.
  2. 테스트할 텍스트를 붙여넣으면 즉시 강조됩니다.
  3. 치환을 켜면 $1 또는 $<name>으로 치환 결과를 미리 볼 수 있습니다.

정규 표현식이 할 수 없는 것

정규 표현식은 구조가 아니라 패턴을 찾습니다. 무언가의 안쪽으로 얼마나 깊이 들어와 있는지 기억하지 못하므로, 임의로 중첩될 수 있는 것은 표현할 수 있는 범위를 분명히 벗어납니다. 짝이 맞는 괄호, 중첩된 따옴표, HTML, JSON, 소스 코드가 그렇습니다.

이런 것에 잘 동작하는 것처럼 보이는 패턴은 흔한 경우에 맞고 나머지에서는 대개 조용히 실패합니다. 속성에 >가 들어가기 전까지만 동작하는 HTML 태그 패턴, 문자열 리터럴 한가운데를 먹어 버리는 주석 제거기, 따옴표로 감싼 첫 쉼표에서 깨지는 CSV 분할기가 그렇습니다. 모두 몇 달 뒤 남의 데이터에서 드러나는 버그입니다.

경험칙은 이렇습니다. 찾으려는 대상이 자기 자신을 포함할 수 있다면 파서가 필요합니다. 정규 표현식은 그보다 한 단계 아래 층에서 탁월합니다. 필드 검증, 알려진 로그 형식에서 값 추출, 코드베이스 전체에서 찾아 바꾸기, 각 부분 안에 나타날 수 없는 구분자로 나누기 같은 일입니다.

탐욕적 매칭, 게으른 매칭, 그리고 백트래킹의 원인

수량자는 기본적으로 탐욕적입니다. .*는 가능한 한 많이 가져간 뒤 나머지 패턴이 맞을 때까지 한 글자씩 되돌려 줍니다. 그래서 <.+>를 <b>text</b>에 적용하면 첫 번째 태그가 아니라 문자열 전체와 일치합니다. 탐욕적인 .+가 모든 것을 삼키고, 마지막 >는 입력의 마지막 >로 채워지기 때문입니다.

물음표를 붙이면 수량자가 게을러집니다. <.+?>는 가능한 한 적게 가져가 <b>만 일치합니다. 대부분의 사람이 의도하는 것은 게으른 쪽이지만, 보통은 둘 다보다 문자 클래스가 낫습니다. <[^>]+>는 실제로 원하는 것을 그대로 말하고, 태그 경계를 절대 넘지 않으며, 백트래킹도 하지 않습니다.

이 마지막 점이 실무에서 신경 써야 하는 이유입니다. 패턴을 느리게 만드는 것은 백트래킹이며, (a+)+나 (\s*\w+)*처럼 수량자를 다른 수량자 안에 중첩하면 엔진이 탐색하는 경로 수가 기하급수적으로 늘어납니다. 거의 일치하는 입력에서는 이런 패턴이 몇 분씩 돌아갈 수도 있습니다. 이 테스터가 워커에서 매칭을 실행하고 1.5초 후 강제로 종료하는 것은, 실수로 그런 패턴을 쓰는 일이 아주 흔하기 때문입니다.

발목을 잡는 방언 차이

여기의 엔진은 브라우저의 것이므로 결과는 JavaScript와 정확히 같습니다. 하지만 패턴을 붙여 넣을 언어와 반드시 같지는 않습니다.

이름 있는 그룹은 JavaScript, .NET, 최신 PCRE에서 (?<name>...)이지만, Python은 (?P<name>...)도 받아들이고 오래된 도구는 후자만 이해합니다. 후방 탐색은 현재 JavaScript, .NET, PCRE에서 쓸 수 있지만 Python에서는 고정 길이여야 하고 Go에는 아예 없습니다. Go의 RE2와 Rust의 regex 크레이트는 선형 시간을 보장하기 위해 역참조와 전후방 탐색을 설계상 빼 두었으며, 둘 중 하나에 의존하는 패턴은 컴파일조차 되지 않습니다.

약식 클래스의 의미도 달라집니다. JavaScript에서 \d는 ASCII 숫자만이지만, .NET에서는 기본적으로 모든 문자 체계의 숫자와 일치하고, Python에서는 패턴이 str인지 bytes인지에 따라 다릅니다. 그리고 JavaScript의 u 플래그는 .과 수량자가 코드 유닛이 아니라 코드 포인트 단위로 동작하게 해 주며, 이것이 없으면 패턴이 이모지나 기본 다국어 평면(BMP) 밖의 문자에서 깨집니다.

중요한 패턴은 배포하기 전에 자신의 실행 환경에서 테스트하세요.

나중에도 읽을 수 있는 패턴 쓰기

앵커를 거세요. 필드 검증에 쓰는 패턴에 ^와 $가 없으면 부분 문자열에도 기꺼이 일치하므로, 앵커 없는 이메일 패턴은 안에 유효한 주소가 묻혀 있기만 하면 무엇이든 받아들입니다. 검증 우회가 반복해서 생기는 원인입니다.

둘 다 가능하다면 선택(|)보다 문자 클래스를 쓰세요. [abc]는 (a|b|c)보다 명확하고 빠르며, [^,]+는 '쉼표를 넘지 않는다'를 .+?가 흉내만 내는 방식이 아니라 정확히 표현합니다. 그룹은 세지 말고 이름을 붙이세요. 누군가 앞에 그룹을 끼워 넣어도 (?<year>...)는 그대로지만 $3은 그렇지 않습니다.

그다음 일치하면 안 되는 경우를 테스트하세요. 대부분의 패턴은 일치해야 할 입력을 넣어 보며 맞을 때까지 고치는 식으로 만들어지고, 그 결과는 어김없이 너무 느슨합니다. 더 유용한 연습은 절대 일치하면 안 되는 입력을 붙여 넣고 정말 일치하지 않는지 확인하는 것입니다. 형식이 틀린 주소, 안에 구분자가 들어간 값, 기대하는 로그 형식과 거의 같지만 조금 다른 줄 같은 것들입니다.

실제 패턴을 한 조각씩 읽어 보기

흔한 로그 한 줄을 다루는 패턴을 예로 들어 보겠습니다. ^(\S+) \S+ \S+ \[([^\]]+)\] "(\w+) ([^"]*)" (\d{3}) (\d+)$ 입니다. 빽빽해 보이지만 깔끔하게 분해됩니다. 맨 앞의 캐럿은 줄의 시작에 고정합니다. (\S+)는 공백이 아닌 문자들의 연속, 즉 클라이언트 주소를 캡처하고, 뒤따르는 캡처하지 않는 \S+ 두 개는 아무도 쓰지 않는 identity 필드와 user 필드를 건너뜁니다.

\[([^\]]+)\]는 타임스탬프를 캡처합니다. 이스케이프한 대괄호, 그다음 닫는 대괄호가 아닌 모든 문자, 마지막으로 닫는 대괄호입니다. 이 부분을 튼튼하게 만들어 주는 것은 부정 문자 클래스인데, .+처럼 필드의 끝을 지나쳐 달려가는 일이 없기 때문입니다. 따옴표로 감싼 요청 부분도 같은 발상을 따옴표에 적용한 것이고, 메서드와 경로로 나누어 두었습니다.

(\d{3})은 상태 코드로 정확히 세 자리를 캡처하고, (\d+)는 바이트 수를 캡처하며, 달러 기호가 끝을 고정합니다. 여기서 일반적인 교훈을 얻을 수 있습니다. 부정 문자 클래스와 명시적인 개수로 만든 패턴은 점과 게으른 수량자로 만든 같은 패턴보다 읽기 쉽고 더 빠르게 동작합니다.

파국적 백트래킹과 그것을 피하는 법

백트래킹 방식 엔진은, 그리고 대부분이 그렇습니다만, 매칭에 실패하면 다른 가능성을 시도합니다. 수량자를 다른 수량자 안에 중첩하면 엔진이 입력을 나눌 수 있는 경우의 수가 지수적으로 늘어나므로, (a+)+$ 같은 패턴에 a가 길게 이어지고 뒤에 b가 붙은 문자열을 넣으면 우주가 존재한 시간보다 더 오래 돌아갈 수도 있습니다.

이것은 ReDoS로 분류되는 실제 서비스 거부 공격 수단이며, 운영 중인 서비스를 멈춰 세운 적도 있습니다. 요청을 파싱하는 경로에 있는 정규식에 조작된 입력이 들어오면 CPU 코어 하나를 무한정 붙잡아 둡니다. 2019년 Cloudflare의 전 세계 장애가 바로 이런 형태의 패턴 때문에 일어났습니다.

방어책 세 가지면 대부분의 경우를 막을 수 있습니다. 수량자를 중첩하지 말고 점보다 부정 문자 클래스를 쓰십시오. [^"]*는 뒤에 오는 것과 겹칠 수 없으니 되돌아갈 여지가 없습니다. 엔진이 지원한다면 원자적 그룹이나 소유 수량자를 쓰십시오. 그리고 패턴이 사용자에게서 온다면 이 테스터처럼 타임아웃을 건 워커에서 실행하거나, RE2 같은 선형 시간 엔진을 쓰십시오.

유니코드, 그리고 \w로는 부족한 이유

축약 문자 클래스는 ASCII를 기준으로 정의되었고 지금도 대체로 그대로입니다. JavaScript에서 \d는 0-9만, \w는 ASCII 문자와 숫자, 밑줄만 매칭하므로 \w+로 이름을 검증하는 패턴은 José와 Zoë를 비롯해 라틴 문자로 쓰이지 않은 모든 이름을 거부합니다.

이를 제대로 해결해 주는 것이 유니코드 속성 이스케이프입니다. u 플래그를 켜면 \p{L}은 어떤 문자 체계의 글자든 매칭하고, \p{N}은 모든 숫자 문자를, \p{Script=Han}은 특정 문자 체계를 매칭합니다. 문자 클래스를 손으로 넓히는 것보다 훨씬 정확하고, 유니코드에 글자가 추가되어도 계속 동작합니다.

u 플래그를 켜야 할 두 번째 이유도 있습니다. 켜지 않으면 엔진이 코드 포인트가 아니라 UTF-16 코드 단위를 다루기 때문에 점이 이모지의 절반에 매칭되고 문자 클래스가 서로게이트 쌍을 갈라놓을 수 있습니다. 켜더라도 눈에 보이는 한 글자가 여러 코드 포인트일 수 있습니다. 국기나 가족 이모지, 기본 문자에 결합 기호를 붙여 쓴 악센트 글자가 그렇습니다. 그래서 정규식으로 글자 수를 세려면 여전히 v 플래그의 집합 표기법이나 자소를 이해하는 라이브러리가 필요합니다.

정규식을 쓰지 말아야 할 때

고정된 문자열이라면 평범한 문자열 메서드가 더 명확하고 빠릅니다. startsWith, includes, split이 있습니다. 문자열이 .json으로 끝나는지 확인하려고 패턴을 꺼내 드는 것은 고칠 만한 습관입니다. 단순한 쪽은 나중에 설명할 필요가 없기 때문입니다.

중첩이 있는 것, 즉 HTML, JSON, 소스 코드, 짝이 맞는 괄호 같은 것에는 파서를 쓰십시오. 정규식은 깊이를 셀 수 없으므로 이런 것을 처리하는 듯 보이는 패턴은 모두 흔한 경우에만 맞고 나머지에서는 조용히 실패합니다. 정규식으로 HTML을 파싱하는 것에 관한 그 유명한 Stack Overflow 답변이 웃긴 이유는, 그 밑에 깔린 지적이 정확하기 때문입니다.

구조화된 데이터에는 대개 그 일만을 위해 만들어진 도구가 있습니다. 따옴표 안의 쉼표를 이해하는 CSV 파서, userinfo와 포트를 이해하는 URL 파서, 시간대를 이해하는 날짜 라이브러리가 그렇습니다. 이들이 존재하는 이유는 하나같이, 정규식 버전이 수천 번 작성되었고 그때마다 미묘하게 틀렸기 때문입니다.

두고두고 쓰게 될 패턴들

공백 다듬기와 정규화: \s+를 공백 하나로 치환하면 연속된 공백이 합쳐지고, trim 함수가 없는 언어에서는 ^\s+|\s+$로 앞뒤를 잘라 냅니다. 따옴표로 감싼 값을 뽑아낼 때는 "([^"]*)"를 씁니다. 필드 안에는 나오지 않는 구분자로 나눌 때는 게으른 점 대신 부정 문자 클래스를 쓰십시오.

반대로 쓰지 않는 편이 나은 패턴이 둘 있습니다. 정규식으로 이메일 주소를 검증하는 것은 함정입니다. RFC 5322의 문법은 정말로 복잡하고, 실용적인 확인은 로컬 파트가 비어 있지 않고 골뱅이 기호가 있으며 도메인에 점이 있는지를 보고 나서 확인 메일을 보내는 것입니다. 그것이 유일한 진짜 검증입니다. URL 검증도 같은 모양의 문제이며, 이제는 어느 언어든 이 질문에 제대로 답해 주는 URL 파서를 기본으로 제공합니다.

패턴이 설명이 필요할 만큼 길어졌다면 이름 있는 그룹을 쓰고, 지원하는 언어라면 패턴 안에 공백과 주석을 넣을 수 있는 확장 플래그를 쓰십시오. 여섯 달 뒤에도 읽을 수 있는 패턴은 오늘 두 줄을 더 쓸 가치가 있습니다.

자주 묻는 질문

파국적 백트래킹이란 무엇인가요?
일부 패턴은 거의 일치하는 입력에서 지수 시간이 걸립니다. 대표적인 형태는 수량자 안의 수량자로, (a+)+$를 a가 길게 이어진 뒤 다른 문자가 오는 문자열에 적용하면 엔진이 가능한 모든 분할을 시도합니다. CPU가 수 초에서 수 분간 점유되는 일도 흔합니다.
이 도구는 왜 멈추지 않나요?
실행 중인 정규식은 JavaScript에서 취소할 수 없으므로 매칭을 Web Worker에서 수행합니다. 1.5초를 넘기면 워커를 종료하고 새 워커로 교체합니다. 그래서 페이지는 계속 반응하고 무슨 일이 있었는지도 알려줍니다.
각 플래그는 무엇을 하나요?
g는 첫 번째뿐 아니라 모든 일치를 찾고, i는 대소문자를 무시하며, m은 ^와 $가 줄바꿈에서도 동작하게 하고, s는 .이 줄바꿈과도 일치하게 합니다. u는 \p{...} 속성 이스케이프를 포함한 완전한 유니코드 처리를 켜고, y는 현재 위치에 고정해 매칭합니다.
치환할 때 캡처 그룹은 어떻게 참조하나요?
번호 그룹은 $1, $2, 이름 그룹은 $<name>, 전체 일치는 $&, 달러 기호 자체는 $$로 참조합니다.
제 코드와 같은 엔진인가요?
브라우저의 JavaScript 엔진을 사용하므로 JavaScript와는 정확히 일치합니다. PCRE, Python, Go, .NET은 후방 탐색 지원, 이름 그룹 문법, 유니코드 처리가 달라서 실제 런타임에서도 확인해야 합니다.
테스트 데이터가 어딘가로 전송되나요?
아닙니다. 패턴과 텍스트 모두 브라우저에만 남으므로 로그나 운영 환경 샘플도 안전하게 사용할 수 있습니다.

마지막 업데이트:

참고 자료

MDN — JavaScript RegExp

최근 변경

  • — 실행 가능한 예제, 예상 결과, 제한 사항 및 참고 링크를 추가했습니다.

문제 신고

진행 단계, 예상 결과, 실제 결과를 적어 주세요. 비밀 정보와 개인 정보는 삭제해 주세요.

관련 도구