
정규표현식(RegExp) 실제: 개발자의 만능 텍스트 도구
정규표현식을 외계어처럼 느꼈는데, 실제로 자주 쓰는 패턴 20개만 익히니 텍스트 처리가 한 줄로 끝났다.

정규표현식을 외계어처럼 느꼈는데, 실제로 자주 쓰는 패턴 20개만 익히니 텍스트 처리가 한 줄로 끝났다.
버튼을 눌렀는데 부모 DIV까지 클릭되는 현상. 이벤트는 물방울처럼 위로 올라갑니다(Bubbling). 반대로 내려오는 캡처링(Capturing)도 있죠.

세 가지 AI 코딩 도구를 실제로 써보고 비교했다. 자동완성, 코드 생성, 리팩토링 각각 어디가 강한지 솔직한 후기.

React가 1년 만에 바뀌고, AI가 코딩을 해주는 세상. 개발자로서의 불안감(FOMO)을 이겨내고, 트렌드에 휩쓸리지 않으면서 단단한 엔지니어로 성장하는 현실적인 학습 전략과 JIT 학습법, 그리고 2025년 학습 로드맵을 공유합니다.

함수가 선언될 때의 렉시컬 환경(Lexical Environment)을 기억하는 현상. React Hooks의 원리이자 정보 은닉의 핵심 키.

