정규 표현식 (Regular Expressions)
1. 개요
정규 표현식(Regular Expressions, 줄여서 Regex 또는 Regexp)은 특정한 규칙을 가진 문자열의 집합을 표현하기 위해 사용하는 특수한 패턴의 문자열이자 형식 언어입니다. 텍스트 내에서 특정 패턴을 검색, 추출, 치환하거나 입력된 데이터가 정해진 형식에 부합하는지 검증하는 패턴 매칭(Pattern Matching)을 목적으로 합니다. 현대 컴퓨팅 환경에서 정규 표현식은 데이터 전처리, 로그 분석, 입력 폼 유효성 검사 등 문자열 처리가 필요한 거의 모든 프로그래밍 언어와 텍스트 에디터에서 표준적으로 사용되는 필수적인 도구입니다.
2. 기본 문법 및 메타 문자
정규 표현식은 일반 문자인 리터럴(Literal)과 특별한 의미를 가진 메타 문자(Meta Character)의 조합으로 구성됩니다. 리터럴은 문자 그대로의 일치를 의미하며, 메타 문자는 위치, 수량, 종류 등의 규칙을 정의합니다.
2.1 기본 메타 문자 요약
| 기호 |
기능 |
설명 |
예시 |
결과 |
. |
와일드카드 |
줄 바꿈 문자를 제외한 모든 단일 문자 일치 |
a.b |
aab, acb, a1b |
^ |
시작 앵커 |
문자열의 시작 지점과 일치 |
^Hello |
"Hello world" (일치) |
$` | 종료 앵커 | 문자열의 끝 지점과 일치 | `world$ |
"Hello world" (일치) |
|
|
|
* |
0회 이상 반복 |
앞의 문자가 0번 이상 반복됨 |
ab* |
a, ab, abb, abbb |
+ |
1회 이상 반복 |
앞의 문자가 1번 이상 반복됨 |
ab+ |
ab, abb, abbb (a는 제외) |
? |
0회 또는 1회 |
앞의 문자가 존재하거나 존재하지 않음 |
ab? |
a, ab |
\ |
이스케이프 |
메타 문자를 리터럴로 처리하거나, 일반 문자를 메타 문자로 처리 |
\. / \d |
마침표(.) 문자 / 숫자 패턴 |
\d |
숫자 |
모든 숫자와 일치 ([0-9]와 동일) |
\d\d |
12, 50, 99 |
\w |
단어 문자 |
알파벳, 숫자, 언더바(_)와 일치 |
\w+ |
hello, user_1, 123 |
\s |
공백 문자 |
스페이스, 탭, 줄 바꿈 등 공백과 일치 |
\s+ |
" ", " ", "\t" |
2.2 앵커와 경계 (Anchors & Boundaries)
앵커는 특정 문자와 일치하는 것이 아니라, 문자열 내의 특정 위치를 지정합니다.
- ^ (Caret): 문자열의 시작을 의미합니다. 멀티라인 모드에서는 각 줄의 시작을 의미합니다.
- $ (Dollar): 문자열의 끝을 의미합니다. 멀티라인 모드에서는 각 줄의 끝을 의미합니다.
- \b (Word Boundary): 단어 경계를 의미합니다. 문자와 비문자(공백, 특수문자 등) 사이의 지점을 찾습니다. 예를 들어 \bcat\b는 "category" 내의 cat은 제외하고 독립된 단어 "cat"만 찾습니다.
- \B (Non-word Boundary): 단어 경계가 아닌 지점을 의미합니다. 즉, 단어 내부의 위치를 찾을 때 사용합니다.
3. 고급 패턴 매칭
단순한 메타 문자를 넘어 복잡한 조건을 설정하기 위해 문자 클래스와 수량자, 그룹화 기능을 사용합니다.
3.1 문자 클래스와 수량자
- 문자 클래스
[]: 괄호 안에 나열된 문자 중 하나와 일치합니다.
[aeiou]: 모든 모음 중 하나.
[0-9]: 모든 숫자 중 하나 (\d와 동일).
[a-zA-Z]: 모든 영문 대소문자 중 하나.
[^0-9]: 숫자가 아닌 모든 문자 (부정 클래스).
- 수량자
{n,m}: 반복 횟수를 명시적으로 지정합니다.
{n}: 정확히 $n$번 반복.
{n,}: 최소 $n$번 이상 반복.
{n,m}: 최소 $n$번에서 최대 $m$번 반복.
3.2 그룹화 및 논리 연산
- 그룹화 및 캡처링
(): 패턴을 하나로 묶어 수량자를 적용하거나, 매칭된 부분을 나중에 다시 참조(Capturing)할 때 사용합니다.
- 논리적 선택
| (OR): A|B 형태로 작성하며, A 또는 B 중 하나와 일치하면 성공으로 간주합니다.
4. 전방/후방 탐색 (Lookaround)
전방/후방 탐색은 특정 패턴이 앞이나 뒤에 있는지 확인하지만, 실제 매칭 결과(결과 문자열)에는 포함시키지 않는 '제로 너비(Zero-width) 단언'입니다.
| 종류 |
문법 |
설명 |
예시 |
결과 |
| 긍정 전방 탐색 |
(?=...) |
뒤에 특정 패턴이 오는 경우만 일치 |
\d+(?=원) |
"1000원" $\rightarrow$ 매칭 결과: "1000" |
| 부정 전방 탐색 |
(?!...) |
뒤에 특정 패턴이 오지 않는 경우만 일치 |
\d+(?!원) |
"1000달러" $\rightarrow$ 매칭 결과: "1000" |
| 긍정 후방 탐색 |
(?<=...) |
앞에 특정 패턴이 있는 경우만 일치 |
`(?<=$)\d+` | "$100" $\rightarrow$ 매칭 결과: "100" |
|
| 부정 후방 탐색 |
(?<!...) |
앞에 특정 패턴이 없는 경우만 일치 |
`(?<!$)\d+` | "100원" $\rightarrow$ 매칭 결과: "100" |
|
5. 주요 함수 및 활용 사례
대부분의 언어는 정규 표현식을 처리하는 내장 라이브러리를 제공합니다.
5.1 언어별 구현 예시
Python (re 모듈)
import re
text = "Contact: 010-1234-5678, Email: user@example.com"
# 전화번호 추출
phone = re.search(r'\d{3}-\d{3,4}-\d{4}', text).group()
# 이메일 치환
masked = re.sub(r'[\w\.-]+@[\w\.-]+', '[MASKED]', text)
print(phone) # 010-1234-5678
print(masked) # Contact: 010-1234-5678, Email: [MASKED]
JavaScript (RegExp 객체)
const text = "Apple, Banana, Orange";
const regex = /Apple|Banana|Orange/g;
const matches = text.match(regex); // ['Apple', 'Banana', 'Orange']
const splitText = text.split(/, /); // ['Apple', 'Banana', 'Orange']
5.2 자주 쓰이는 정규식 패턴 모음집
| 대상 |
패턴 |
설명 |
| 이메일 |
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ |
표준 이메일 형식 검증 |
| 비밀번호 |
^(?=.*[a-zA-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$ |
영문, 숫자, 특수문자 포함 8자 이상 |
| 전화번호 |
^010-\d{3,4}-\d{4}$ |
한국 휴대전화 번호 형식 |
| 숫자만 |
^[0-9]+$ |
문자열 전체가 숫자로만 구성되었는지 확인 |
| 날짜 (YYYY-MM-DD) |
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$ |
기본 날짜 형식 검증 |
| 공백 제거 |
\s+ |
하나 이상의 연속된 공백 문자 |
6. 성능 최적화 및 주의사항
6.1 탐욕적(Greedy) vs 게으른(Lazy) 매칭
수량자는 기본적으로 가능한 한 가장 긴 문자열을 매칭하려 하며, 이를 '탐욕적'이라고 합니다. 반대로 가장 짧은 일치 항목을 찾는 것을 '게으른' 매칭이라고 합니다.
- 탐욕적 매칭 (Greedy): 기본 설정으로, 일치하는 가장 긴 문자열을 찾습니다.
- 예:
<.*> 패턴으로 <div>Hello</div>를 매칭하면 <div>Hello</div> 전체가 선택됩니다.
- 게으른 매칭 (Lazy/Non-greedy): 수량자 뒤에
?를 붙여 일치하는 가장 짧은 문자열을 찾습니다.
- 예:
<.*?> 패턴으로 <div>Hello</div>를 매칭하면 <div>만 선택됩니다.
6.2 파멸적 되돌아오기 (Catastrophic Backtracking)
중첩된 수량자(예: (a+)+$)를 사용하여 매우 긴 문자열을 처리할 때, 매칭에 실패하면 엔진이 가능한 모든 조합을 시도하며 연산량이 기하급수적으로 증가하는 현상입니다. 이는 CPU 점유율을 100%로 만들어 서비스 거부 공격(ReDoS)의 원인이 될 수 있습니다.
- 방지법:
1. 중첩된 수량자 사용을 지양합니다.
2. 가능한 구체적인 문자 클래스를 사용하여 범위를 제한합니다.
3. 소유격 수량자(Possessive Quantifiers)나 원자적 그룹(Atomic Grouping)을 지원하는 엔진을 사용합니다.
7. 관련 도구 및 학습 리소스
정규 표현식은 복잡도가 높으므로 시각적 도구를 통해 검증하는 것이 권장됩니다.
- Regex101: 실시간 매칭 확인, 상세 설명 및 라이브러리별 문법 지원 (가장 널리 사용됨).
- RegExr: 인터랙티브한 테스트 환경과 유용한 치트시트 제공.
- 공식 문서: MDN Web Docs (JavaScript), Python re module documentation.
# 정규 표현식 (Regular Expressions)
## 1. 개요
정규 표현식(Regular Expressions, 줄여서 Regex 또는 Regexp)은 특정한 규칙을 가진 문자열의 집합을 표현하기 위해 사용하는 **특수한 패턴의 문자열**이자 형식 언어입니다. 텍스트 내에서 특정 패턴을 검색, 추출, 치환하거나 입력된 데이터가 정해진 형식에 부합하는지 검증하는 패턴 매칭(Pattern Matching)을 목적으로 합니다. 현대 컴퓨팅 환경에서 정규 표현식은 데이터 전처리, 로그 분석, 입력 폼 유효성 검사 등 문자열 처리가 필요한 거의 모든 프로그래밍 언어와 텍스트 에디터에서 표준적으로 사용되는 필수적인 도구입니다.
## 2. 기본 문법 및 메타 문자
정규 표현식은 일반 문자인 **리터럴(Literal)**과 특별한 의미를 가진 **메타 문자(Meta Character)**의 조합으로 구성됩니다. 리터럴은 문자 그대로의 일치를 의미하며, 메타 문자는 위치, 수량, 종류 등의 규칙을 정의합니다.
### 2.1 기본 메타 문자 요약
| 기호 | 기능 | 설명 | 예시 | 결과 |
| :--- | :--- | :--- | :--- | :--- |
| `.` | 와일드카드 | 줄 바꿈 문자를 제외한 모든 단일 문자 일치 | `a.b` | aab, acb, a1b |
| `^` | 시작 앵커 | 문자열의 시작 지점과 일치 | `^Hello` | "Hello world" (일치) |
| `$` | 종료 앵커 | 문자열의 끝 지점과 일치 | `world$` | "Hello world" (일치) |
| `*` | 0회 이상 반복 | 앞의 문자가 0번 이상 반복됨 | `ab*` | a, ab, abb, abbb |
| `+` | 1회 이상 반복 | 앞의 문자가 1번 이상 반복됨 | `ab+` | ab, abb, abbb (a는 제외) |
| `?` | 0회 또는 1회 | 앞의 문자가 존재하거나 존재하지 않음 | `ab?` | a, ab |
| `\` | 이스케이프 | 메타 문자를 리터럴로 처리하거나, 일반 문자를 메타 문자로 처리 | `\.` / `\d` | 마침표(.) 문자 / 숫자 패턴 |
| `\d` | 숫자 | 모든 숫자와 일치 (`[0-9]`와 동일) | `\d\d` | 12, 50, 99 |
| `\w` | 단어 문자 | 알파벳, 숫자, 언더바(_)와 일치 | `\w+` | hello, user_1, 123 |
| `\s` | 공백 문자 | 스페이스, 탭, 줄 바꿈 등 공백과 일치 | `\s+` | " ", " ", "\t" |
### 2.2 앵커와 경계 (Anchors & Boundaries)
앵커는 특정 문자와 일치하는 것이 아니라, 문자열 내의 **특정 위치**를 지정합니다.
- **`^` (Caret)**: 문자열의 시작을 의미합니다. 멀티라인 모드에서는 각 줄의 시작을 의미합니다.
- **`$` (Dollar)**: 문자열의 끝을 의미합니다. 멀티라인 모드에서는 각 줄의 끝을 의미합니다.
- **`\b` (Word Boundary)**: 단어 경계를 의미합니다. 문자와 비문자(공백, 특수문자 등) 사이의 지점을 찾습니다. 예를 들어 `\bcat\b`는 "category" 내의 cat은 제외하고 독립된 단어 "cat"만 찾습니다.
- **`\B` (Non-word Boundary)**: 단어 경계가 아닌 지점을 의미합니다. 즉, 단어 내부의 위치를 찾을 때 사용합니다.
## 3. 고급 패턴 매칭
단순한 메타 문자를 넘어 복잡한 조건을 설정하기 위해 문자 클래스와 수량자, 그룹화 기능을 사용합니다.
### 3.1 문자 클래스와 수량자
- **문자 클래스 `[]`**: 괄호 안에 나열된 문자 중 하나와 일치합니다.
- `[aeiou]`: 모든 모음 중 하나.
- `[0-9]`: 모든 숫자 중 하나 (`\d`와 동일).
- `[a-zA-Z]`: 모든 영문 대소문자 중 하나.
- `[^0-9]`: 숫자가 아닌 모든 문자 (부정 클래스).
- **수량자 `{n,m}`**: 반복 횟수를 명시적으로 지정합니다.
- `{n}`: 정확히 $n$번 반복.
- `{n,}`: 최소 $n$번 이상 반복.
- `{n,m}`: 최소 $n$번에서 최대 $m$번 반복.
### 3.2 그룹화 및 논리 연산
- **그룹화 및 캡처링 `()`**: 패턴을 하나로 묶어 수량자를 적용하거나, 매칭된 부분을 나중에 다시 참조(Capturing)할 때 사용합니다.
- **논리적 선택 `|` (OR)**: `A|B` 형태로 작성하며, A 또는 B 중 하나와 일치하면 성공으로 간주합니다.
## 4. 전방/후방 탐색 (Lookaround)
전방/후방 탐색은 특정 패턴이 앞이나 뒤에 있는지 확인하지만, **실제 매칭 결과(결과 문자열)에는 포함시키지 않는** '제로 너비(Zero-width) 단언'입니다.
| 종류 | 문법 | 설명 | 예시 | 결과 |
| :--- | :--- | :--- | :--- | :--- |
| **긍정 전방 탐색** | `(?=...)` | 뒤에 특정 패턴이 오는 경우만 일치 | `\d+(?=원)` | "1000원" $\rightarrow$ 매칭 결과: "1000" |
| **부정 전방 탐색** | `(?!...)` | 뒤에 특정 패턴이 오지 않는 경우만 일치 | `\d+(?!원)` | "1000달러" $\rightarrow$ 매칭 결과: "1000" |
| **긍정 후방 탐색** | `(?<=...)` | 앞에 특정 패턴이 있는 경우만 일치 | `(?<=\$)\d+` | "$100" $\rightarrow$ 매칭 결과: "100" |
| **부정 후방 탐색** | `(?<!...)` | 앞에 특정 패턴이 없는 경우만 일치 | `(?<!\$)\d+` | "100원" $\rightarrow$ 매칭 결과: "100" |
## 5. 주요 함수 및 활용 사례
대부분의 언어는 정규 표현식을 처리하는 내장 라이브러리를 제공합니다.
### 5.1 언어별 구현 예시
**Python (`re` 모듈)**
```python
import re
text = "Contact: 010-1234-5678, Email: user@example.com"
# 전화번호 추출
phone = re.search(r'\d{3}-\d{3,4}-\d{4}', text).group()
# 이메일 치환
masked = re.sub(r'[\w\.-]+@[\w\.-]+', '[MASKED]', text)
print(phone) # 010-1234-5678
print(masked) # Contact: 010-1234-5678, Email: [MASKED]
```
**JavaScript (`RegExp` 객체)**
```javascript
const text = "Apple, Banana, Orange";
const regex = /Apple|Banana|Orange/g;
const matches = text.match(regex); // ['Apple', 'Banana', 'Orange']
const splitText = text.split(/, /); // ['Apple', 'Banana', 'Orange']
```
### 5.2 자주 쓰이는 정규식 패턴 모음집
| 대상 | 패턴 | 설명 |
| :--- | :--- | :--- |
| **이메일** | `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` | 표준 이메일 형식 검증 |
| **비밀번호** | `^(?=.*[a-zA-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$` | 영문, 숫자, 특수문자 포함 8자 이상 |
| **전화번호** | `^010-\d{3,4}-\d{4}$` | 한국 휴대전화 번호 형식 |
| **숫자만** | `^[0-9]+$` | 문자열 전체가 숫자로만 구성되었는지 확인 |
| **날짜 (YYYY-MM-DD)** | `^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$` | 기본 날짜 형식 검증 |
| **공백 제거** | `\s+` | 하나 이상의 연속된 공백 문자 |
## 6. 성능 최적화 및 주의사항
### 6.1 탐욕적(Greedy) vs 게으른(Lazy) 매칭
수량자는 기본적으로 가능한 한 가장 긴 문자열을 매칭하려 하며, 이를 '탐욕적'이라고 합니다. 반대로 가장 짧은 일치 항목을 찾는 것을 '게으른' 매칭이라고 합니다.
- **탐욕적 매칭 (Greedy)**: 기본 설정으로, 일치하는 가장 긴 문자열을 찾습니다.
- 예: `<.*>` 패턴으로 `<div>Hello</div>`를 매칭하면 `<div>Hello</div>` 전체가 선택됩니다.
- **게으른 매칭 (Lazy/Non-greedy)**: 수량자 뒤에 `?`를 붙여 일치하는 가장 짧은 문자열을 찾습니다.
- 예: `<.*?>` 패턴으로 `<div>Hello</div>`를 매칭하면 `<div>`만 선택됩니다.
### 6.2 파멸적 되돌아오기 (Catastrophic Backtracking)
중첩된 수량자(예: `(a+)+$`)를 사용하여 매우 긴 문자열을 처리할 때, 매칭에 실패하면 엔진이 가능한 모든 조합을 시도하며 연산량이 기하급수적으로 증가하는 현상입니다. 이는 CPU 점유율을 100%로 만들어 서비스 거부 공격(ReDoS)의 원인이 될 수 있습니다.
- **방지법**:
1. 중첩된 수량자 사용을 지양합니다.
2. 가능한 구체적인 문자 클래스를 사용하여 범위를 제한합니다.
3. 소유격 수량자(Possessive Quantifiers)나 원자적 그룹(Atomic Grouping)을 지원하는 엔진을 사용합니다.
## 7. 관련 도구 및 학습 리소스
정규 표현식은 복잡도가 높으므로 시각적 도구를 통해 검증하는 것이 권장됩니다.
- **Regex101**: 실시간 매칭 확인, 상세 설명 및 라이브러리별 문법 지원 (가장 널리 사용됨).
- **RegExr**: 인터랙티브한 테스트 환경과 유용한 치트시트 제공.
- **공식 문서**: MDN Web Docs (JavaScript), Python `re` module documentation.