문자열을 다루는 마법의 주문 - 정규표현식(Regex)
프로그래밍을 하다 보면 "이메일 형식이 맞는지 확인해 줘", "전화번호에서 숫자만 쏙쏙 뽑아줘", "사용자가 입력한 글에서 해시태그(#)만 전부 찾아내 줘" 같은 까다로운 요구사항을 자주 마주하게 됩니다.
이걸 일반적인 if문이나 문자열 함수로만 짜려면 코드가 수십 줄로 길어지지만, 정규표현식(Regular Expression, 줄여서 Regex)을 사용하면 단 한 줄의 '마법의 주문'으로 깔끔하게 해결할 수 있습니다. 처음엔 외계어처럼 보이지만, 원리만 알면 이보다 더 강력한 무기가 없습니다.
1. PHP의 정규표현식 함수들
PHP에서는 Perl 언어 방식의 정규표현식(PCRE)을 표준으로 사용하며, 함수 이름이 모두 preg_로 시작합니다. 실무에서 쓰는 함수는 딱 4가지뿐입니다.
| 함수 | 언제 쓰나요? |
|---|---|
preg_match() | 이 문자열이 내가 원하는 패턴에 맞는지(True/False) 검사할 때 |
preg_match_all() | 문자열 안에서 패턴에 맞는 글자를 전부 다 긁어모을 때 |
preg_replace() | 패턴에 맞는 부분을 찾아서 다른 글자로 싹 바꿔치기 할 때 |
preg_split() | 패턴을 기준으로 문자열을 토막 내어 배열로 만들 때 |
2. 암호 해독하기: 패턴 기본 문법
정규표현식은 항상 슬래시(/)로 시작해서 슬래시로 끝납니다. (예: /패턴/)
① 기호의 의미 (문자 클래스)
| 패턴 | 의미 |
|---|---|
. | 줄바꿈을 제외한 아무 글자 딱 1개 |
\d | 숫자 (0~9) |
\D | 숫자가 아닌 것 |
\w | 단어를 구성하는 글자 (알파벳, 숫자, 밑줄 _) |
\s | 띄어쓰기, 탭, 줄바꿈 등 공백 |
[abc] | a, b, c 중 아무거나 1개 |
[^abc] | a, b, c를 제외한 아무거나 1개 |
[a-z] | 소문자 a부터 z 사이의 알파벳 |
[가-힣] | 한글 전부 |
② 몇 번이나 나오나요? (수량자)
| 패턴 | 의미 |
|---|---|
* | 0번 이상 (없어도 되고 많아도 됨) |
+ | 1번 이상 (무조건 하나는 있어야 됨) |
? | 0번 또는 1번 (있거나 없거나) |
{n} | 정확히 n번 반복됨 |
{n,m} | n번 이상, m번 이하 반복됨 |
③ 위치와 옵션 (앵커와 플래그)
^: 문자열의 시작을 의미합니다.$: 문자열의 끝을 의미합니다./패턴/i: 플래그i를 붙이면 대소문자를 구별하지 않습니다./패턴/u: 플래그u를 붙이면 한글 같은 다국어(UTF-8)를 정확히 인식합니다.
3. 실전 예제: 하나씩 뜯어보기
preg_match - 패턴 검사와 추출
<?php
$phone = '010-1234-5678';
// 해석: 시작(^)이 010,011,016 등이고, 하이픈(-)이 올수도(?)있고, 숫자가 3~4자리 오고...
if (preg_match('/^01[016789]-?\d{3,4}-?\d{4}$/', $phone)) {
echo '올바른 한국 휴대폰 번호입니다.<br>';
}
// 괄호()를 치면, 매칭된 부분만 따로 변수에 쏙쏙 뽑아낼 수 있습니다. (캡처 그룹)
$date = '2026-04-19';
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $date, $matches)) {
echo "년: {$matches[1]}, 월: {$matches[2]}, 일: {$matches[3]}<br>";
}
(참고: 단순 이메일이나 URL 검사는 정규표현식을 직접 짜기보다 PHP 내장 함수인 filter_var($email, FILTER_VALIDATE_EMAIL)를 쓰는 것이 훨씬 정신 건강에 좋습니다.)
preg_match_all - 싹 다 긁어오기
본문에서 해시태그나 이미지 주소만 모두 추출해야 할 때 아주 유용합니다.
<?php
$text = '오늘 #날씨 정말 좋다! #산책 #기분최고';
// 해석: '#' 뒤에 단어(\w)가 하나 이상(+) 오는 것을 모두 찾아라. (한글 인식을 위해 /u 필수)
preg_match_all('/#(\w+)/u', $text, $matches);
print_r($matches[1]);
// 결과: Array ( [0] => 날씨 [1] => 산책 [2] => 기분최고 )
preg_replace - 찾아 바꾸기
사용자가 멋대로 입력한 데이터를 깔끔하게 정제할 때 최고의 도구입니다.
<?php
// 1. 실수로 연속해서 친 공백 무리(\s+)를 단 하나의 진짜 공백(' ')으로 바꿔라!
$messy = '안녕하세요 PHP 세계';
echo preg_replace('/\s+/', ' ', $messy) . "<br>";
// 출력: 안녕하세요 PHP 세계
// 2. 숫자가 아닌 것(\D)은 싹 다 지워라! (빈 문자열로 교체)
$dirtyPhone = '전화: 010-1234-5678 입니다.';
echo preg_replace('/\D/', '', $dirtyPhone) . "<br>";
// 출력: 01012345678
// 3. $1, $2를 이용해 원래 데이터의 순서를 재조립하기
$phone = '01012345678';
echo preg_replace('/^(\d{3})(\d{4})(\d{4})$/', '$1-$2-$3', $phone);
// 출력: 010-1234-5678
4. 실무 필수: 비밀번호 강력도 검사기
회원가입을 받을 때 "비밀번호는 영문 대/소문자, 숫자, 특수문자를 모두 포함해야 합니다"라는 깐깐한 규칙, 다들 한 번쯤 짜증 내며 입력해 보셨죠? 그 규칙을 만드는 코드가 바로 이것입니다.
<?php
declare(strict_types=1);
function validatePassword(string $password): array {
$errors = [];
if (mb_strlen($password) < 8) {
$errors[] = '8자 이상이어야 합니다.';
}
if (!preg_match('/[A-Z]/', $password)) {
$errors[] = '대문자를 최소 1개 포함해야 합니다.';
}
if (!preg_match('/[a-z]/', $password)) {
$errors[] = '소문자를 최소 1개 포함해야 합니다.';
}
if (!preg_match('/\d/', $password)) {
$errors[] = '숫자를 최소 1개 포함해야 합니다.';
}
if (!preg_match('/[!@#$%^&*]/', $password)) {
$errors[] = '특수문자를 최소 1개 포함해야 합니다.';
}
return $errors;
}
$errors = validatePassword('weakpass');
if (empty($errors)) {
echo '아주 훌륭하고 강력한 비밀번호입니다.';
} else {
foreach ($errors as $e) echo "- " . $e . "<br>";
}
// 출력:
// - 대문자를 최소 1개 포함해야 합니다.
// - 숫자를 최소 1개 포함해야 합니다.
// - 특수문자를 최소 1개 포함해야 합니다.
마치며
정규표현식은 너무 길고 복잡하게 짜면 나중에 짠 본인조차 해독하지 못하는 사태가 벌어집니다. 따라서 정규표현식은 '최대한 짧고 명확하게' 짜는 것이 실력입니다."내가 짜려는 정규식 패턴이 맞을까?" 하고 헷갈린다면, 전 세계 개발자들이 애용하는 테스트 사이트인 regex101.com 같은 곳에서 미리 테스트해 보는 것을 강력히 추천합니다!