Regex Basics
re.sub 한 줄에서 시작하는 정규표현식 기초
- Haram Lee
- 2026-10-06
- memo
코드 리뷰에서 re.sub 한 줄을 만나 정규표현식을 다시 정리했다. 정규표현식은 파이썬 re, BigQuery의 REGEXP_* 함수, 로그 검색, grep에서 거의 같은 문법으로 쓰인다. 한 번 익혀 두면 여러 곳에서 쓸 수 있다.
출발점: 값을 규칙에 맞게 고치는 한 줄
import re
safe = re.sub(r'[^A-Za-z0-9_.-]', '_', name)[:63].strip('_.-')쿠버네티스 라벨 값에는 63자 이하여야 하고, 영문자, 숫자, _, ., -만 쓸 수 있으며, 양 끝은 영숫자여야 한다는 규칙이 있다. 이 코드는 이런 규칙이 있는 곳에 임의의 이름을 넣기 전에 쓴다.
re.sub(패턴, 바꿀 문자열, 원본)은 원본에서 패턴에 맞는 부분을 모두 바꾼다.[^A-Za-z0-9_.-]는 “영문자, 숫자,_,.,-가 아닌 문자 하나"라서 허용되지 않는 문자가 전부_로 바뀐다.[:63]은 정규표현식이 아니라 문자열 자르기다..strip('_.-')도 정규표현식이 아니다. 양 끝의_,.,-를 떼어 낸다.
'my report/2026' → 'my_report_2026'
'-draft.' → 'draft'
'a' * 70 → 'a' * 63정규표현식이 하는 일은 1번뿐이다. 정규표현식을 읽을 때는 어디까지가 패턴인지부터 구분하면 덜 헷갈린다.
패턴 문법
문자 하나 고르기
| 패턴 | 뜻 |
|---|---|
a | 글자 a 그대로 |
. | 줄바꿈을 뺀 아무 문자 하나 |
[abc] | 대괄호 안 문자 중 하나 |
[a-z] | 범위 안 문자 하나 |
[^abc] | 대괄호 안 문자가 아닌 것 하나 |
\d | 숫자 하나 |
\w | 영문자, 숫자, _ 하나 |
\s | 공백 문자 하나 (스페이스, 탭, 줄바꿈) |
\D, \W, \S | 각각 위의 반대 |
^는 대괄호 맨 앞에서만 “아닌 것"이다. 대괄호 밖에서는 “문자열 시작"이다.- 대괄호 안에서
-는 범위를 만든다. 글자-를 넣으려면 맨 끝에 둔다. .,*,(같은 특수 문자를 글자로 찾으려면 앞에\를 붙인다(\.). 대괄호 안의.은 특수 문자가 아니어서\가 필요 없다.
반복
| 패턴 | 뜻 |
|---|---|
* | 0번 이상 |
+ | 1번 이상 |
? | 0번 또는 1번 |
{3} | 정확히 3번 |
{2,5} | 2번에서 5번 |
반복 기호는 바로 앞 한 덩어리에만 붙는다. ab+는 a 뒤에 b가 1번 이상이고, (ab)+는 ab가 1번 이상이다.
위치
| 패턴 | 뜻 |
|---|---|
^ | 문자열 시작 |
$ | 문자열 끝 |
\b | 단어 경계 |
묶기와 고르기
| 패턴 | 뜻 |
|---|---|
(...) | 묶고, 맞은 부분을 따로 꺼낼 수 있게 저장 |
(?P<name>...) | 저장한 부분에 이름 붙이기 (파이썬 문법) |
(?:...) | 묶기만 하고 저장하지 않음 |
a|b | a 또는 b |
파이썬 re 함수
| 함수 | 하는 일 | 결과 |
|---|---|---|
re.search(p, s) | 어디서든 처음 맞는 곳 하나 | Match 또는 None |
re.match(p, s) | 맨 앞에서만 확인 | Match 또는 None |
re.fullmatch(p, s) | 문자열 전체가 맞는지 | Match 또는 None |
re.findall(p, s) | 맞는 곳 전부 | 문자열 목록 (묶음이 있으면 묶음 내용) |
re.sub(p, r, s) | 맞는 곳 전부 바꾸기 | 새 문자열 |
re.split(p, s) | 패턴을 구분자로 나누기 | 문자열 목록 |
re.compile(p) | 패턴을 미리 만들어 재사용 | 패턴 객체 |
match와 search를 자주 헷갈린다.
re.match(r'\d+', 'abc123') # None (맨 앞이 숫자가 아님)
re.search(r'\d+', 'abc123').group() # '123'패턴 앞의 r은 raw string 표시다. r 없이 '\b'를 쓰면 파이썬이 먼저 백스페이스 문자로 해석한다. 정규표현식은 항상 r'...'로 쓴다.
바로 쓸 수 있는 예
로그 줄에서 요청 id 꺼내기. 묶음 하나를 쓰고 group(1)으로 꺼낸다.
line = '2026-10-18 09:12:03 ERROR request_id=req-7f3a9c failed after 3 retries'
re.search(r'request_id=(\S+)', line).group(1)
# 'req-7f3a9c'여러 id를 한꺼번에 꺼내기.
re.findall(r'app-(\d{8}-\d{4})', 'started app-20261018-0001, retry app-20261018-0002')
# ['20261018-0001', '20261018-0002']이름 붙은 묶음으로 날짜와 시각 꺼내기.
m = re.search(r'(?P<date>\d{4}-\d{2}-\d{2}) (?P<hour>\d{2}):', line)
m.group('date'), m['hour']
# ('2026-10-18', '09')값을 고치지 않고 규칙에 맞는지만 검사하기. 첫 글자는 영숫자, 가운데는 허용 문자 61자 이하, 끝 글자는 영숫자여야 한다. 한 글자짜리 값도 통과하도록 가운데와 끝을 ?로 묶었다.
NAME = r'[A-Za-z0-9]([A-Za-z0-9_.-]{0,61}[A-Za-z0-9])?'
bool(re.fullmatch(NAME, 'my_report_2026')) # True
bool(re.fullmatch(NAME, '-bad')) # False
bool(re.fullmatch(NAME, 'x')) # TruecamelCase를 snake_case로 바꾸기. 바꿀 문자열의 \1, \2는 첫째, 둘째 묶음에 맞은 내용이다.
re.sub(r'([a-z])([A-Z])', r'\1_\2', 'createdAt').lower()
# 'created_at'여러 종류의 구분자로 나누기.
re.split(r'[,\s]+', 'a, b c,d')
# ['a', 'b', 'c', 'd']자주 하는 실수
욕심 많은 반복. *와 +는 가능한 한 길게 맞춘다. 뒤에 ?를 붙이면 가능한 한 짧게 맞춘다.
re.sub(r'<.+>', '', '<a>x<b>y') # 'y' (<a>x<b> 전체가 지워짐)
re.sub(r'<.+?>', '', '<a>x<b>y') # 'xy' (<a>, <b>만 지워짐).을 글자로 착각하기. .은 아무 문자 하나다.
bool(re.search(r'example.com', 'exampleXcom')) # True
bool(re.search(r'example\.com', 'exampleXcom')) # False부분 일치를 전체 일치로 착각하기. re.search(r'\d+', 'abc123')이 맞았다고 값이 숫자로만 이뤄진 것은 아니다. 검증은 fullmatch로 한다.
다른 도구에서 쓸 때
뼈대(문자 고르기, 반복, 위치, 묶기)는 어디서나 같다. 차이는 고급 기능에 있다.
| 도구 | 엔진 | 주의할 점 |
|---|---|---|
파이썬 re | 자체 엔진 | 기능이 가장 넓다 |
BigQuery REGEXP_* | RE2 | lookahead 같은 앞뒤 살피기와 \1 역참조가 없다 |
Grafana Loki LogQL |~ | RE2 | BigQuery와 같다. 라벨 매칭 =~는 값 전체와 맞아야 한다 |
grep | 기본, -E, -P | 기본 모드에서는 +, ?, |, ()에 \가 필요하다. -E를 쓰면 파이썬과 비슷하다 |
(?i)는 대소문자를 구분하지 않는다는 표시로, RE2와 파이썬 모두에서 쓸 수 있다. 파이썬의 re.IGNORECASE 플래그와 같다.
re.findall(r'(?i)error|timeout', 'ERROR x Timeout y')
# ['ERROR', 'Timeout']연습할 곳
- 파이썬 셸에서
import re로 바로 해 본다. - regex101.com에 패턴과 문자열을 넣으면 각 부분이 무엇과 맞았는지 색으로 보여 준다. 엔진을 Python으로 고를 수 있다.