2025-09-23 20:29
-
정규표현식은 텍스트에서 특정 패턴을 찾고 처리하기 위해 탄생한 강력한 도구다.
-
기본 문자, 메타 문자, 수량자 등을 조합하여 복잡한 패턴을 만들고 검색, 치환, 추출 등 다양한 작업에 활용된다.
-
정규표현식 엔진의 작동 방식을 이해하고 역참조, 전후방탐색 등 고급 기술을 익히면 데이터 처리 능력을 극대화할 수 있다.
개발자 필수 스킬 정규표현식 완벽 핸드북
프로그래밍, 데이터 분석, 시스템 관리 등 컴퓨터 과학의 여러 분야에서 텍스트 데이터는 핵심적인 역할을 담당한다. 이러한 텍스트 데이터를 효율적으로 다루기 위해 탄생한 언어가 바로 정규표현식(Regular Expression), 줄여서 Regex 또는 RegExp라고 불리는 강력한 도구다. 이 핸드북은 정규표현식의 탄생 배경부터 기본 문법, 작동 원리, 그리고 실전 활용법과 심화 기술까지 모든 것을 담아내어, 당신을 텍스트 처리의 마법사로 만들어 줄 것이다.
1. 정규표현식은 왜 만들어졌을까 탄생 배경과 철학
정규표현식의 역사는 1950년대로 거슬러 올라간다. 수학자 **스티븐 클레이니(Stephen Kleene)**는 형식 언어(Formal Language) 이론을 연구하며 특정 규칙을 따르는 문자열의 집합, 즉 ‘정규 집합(Regular Set)‘을 표현하기 위한 수학적 표기법을 고안했다. 이것이 바로 정규표현식의 이론적 기원이다.
당시 컴퓨터 과학자들은 인간의 언어를 기계가 이해하고 처리하게 하는 방법에 대해 깊이 연구하고 있었다. 특히, 인간의 뇌가 어떻게 문자 패턴을 인식하고 처리하는지에 대한 연구는 중요한 과제였다. 클레이니의 연구는 이러한 컴퓨터 과학의 흐름과 맞물려, 텍스트 내에서 특정 패턴을 기술하고 찾아내는 형식적인 방법론의 기초를 제공했다.
이론에 머물던 정규표현식이 컴퓨터 도구로 구현된 것은 1968년, 유닉스(Unix)의 아버지 중 한 명인 **켄 톰슨(Ken Thompson)**에 의해서다. 그는 QED(Quick Editor)라는 텍스트 편집기에 클레이니의 표기법을 기반으로 한 패턴 검색 기능을 추가했다. 이것이 우리가 오늘날 사용하는 정규표현식의 실질적인 시초다. 이후 grep (Global Regular Expression Print), sed, awk 등 유닉스의 핵심 텍스트 처리 유틸리티에 정규표현식이 탑재되면서 개발자들 사이에서 필수적인 도구로 자리 잡게 되었다.
정규표현식의 핵심 철학은 **“간결한 패턴으로 복잡한 텍스트를 제어한다”**는 것이다. 이메일 주소, 전화번호, HTML 태그처럼 일정한 규칙을 가진 모든 텍스트는 정규표현식이라는 ‘패턴 언어’로 정의될 수 있다. 개발자는 이 패턴을 이용하여 방대한 텍스트 더미 속에서 원하는 정보를 정확하게 찾아내거나(검색), 일괄적으로 수정하고(치환), 필요한 부분만 뽑아낼(추출) 수 있다. 이는 단순한 문자열 검색(예: Ctrl+F)과는 차원이 다른, 정교하고 유연한 텍스트 처리를 가능하게 한다.
2. 정규표현식의 구조 해부 패턴을 만드는 레고 블록
정규표현식은 여러 가지 특수 기호(메타 문자)와 일반 문자를 조합하여 패턴을 만드는 방식으로 작동한다. 마치 레고 블록을 조립하여 원하는 모양을 만드는 것과 같다. 각 블록의 역할을 정확히 이해하는 것이 정규표현식 숙달의 첫걸음이다.
2.1. 기본 구성 요소 (Atoms)
패턴의 가장 기본이 되는 단위는 개별 문자와 메타 문자다.
-
리터럴 (Literals):
a,b,1,2등 일반적인 문자나 숫자는 그 자체로 해당 문자와 일치한다.cat이라는 패턴은 “cat”이라는 문자열을 정확히 찾아낸다. -
메타 문자 (Metacharacters): 특별한 의미를 가진 문자들이다. 이들이 정규표현식을 강력하게 만드는 핵심 요소다.
| 메타 문자 | 이름 | 설명 | 예시 |
|---|---|---|---|
. | 점(Dot) | 개행 문자(\n)를 제외한 모든 문자 하나와 일치한다. | c.t는 “cat”, “cot”, “c_t” 등과 일치한다. |
| | 파이프(Pipe) | OR 연산자와 같다. 여러 패턴 중 하나를 선택한다. | cat|dog는 “cat” 또는 “dog”와 일치한다. |
[] | 대괄호(Brackets) | 대괄호 안에 있는 문자 중 하나와 일치한다. ‘문자 클래스’라고 부른다. | [abc]는 “a”, “b”, “c” 중 하나와 일치한다. |
[^] | 부정 대괄호 | 대괄호 안에 없는 모든 문자와 일치한다. | [^abc]는 “a”, “b”, “c”를 제외한 모든 문자와 일치한다. |
() | 그룹(Group) | 여러 패턴을 하나의 단위로 묶는다. | (cat)+는 “cat”, “catcat”, “catcatcat” 등과 일치한다. |
\ | 이스케이프(Escape) | 메타 문자의 특별한 기능을 없애고 일반 문자로 취급하게 한다. | \.는 실제 점(.) 문자와 일치한다. |
2.2. 수량자 (Quantifiers)
수량자는 바로 앞의 구성 요소가 몇 번 반복될지를 지정한다.
| 수량자 | 이름 | 설명 | 예시 |
|---|---|---|---|
* | 별표(Asterisk) | 앞의 문자가 0번 이상 반복되는 경우와 일치한다. | ca*t는 “ct”, “cat”, “caat”, “caaat” 등과 일치한다. |
+ | 더하기(Plus) | 앞의 문자가 1번 이상 반복되는 경우와 일치한다. | ca+t는 “cat”, “caat” 등과 일치하지만 “ct”와는 일치하지 않는다. |
? | 물음표(Question Mark) | 앞의 문자가 0번 또는 1번 나타나는 경우와 일치한다. | colou?r는 “color”와 “colour” 모두와 일치한다. |
{n} | 중괄호(Braces) | 앞의 문자가 정확히 n번 반복되는 경우와 일치한다. | a{3}는 “aaa”와 일치한다. |
{n,m} | 범위 중괄호 | 앞의 문자가 최소 n번, 최대 m번 반복되는 경우와 일치한다. | a{2,4}는 “aa”, “aaa”, “aaaa”와 일치한다. |
{n,} | 최소 반복 | 앞의 문자가 최소 n번 이상 반복되는 경우와 일치한다. | a{2,}는 “aa”, “aaa”, … 등과 일치한다. |
탐욕적(Greedy) vs. 게으른(Lazy) 수량자
기본적으로 수량자는 탐욕적으로 동작한다. 이는 가능한 한 가장 긴 문자열을 찾으려고 시도한다는 의미다. 예를 들어, <h1>Title</h1> 이라는 텍스트에 <.+> 패턴을 적용하면, 엔진은 <h1>Title</h1> 전체를 선택한다. 엔진은 .을 최대한 많이 일치시키려 하기 때문이다.
하지만 때로는 가장 짧은 문자열을 찾아야 할 필요가 있다. 이때 게으른 수량자를 사용한다. 수량자 뒤에 ?를 붙이면 게으른 모드로 동작한다. <.+?> 패턴을 동일한 텍스트에 적용하면, <h1>과 </h1> 두 개를 각각 찾아낸다. 엔진이 >를 만나는 순간 최소한의 일치로 만족하기 때문이다.
2.3. 경계 (Boundaries)
경계 메타 문자는 문자 자체가 아닌, 문자열 내의 특정 ‘위치’와 일치한다.
| 경계 | 이름 | 설명 | 예시 |
|---|---|---|---|
^ | 캐럿(Caret) | 문자열의 시작 부분과 일치한다. | ^cat는 “cat”으로 시작하는 문자열과 일치한다. |
$ | 달러(Dollar) | 문자열의 끝 부분과 일치한다. | cat$는 “cat”으로 끝나는 문자열과 일치한다. |
\b | 단어 경계(Word Boundary) | 단어(알파벳, 숫자, _의 연속)와 비단어 문자 사이의 경계에 일치한다. | \bcat\b는 “the cat”의 “cat”과는 일치하지만, “category”의 “cat”과는 일치하지 않는다. |
\B | 비단어 경계 | 단어 경계가 아닌 위치에 일치한다. | \Bcat\B는 “category”의 “cat”과는 일치하지만, “the cat”의 “cat”과는 일치하지 않는다. |
2.4. 약어 (Shorthand Character Classes)
자주 사용되는 문자 클래스는 약어로 간단하게 표현할 수 있다.
| 약어 | 의미 | 동일 표현 |
|---|---|---|
\d | 숫자(Digit) | [0-9] |
\D | 숫자가 아닌 문자 | [^0-9] |
\w | 단어 문자(Word) | [a-zA-Z0-9_] |
\W | 단어 문자가 아닌 문자 | [^a-zA-Z0-9_] |
\s | 공백 문자(Space) | [ \t\n\r\f\v] (띄어쓰기, 탭, 개행 등) |
\S | 공백이 아닌 문자 | [^ \t\n\r\f\v] |
3. 정규표현식 사용법 엔진은 어떻게 패턴을 찾는가?
정규표현식을 작성했다면, 이제 ‘엔진’이 이 패턴을 가지고 텍스트를 분석할 차례다. 정규표현식 엔진은 크게 두 가지 방식으로 작동하며, 이 차이를 이해하는 것은 복잡한 패턴의 성능을 예측하고 최적화하는 데 매우 중요하다.
3.1. NFA (Nondeterministic Finite Automaton) 엔진
Perl, Python, Java, JavaScript, Ruby 등 대부분의 최신 프로그래밍 언어에서 사용하는 엔진이다. NFA 엔진은 정규표현식 주도(Regex-directed) 방식으로 작동한다.
작동 방식 비유: 길 찾기 내비게이션
NFA 엔진의 작동 방식은 내비게이션이 여러 갈래의 길 앞에서 가능한 모든 경로를 하나씩 시도해보는 것과 같다.
-
패턴 따라가기: 엔진은 정규표현식의 각 부분을 순서대로 따라가며 텍스트와 비교한다.
a(b|c)d라는 패턴과abd라는 텍스트가 있다고 가정하자. -
a일치: 패턴의a와 텍스트의a가 일치한다. 다음으로 넘어간다. -
분기점 (OR): 패턴에서
(b|c)라는 분기점을 만난다. 엔진은 첫 번째 선택지인b를 먼저 시도한다. -
b일치: 텍스트의 다음 문자인b와 일치한다. 분기점을 성공적으로 통과했다. -
d일치: 패턴의 마지막d와 텍스트의d가 일치한다. -
성공: 패턴 전체가 텍스트와 일치했으므로, 엔진은 “일치 성공”을 반환한다.
만약 텍스트가 acd였다면, 3번 단계에서 b가 일치하지 않았을 것이다. 이때 엔진은 **백트래킹(Backtracking)**이라는 중요한 작업을 수행한다. 즉, 이전 분기점으로 돌아가 다른 선택지(c)를 시도한다. c가 텍스트의 c와 일치하므로 계속 진행하여 최종적으로 일치에 성공한다.
이 백트래킹 기능 때문에 NFA 엔진은 **역참조(Backreference)**나 **전후방탐색(Lookaround)**과 같은 강력한 고급 기능을 지원할 수 있다. 하지만 잘못 작성된 패턴(예: (a*)*)은 수많은 백트래킹을 유발하여 시스템 성능을 심각하게 저하시키는 ‘치명적인 백트래킹(Catastrophic Backtracking)’ 문제를 일으킬 수 있다.
3.2. DFA (Deterministic Finite Automaton) 엔진
grep, awk의 일부 구형 버전이나 lex 같은 도구에서 사용된다. DFA 엔진은 텍스트 주도(Text-directed) 방식으로 작동한다.
작동 방식 비유: 한 번에 모든 가능성 확인
DFA 엔진은 텍스트를 한 글자씩 읽으면서, 해당 글자에서 시작될 수 있는 모든 가능한 일치 상태를 동시에 추적한다.
-
상태 기록: 엔진은 텍스트의 첫 글자부터 읽기 시작한다. 현재 위치에서 패턴의 어느 부분까지 일치 가능한지 모든 상태를 기억한다.
-
한 번의 스캔: 텍스트를 처음부터 끝까지 단 한 번만 스캔한다. 백트래킹이 절대 발생하지 않는다.
-
최장 일치: 항상 가장 긴 일치 결과를 찾아낸다.
DFA 엔진은 매우 빠르고 예측 가능하게 동작한다는 큰 장점이 있다. 어떤 복잡한 패턴이라도 텍스트 길이에 비례하는 선형적인 시간 안에 검색을 완료한다. 하지만 백트래킹이 없기 때문에 NFA 엔진이 제공하는 역참조나 전후방탐색 같은 고급 기능은 사용할 수 없다.
| 특징 | NFA (Non-deterministic Finite Automaton) | DFA (Deterministic Finite Automaton) |
|---|---|---|
| 주도 방식 | 정규표현식 주도 (Regex-directed) | 텍스트 주도 (Text-directed) |
| 작동 원리 | 백트래킹을 통해 가능한 모든 경로를 시도 | 모든 가능한 상태를 동시에 추적하며 텍스트를 한 번만 스캔 |
| 성능 | 예측이 어려우며, 치명적 백트래킹 발생 가능 | 매우 빠르고 예측 가능 (텍스트 길이에 비례) |
| 기능 지원 | 역참조, 전후방탐색 등 고급 기능 지원 | 기능이 제한적임 (역참조 등 지원 불가) |
| 일치 결과 | 정규표현식의 선택 순서에 따라 첫 번째 일치 결과 반환 | 항상 가장 긴 문자열을 반환 |
| 주요 사용처 | Perl, Python, Java, JavaScript, Ruby 등 | 구형 grep, awk, lex |
4. 실전 정규표현식 활용 예제
이제 이론을 바탕으로 실제 문제들을 정규표현식으로 어떻게 해결하는지 살펴보자.
예제 1: 이메일 주소 검증
가장 흔한 예제 중 하나다. 간단한 이메일 형식(사용자명@도메인.최상위도메인)을 검증하는 패턴을 만들어보자.
-
^[\w.-]+: 문자열의 시작(^). 단어 문자(\w), 점(.), 하이픈(-)이 하나 이상(+) 온다. (사용자명 부분) -
@:@문자가 반드시 와야 한다. -
[\w-]+: 단어 문자(\w)와 하이픈(-)이 하나 이상(+) 온다. (도메인 이름) -
\.: 점(.) 문자가 반드시 와야 한다. -
[\w.-]+$: 단어 문자, 점, 하이픈이 하나 이상 오고 문자열이 끝난다($). (최상위 도메인 및 하위 도메인)
최종 패턴: ^[\w.-]+@[\w-]+\.[\w.-]+$
이 패턴은 기본적인 형식을 검증하지만, 세상의 모든 유효한 이메일 주소를 100% 완벽하게 검증하지는 못한다. 실제 이메일 표준(RFC 5322)은 훨씬 더 복잡하기 때문이다. 하지만 대부분의 실용적인 목적에는 충분하다.
예제 2: HTML 태그 제거
웹 스크래핑이나 데이터 정제 시 특정 HTML 태그를 제거해야 할 때가 많다.
-
<: 여는 꺽쇠 -
[^>]+:>를 제외한 모든 문자가 하나 이상 온다. (태그 이름과 속성) -
>: 닫는 꺽쇠
최종 패턴: <[^>]+>
이 패턴을 찾아 빈 문자열로 치환하면 <h1>Title</h1>은 “Title”만 남게 된다. 앞서 설명한 게으른 수량자를 사용한 <.+?> 패턴도 동일하게 동작할 수 있다.
예제 3: 날짜 형식 변환 (YYYY-MM-DD → MM/DD/YYYY)
문자열에 “2025-09-23”과 같은 날짜가 있을 때, “09/23/2025” 형식으로 바꾸고 싶다. 이때는 그룹과 역참조 기능이 매우 유용하다.
-
패턴:
(\d{4})-(\d{2})-(\d{2})-
(\d{4}): 숫자 4개를 첫 번째 그룹으로 묶는다. (연도) -
-: 하이픈 -
(\d{2}): 숫자 2개를 두 번째 그룹으로 묶는다. (월) -
-: 하이픈 -
(\d{2}): 숫자 2개를 세 번째 그룹으로 묶는다. (일)
-
-
치환:
$2/$3/$1(언어에 따라\2/\3/\1으로 표기하기도 한다)-
$2: 두 번째 그룹(월)을 가져온다. -
/: 슬래시 -
$3: 세 번째 그룹(일)을 가져온다. -
/: 슬래시 -
$1: 첫 번째 그룹(연도)을 가져온다.
-
이 과정을 거치면 “2025-09-23”은 “09/23/2025”로 깔끔하게 변환된다.
5. 정규표현식 심화 마스터를 위한 고급 기술
기본적인 사용법에 익숙해졌다면, 이제 당신의 텍스트 처리 능력을 한 차원 높여줄 고급 기술들을 만나볼 시간이다.
5.1. 역참조 (Backreferences)
역참조는 앞에서 **그룹 ()**으로 캡처한 문자열을 패턴 내에서 다시 참조하는 기능이다. \1, \2 와 같은 형태로 사용하며, 숫자는 캡처 그룹의 순서를 의미한다.
예시: 중복된 단어 찾기
\b(\w+)\s+\1\b
-
\b: 단어 경계 -
(\w+): 하나 이상의 단어 문자를 첫 번째 그룹으로 캡처한다. -
\s+: 하나 이상의 공백 문자 -
\1: 첫 번째 그룹에서 캡처한 내용과 똑같은 문자열을 의미한다. -
\b: 단어 경계
이 패턴은 “I love love this.” 라는 문장에서 “love love”를 찾아낼 수 있다. (\w+)가 첫 번째 “love”를 캡처하여 \1에 저장하고, 뒤이어 나오는 \1이 저장된 “love”와 일치하는지 확인하기 때문이다.
5.2. 전후방탐색 (Lookarounds)
전후방탐색은 특정 패턴이 일치하는지 ‘확인’만 하고 실제 일치 결과에는 포함시키지 않는 매우 강력한 기능이다. 이를 ‘너비가 0인 단언(Zero-width Assertions)‘이라고도 부른다. 즉, 텍스트를 소비(consume)하지 않고 조건만 검사한다.
긍정형 전방탐색 (Positive Lookahead): (?=...)
... 패턴이 바로 뒤에 와야 일치한다.
-
예시:
\d+(?=원)-
\d+: 하나 이상의 숫자 -
(?=원): 바로 뒤에 “원”이라는 글자가 와야 한다. -
이 패턴은 “1000원”이라는 텍스트에서 “1000”만 찾아낸다. “원”은 조건 검사에만 사용되고 결과에는 포함되지 않는다.
-
부정형 전방탐색 (Negative Lookahead): (?!...)
... 패턴이 바로 뒤에 오지 않아야 일치한다.
-
예시:
\d+(?!원)-
\d+: 하나 이상의 숫자 -
(?!원): 바로 뒤에 “원”이라는 글자가 오면 안 된다. -
“1000원”에서는 일치하지 않고, “1000달러”에서는 “1000”을 찾아낸다.
-
긍정형 후방탐색 (Positive Lookbehind): (?<=...)
... 패턴이 바로 앞에 와야 일치한다.
-
예시:
(?<=₩)\d+-
(?<=₩): 바로 앞에 ”₩” 문자가 있어야 한다. -
\d+: 하나 이상의 숫자 -
“₩5000”이라는 텍스트에서 “5000”을 찾아낸다. ”₩“는 결과에 포함되지 않는다.
-
부정형 후방탐색 (Negative Lookbehind): (?<!...)
... 패턴이 바로 앞에 오지 않아야 일치한다.
-
예시:
(?<!₩)\d+-
(?<!₩): 바로 앞에 ”₩” 문자가 없어야 한다. -
\d+: 하나 이상의 숫자 -
“₩5000”에서는 일치하지 않고, “가격: 5000”에서는 “5000”을 찾아낸다.
-
주의: 일부 정규표현식 엔진은 후방탐색에서 가변 길이의 패턴을 허용하지 않는 등 제약이 있을 수 있다.
결론 텍스트 세계를 탐험하는 항해술
정규표현식은 배우기 시작할 때는 외계어처럼 보일 수 있지만, 그 구조와 원리를 이해하고 나면 텍스트 데이터를 다루는 당신의 능력을 비약적으로 향상시키는 강력한 무기가 된다. 이 핸드북에서 다룬 개념들은 정규표현식의 광대한 세계를 탐험하기 위한 지도와 나침반이 되어줄 것이다.
단순한 문자열 검색에서 벗어나 데이터 정제, 로그 분석, 웹 스크래핑, 입력값 검증 등 다양한 영역에서 정규표현식을 자유자재로 활용해보자. 처음에는 온라인 정규표현식 테스터(Regex101, Regexr 등)와 같은 도구를 활용하여 다양한 패턴을 실험하고 결과를 즉시 확인하는 것이 큰 도움이 된다. 꾸준한 연습과 실험을 통해, 당신도 곧 텍스트라는 바다를 자유롭게 항해하는 숙련된 선장이 될 수 있을 것이다.