코드에서 /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/ 같은 걸 보면 눈이 풀렸다. "이게 뭔 외계어야?" 싶어서 스택오버플로우에서 복붙하고 "일단 돌아가네" 하고 넘어갔다.
그런데 프로젝트에서 유저 입력 검증, CSV 파싱, 로그 필터링을 하다 보니 매번 복붙만 할 수는 없었다. 정규표현식 없이 텍스트 처리하려니까 for 문 중첩에 if 문 도배가 되더라. 50줄짜리 코드가 정규표현식 한 줄로 끝나는 걸 보고 "아, 이거 제대로 배워야겠다" 싶었다.
결론부터 말하면, 정규표현식 전체를 마스터할 필요는 없었다. 실제로 자주 쓰는 패턴 20개만 익히니까 웬만한 텍스트 처리는 다 해결됐다. 마치 요리할 때 칼질 20가지 기술 다 배우는 게 아니라, 채썰기·다지기·저미기만 익히면 되는 것처럼.
정규표현식(Regular Expression, RegExp)은 결국 "텍스트에서 패턴을 찾는 도구"다. "이메일 형식인지 확인", "전화번호 추출", "비밀번호 규칙 검사" 같은 게 모두 패턴 매칭이다.
처음엔 /^[a-zA-Z]/ 같은 게 암호문 같았는데, 하나씩 뜯어보니 그냥 "알파벳으로 시작하는 문자열"이라는 뜻이었다. 각 기호가 의미하는 바만 알면 레고 블록처럼 조합할 수 있더라.
정규표현식의 핵심 기호들은 이렇다.
1. 와일드카드와 앵커. 아무 문자나 하나 (공백 포함)* 0번 이상 반복+ 1번 이상 반복? 0번 또는 1번^ 문자열 시작코드에서 /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/ 같은 걸 보면 눈이 풀렸다. "이게 뭔 외계어야?" 싶어서 스택오버플로우에서 복붙하고 "일단 돌아가네" 하고 넘어갔다.
그런데 프로젝트에서 유저 입력 검증, CSV 파싱, 로그 필터링을 하다 보니 매번 복붙만 할 수는 없었다. 정규표현식 없이 텍스트 처리하려니까 for 문 중첩에 if 문 도배가 되더라. 50줄짜리 코드가 정규표현식 한 줄로 끝나는 걸 보고 "아, 이거 제대로 배워야겠다" 싶었다.
결론부터 말하면, 정규표현식 전체를 마스터할 필요는 없었다. 실제로 자주 쓰는 패턴 20개만 익히니까 웬만한 텍스트 처리는 다 해결됐다. 마치 요리할 때 칼질 20가지 기술 다 배우는 게 아니라, 채썰기·다지기·저미기만 익히면 되는 것처럼.
정규표현식(Regular Expression, RegExp)은 결국 "텍스트에서 패턴을 찾는 도구"다. "이메일 형식인지 확인", "전화번호 추출", "비밀번호 규칙 검사" 같은 게 모두 패턴 매칭이다.
처음엔 /^[a-zA-Z]/ 같은 게 암호문 같았는데, 하나씩 뜯어보니 그냥 "알파벳으로 시작하는 문자열"이라는 뜻이었다. 각 기호가 의미하는 바만 알면 레고 블록처럼 조합할 수 있더라.
정규표현식의 핵심 기호들은 이렇다.
1. 와일드카드와 앵커. 아무 문자나 하나 (공백 포함)* 0번 이상 반복+ 1번 이상 반복? 0번 또는 1번^ 문자열 시작[abc] a, b, c 중 하나[^abc] a, b, c가 아닌 것[a-z] 소문자 알파벳\d 숫자 (0-9)\w 단어 문자 (알파벳, 숫자, 언더스코어)\s 공백 문자\D 숫자가 아닌 것 (대문자는 부정)() 그룹화 및 캡처(?<name>) 이름 있는 그룹{3} 정확히 3번{3,} 3번 이상{3,5} 3~5번.* 탐욕적 (최대한 많이).*? 게으른 (최소한만)이걸 조합하면 복잡한 패턴도 만들 수 있다.
// 이메일 검증 (간단 버전)
const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
console.log(emailRegex.test('user@example.com')); // true
console.log(emailRegex.test('invalid-email')); // false
// 전화번호 추출 (한국 형식)
const phoneRegex = /010-\d{4}-\d{4}/g;
const text = '연락처: 010-1234-5678, 사무실: 010-9876-5432';
console.log(text.match(phoneRegex)); // ['010-1234-5678', '010-9876-5432']
// HTML 태그 제거
const htmlRegex = /<[^>]+>/g;
const html = '<p>Hello <strong>World</strong></p>';
console.log(html.replace(htmlRegex, '')); // "Hello World"
처음엔 "이걸 어떻게 외워?" 싶었는데, 자주 쓰다 보니 패턴이 보이더라. \d+는 "숫자 여러 개", \w+는 "단어", \s*는 "공백 있을 수도 없을 수도" 이런 식으로.
실제로 자주 쓰는 패턴들을 정리했다. 외울 필요 없고, 필요할 때 복붙하면 된다.
1. 이메일/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/
2. URL
/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b/
3. 한국 전화번호 (하이픈 있음)
/^01[016789]-\d{3,4}-\d{4}$/
4. 한국 전화번호 (하이픈 없음)
/^01[016789]\d{7,8}$/
5. 한글만
/^[가-힣]+$/
6. 영문+숫자만
/^[a-zA-Z0-9]+$/
7. 비밀번호 (8자 이상, 영문+숫자+특수문자)
/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/
8. 날짜 (YYYY-MM-DD)
/^\d{4}-\d{2}-\d{2}$/
9. 시간 (HH:MM)
/^([01]\d|2[0-3]):([0-5]\d)$/
10. 신용카드 (4자리씩 4그룹)
/^\d{4}-\d{4}-\d{4}-\d{4}$/
11. IPv4
/^(\d{1,3}\.){3}\d{1,3}$/
12. 16진수 컬러코드
/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
13. 주민등록번호 (앞자리만)
/^\d{6}$/
14. 양의 정수
/^\d+$/
15. 소수점 포함 숫자
/^\d+(\.\d+)?$/
16. 파일 확장자 추출
/\.([a-zA-Z0-9]+)$/
17. 공백 제거
/\s+/g
18. 연속 공백을 하나로
/ +/g
19. URL에서 도메인 추출
/^https?:\/\/([^\/]+)/
20. 마크다운 이미지 문법
/!\[([^\]]*)\]\(([^)]+)\)/g
JavaScript에서 정규표현식은 두 가지 방식으로 쓴다.
1. test() - 매칭 여부만 확인const regex = /\d{3}-\d{4}/;
console.log(regex.test('010-1234')); // true
console.log(regex.test('abc-defg')); // false
2. exec() - 첫 번째 매칭 정보
const regex = /(\d{3})-(\d{4})/;
const result = regex.exec('전화: 010-1234');
console.log(result[0]); // "010-1234" (전체)
console.log(result[1]); // "010" (첫 번째 그룹)
console.log(result[2]); // "1234" (두 번째 그룹)
3. String.match() - 모든 매칭 결과
const text = 'CSS: #FF5733, #C70039, #900C3F';
const colors = text.match(/#[A-Fa-f0-9]{6}/g);
console.log(colors); // ["#FF5733", "#C70039", "#900C3F"]
4. String.matchAll() - 그룹 포함 모든 매칭
const text = '가격: $100, $200, $300';
const regex = /\$(\d+)/g;
for (const match of text.matchAll(regex)) {
console.log(`전체: ${match[0]}, 숫자: ${match[1]}`);
}
// 전체: $100, 숫자: 100
// 전체: $200, 숫자: 200
// 전체: $300, 숫자: 300
const text = 'Hello World, Hello Universe';
console.log(text.replace(/Hello/g, 'Hi')); // "Hi World, Hi Universe"
// 그룹 활용
const html = '<p>Text</p>';
console.log(html.replace(/<([^>]+)>/g, '[$1]')); // "[p]Text[/p]"
// 콜백 함수
const caps = 'hello world'.replace(/\b\w/g, char => char.toUpperCase());
console.log(caps); // "Hello World"
2. split()
const csv = 'apple,banana, cherry, orange';
console.log(csv.split(/\s*,\s*/)); // ["apple", "banana", "cherry", "orange"]
3. search()
const text = 'The price is $99.99';
console.log(text.search(/\$\d+/)); // 13 (위치)
ES2018부터 (?<name>) 문법으로 그룹에 이름을 붙일 수 있다.
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2026-02-21'.match(dateRegex);
console.log(match.groups.year); // "2026"
console.log(match.groups.month); // "02"
console.log(match.groups.day); // "21"
// replace에서도 사용 가능
const formatted = '2026-02-21'.replace(
dateRegex,
'$<day>/$<month>/$<year>'
);
console.log(formatted); // "21/02/2026"
숫자 인덱스(match[1], match[2])보다 의미가 명확해서 코드 가독성이 훨씬 좋아진다.
실제로 정규표현식을 가장 많이 쓰는 곳이 폼 검증이다. Zod 같은 스키마 검증 라이브러리와 조합하면 깔끔하다.
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email(),
phone: z.string().regex(/^01[016789]-\d{3,4}-\d{4}$/, '전화번호 형식이 올바르지 않습니다'),
password: z.string().regex(
/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/,
'비밀번호는 8자 이상, 영문+숫자+특수문자 포함'
),
username: z.string().regex(/^[a-zA-Z0-9_]{3,20}$/, '3~20자 영문, 숫자, 언더스코어만 가능'),
});
// 사용
const result = userSchema.safeParse({
email: 'user@example.com',
phone: '010-1234-5678',
password: 'Pass123!',
username: 'my_username'
});
if (!result.success) {
console.log(result.error.issues);
}
React Hook Form과 조합하면 프론트엔드 검증이 한 번에 끝난다.
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
function SignupForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(userSchema)
});
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<input {...register('phone')} />
{errors.phone && <span>{errors.phone.message}</span>}
<button type="submit">가입</button>
</form>
);
}
정규표현식 작성할 때 가장 큰 문제는 "이게 제대로 작동하는지" 확인하기 어렵다는 것이다. 코드에 넣어보기 전에 온라인 도구로 테스트하면 시간이 엄청 절약된다.
regex101.com복잡한 패턴 만들 때는 이 도구들 없이는 못 하겠더라. 특히 그룹 캡처 확인할 때 필수다.
정규표현식은 잘못 쓰면 성능이 끔찍하게 나빠진다. 특히 "Catastrophic Backtracking" 문제는 CPU를 100% 먹는 괴물이 될 수 있다.
// 위험한 패턴 (절대 쓰지 말 것)
const badRegex = /(a+)+b/;
console.time('bad');
badRegex.test('aaaaaaaaaaaaaaaaaaaaaaaac'); // 엄청 느림
console.timeEnd('bad');
// 개선된 패턴
const goodRegex = /a+b/;
console.time('good');
goodRegex.test('aaaaaaaaaaaaaaaaaaaaaaaac'); // 빠름
console.timeEnd('good');
문제가 되는 패턴:
(a+)+ 중첩된 양화사(a*)* 반복된 반복(a|a)* 겹치는 선택지해결법:
실제로는 대부분 간단한 패턴만 써서 문제가 잘 안 생기지만, 복잡한 파싱 할 때는 주의해야 한다.
코드 편집기에서 정규표현식을 쓰면 리팩토링이 엄청 빨라진다.
1. Find & Replace (Cmd/Ctrl + H).* 아이콘)예: 모든 console.log를 주석 처리
찾기: (console\.log\(.+\))
바꾸기: // $1
예: 함수 이름 변경 (camelCase → snake_case)
찾기: function ([a-z]+)([A-Z][a-z]+)
바꾸기: function $1_$2
2. 여러 파일 검색 (Cmd/Ctrl + Shift + F)
^import .+ from ['"].+['"];?\s*$
3. Multi-cursor로 일괄 수정
이걸 알고 나서 "100개 파일에서 특정 패턴 바꾸기" 같은 게 10초 만에 끝난다.
정규표현식에서 "조건은 확인하지만 결과에는 포함 안 함" 같은 게 필요할 때가 있다. 이때 Lookahead/Lookbehind를 쓴다.
Lookahead (앞쪽 확인)(?=...) 긍정 전방탐색 (뒤에 ... 가 있어야 함)(?!...) 부정 전방탐색 (뒤에 ... 가 없어야 함)// 비밀번호: 최소 하나씩 영문, 숫자, 특수문자 포함
const password = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&]).{8,}$/;
console.log(password.test('Password123!')); // true
console.log(password.test('password')); // false (숫자, 특수문자 없음)
// 가격 추출 ($99.99에서 99.99만)
const price = /\$(?=\d+\.\d{2})/g;
console.log('$99.99 and $49.50'.replace(price, '')); // "99.99 and 49.50"
Lookbehind (뒤쪽 확인) - ES2018+
(?<=...) 긍정 후방탐색 (앞에 ... 가 있어야 함)(?<!...) 부정 후방탐색 (앞에 ... 가 없어야 함)// $99에서 99만 추출
const amount = /(?<=\$)\d+/g;
console.log('Price: $99'.match(amount)); // ["99"]
// "not a winner"가 아닌 "winner"만 찾기
const winner = /(?<!not a )winner/;
console.log(winner.test('winner')); // true
console.log(winner.test('not a winner')); // false
처음엔 "이게 언제 필요해?" 싶었는데, 비밀번호 검증이나 복잡한 파싱에서 엄청 유용하더라.
정규표현식을 완벽하게 마스터할 필요는 없다. 필요할 때 패턴 하나 찾아서 쓰고, 조금씩 익숙해지면 된다.
마치 칼질처럼, 처음엔 서툴러도 자주 쓰다 보면 손에 익는다. "이메일 검증", "전화번호 추출", "HTML 태그 제거" 같은 패턴 몇 개만 알아도 코드가 훨씬 간결해진다.
정규표현식이 외계어처럼 느껴졌던 때가 있었다. 하지만 이제는 "텍스트 처리할 일 있으면 일단 정규표현식"이라는 생각이 먼저 든다. 복붙용 패턴 20개 저장해두고, regex101.com 북마크 해두고, 필요할 때마다 조금씩 배워가면 된다.
결국 정규표현식은 도구다. 만능은 아니지만, 텍스트 처리만큼은 이것만 한 게 없다.