정규 표현식

유영·2023년 3월 3일
0

PHP

목록 보기
20/57

1. 정규 표현식의 개념

정규 표현식(regular expression)이란?
정규 표현식(regular expression)은 문자열에서 특정한 규칙을 가지는 문자열의 집합을 찾아내기 위한 검색 패턴입니다.
이러한 검색 패턴은 모든 종류의 문자열 검색이나 교체 등의 작업에서 사용할 수 있습니다.
PHP에서는 다음과 같은 두 가지의 정규 표현식을 지원합니다.

  1. POSIX
  2. PCRE(Perl-Compatible Regular Expression)

POSIX 정규 표현식은 배우기가 쉽고 실행 속도가 빠른 편입니다.
그에 비해 PCRE 정규 표현식은 POSIX 정규 표현식을 확장하였기에 더 강력하고 유연하게 동작합니다.
PCRE 정규 표현식은 표준화되지는 않았지만 펄의 정규 표현식과 그 대체 구현이다.
문자열을 토큰으로 변환하는 과정에서 텍스트를 좌에서 우로 검사하면서
여러 규칙과 일치될 문자의 수량을 다양하게 시도하는 처리에
가장 적합할 뿐 파싱에는 적절치 않다.


정규 표현식 리터럴

PHP에서 정규 표현식 리터럴은 다음 문법을 사용하여 표현합니다.

/검색패턴/플래그

정규 표현식 리터럴은 슬래시(/) 기호로 시작하여, 슬래시(/) 기호로 끝납니다.
또한, 필요에 따라 플래그를 추가하여 기본 검색 설정을 변경할 수도 있습니다.


preg_match() 함수

preg_match() 함수는 해당 문자열에서 전달받은 정규 표현식과 일치하는 패턴을 검색합니다.

preg_match(pattern, $subject, [matches]);

첫 번째 인수로 전달받은 정규 표현식에 해당하는 패턴을 두 번째 인수로 전달받은 문자열에서 검색합니다.

이렇게 검색된 결과는 배열로 반환되며, 세 번째 인수로 반환값이 저장될 배열을 직접 전달할 수도 있습니다.
preg_match() 함수는 정규 표현식에 해당하는 패턴이 검색되면, 더는 검색하지 않고 검색을 중단합니다.
이 함수는 일치하는 패턴이 존재하면 1을 반환하고, 존재하지 않으면 0을 반환합니다.


단순한 패턴 검색

정규 표현식을 사용하여 단순한 패턴을 검색하고자 할 때는 찾고자 하는 문자열을 직접 나열하면 됩니다.

예를 들어, 다음과 같은 정규 표현식은 정확히 "abc"라는 문자열만이 일치할 것입니다.

/abc/

$subject = "간장 공장 공장장은 강 공장장이고, 된장 공장 공장장은 장 공장장이다.";

	if (preg_match('/공장/', $subject)) {
		echo "해당 문자열에서 '공장'을 발견했습니다.<br>";
	} else {
		echo "해당 문자열에서 '공장'을 발견하지 못했습니다.<br>";
	}
	
	if (preg_match('/장공/', $subject)) {
		echo "해당 문자열에서 '장공'을 발견했습니다.<br>";
	} else {
		echo "해당 문자열에서 '장공'을 발견하지 못했습니다.<br>";
	}

위의 예제에서 첫 번째 정규 표현식은 해당 문자열이 "공장"이라는 부분 문자열을 포함하고 있으므로, 1을 반환합니다.

하지만 두 번째 정규 표현식은 해당 문자열이 "장 공"이라는 부분 문자열은 포함하고 있지만, 정확히 "장공"이라는 부분 문자열을 포함하지 않으므로, 아무것도 일치하지 않습니다.
preg_match() 메소드는 해당 문자열에서 인수로 전달받은 정규 표현식과 일치하는 부분 문자열을 찾지 못하면 0을 반환합니다.


플래그(flags)

정규 표현식 리터럴을 작성할 때 플래그를 사용하여 기본 검색 설정을 변경할 수 있습니다.

플래그설명
i대소문자 구분 없이 검색
g일치하는 모든 부분을 선택하도록 설정함.
m여러 줄의 입력 문자열을 그 상태 그대로 여러 줄로 비교하도록 설정함.
y대상 문자열의 현재 위치부터 비교를 시작하도록 설정함.
u대상 문자열이 UTF-8로 인코딩된 것으로 설정함.
s줄이 넘어가도 문자열을 1줄로 인식하여 기능 동작
x패턴에 존재하는 모든 공백 무시
$subject = "bcabcAB";
// 기본 설정으로 검색 패턴을 비교할 때 대소문자를 구분함.
preg_match_all('/AB/', $subject, $matches_01);      // "AB"
// 검색 패턴을 비교할 때 대소문자를 구분하지 않도록 설정함.
preg_match_all('/AB/i', $subject, $matches_02); // "ab", "AB"

preg_match_all() 함수는 해당 문자열에서 전달받은 정규 표현식과 일치하는 패턴을 모두 검색하여,
세 번째 인수로 전달되는 배열에 저장합니다.


2. 정규 표현식의 기초

특수 문자(special characters)

<앵커기호>
^ 문자열의 시작
$ 문자열의 끝

정규 표현식을 사용하여 단순한 패턴을 검색하고자 할 때는 찾고자 하는 문자열을 직접 나열하면 됩니다.
하지만 숫자만을 검색하거나, 띄어쓰기를 찾는 등 정확히 일치하는 패턴보다 더 복잡한 조건을 사용하려면 특수 문자를 사용해야 합니다.
이렇게 정규 표현식에서 사용하는 특정 의미를 가지는 기호를 특수 문자 또는 메타(meta) 문자라고 합니다.
정규 표현식에서 사용할 수 있는 대표적인 특수 문자는 다음과 같습니다.

특수문자설명
.문자줄 바꿈 문자(\n)를 제외한 임의의 한 문자를 의미함.
?해당 문자 패턴이 0번 또는 1번만 반복됨.
_공백100_200는 100 200을 의미
*해당 문자 패턴이 0번 이상 반복됨."a*b"는 "b", "ab", "aab", "aaab"를 포함한다.
+해당 문자 패턴이 1번 이상 반복됨.
{...}반복되는 횟수를 지정함.
^부정/문자 클래스 안의 문자를 제외한 나머지를 선택한다.[^a-z]는 알파벳 소문자로 시작하지 않는 모든 문자를 의미한다.
\ 이스케이프특수 문자를 식에 문자 자체로 포함한다.
|선택을 의미함. (OR)"[a-z]"는 a부터 z까지 중 하나, "[1-9]"는 1부터 9까지 중의 하나를 의미한다.
(...)그룹화의 시작과 끝을 의미함.
[]문자 클래스 "["과 "]" 사이의 문자 중 하나를 선택한다.또한 "-" 기호와 함께 쓰면 범위를 지정가능. "[a-z]"는 a부터 z까지 중 하나를 의미한다.
( )하위식/서브패턴(문자열을 하나로 묶음) 여러 식을 하나로 묶을 수 있다."abc|adc"와 "a(b|d)c"는 같은 의미를 가진다.

양화사(quantifier)

정규 표현식에서는 특수 문자로 수량을 나타내는 다양한 양화사를 사용할 수 있습니다.

  • '*'는 바로 앞의 문자가 0번 이상 나타날 경우를 검색합니다. ({0, }와 같음)

  • '+'는 바로 앞의 문자가 1번 이상 나타날 경우를 검색합니다. ({1, }과 같음)

  • '?'는 바로 앞의 문자가 0번 또는 1번만 나타날 경우를 검색합니다. ({0,1}과 같음)

  • '{n,m}'은 바로 앞의 문자가 반복되는 횟수를 지정합니다.

  • 바로 앞의 문자가 최소 n번이상 최대 m번이하로 나타날 경우를 검색합니다. (n과 m은 반드시 양의 정수이어야만 함)

<수량자>

{m}
m회일치 "a{3}b"는 "aaab"만 포함한다.

{m,}
최소 m회 일치 "a{2,}b"는 "aab", "aaab", "aaaab"를 포함한다. "ab"는 포함되지 않는다.

{,m}
최대 m회 일치

{m, n}
m회 이상 n회 이하 "a{1,3}b"는 "ab", "aab", "aaab"를 포함하지만, "b"나 "aaaab"는 포함하지 않는다.

{3}
3개 존재하는 문자 검색 [A-Z]{3} 대문자 A~Z 중 3글자

{4,}
4개 이상 존재하는 문자 검색 [A-Z]{4,} 대문자 A~Z 중 4글자 이상

{2,4}
2개 이상 4개 이하 존재하는 문자 검색 cis{2,4}co cissco, cisssco, cissssco

  	$subject = "PHP is cooooool!";

	// 문자 'l' 바로 앞에 문자 'o'가 0 또는 1번 나타나는 경우를 검색함.
	preg_match_all('/o?l/', $subject, $match_01);
	var_dump($match_01);	
	echo "<br><br>";

	// 문자 'l' 바로 앞에 문자 'o'가 0번 이상 나타나는 경우를 검색함.
	preg_match_all('/o*l/', $subject, $match_02);
	var_dump($match_02);
	echo "<br><br>";

	// 문자 'l' 바로 앞에 문자 'o'가 1번 이상 나타나는 경우를 검색함.
	preg_match_all('/o+l/', $subject, $match_03);
	var_dump($match_03);
	echo "<br><br>";

	// 영문 소문자가 1번 이상 나타나는 경우를 검색함.
	// 즉, 영문 소문자만으로 이루어진 부분 문자열을 검색함.
	preg_match_all('/[a-z]+/', $subject, $match_04);
	var_dump($match_04);
    
//결과
//array(1) { [0]=> array(1) { [0]=> string(2) "ol" } }
//array(1) { [0]=> array(1) { [0]=> string(7) "ooooool" } }
//array(1) { [0]=> array(1) { [0]=> string(7) "ooooool" } }
//array(1) { [0]=> array(2) { [0]=> string(2) "is" [1]=> string(8) "cooooool" } }

위치 문자

정규 표현식에서는 해당 문자열에서 패턴을 검색할 단어의 위치까지 지정할 수 있습니다.

  • '^'는 단어의 맨 앞에 위치한 해당 패턴만을 검색합니다.
  • '$'는 단어의 맨 뒤에 위치한 해당 패턴만을 검색합니다.
$subject = "abcdef defabc";
// 단어가 문자열 "abc"로 시작하는 경우를 검색하여, 해당 부분 문자열을 'ABC'로 대체함.
$match_01 = preg_replace('/^abc/', 'ABC',$subject);
// 단어가 문자열 "abc"로 끝나는 경우를 검색하여, 해당 부분 문자열을 'ABC'로 대체함.
$match_02 = preg_replace('/abc$/', 'ABC', $subject);

preg_replace() 함수는 해당 문자열에서 전달받은 정규 표현식과 일치하는 패턴을 검색하여, 해당 부분을 두 번째 인수로 전달되는 문자열로 대체한 새로운 문자열을 반환합니다.


선택 문자

정규 표현식에서는 특수문자 '|'를 사용하여 여러 개의 검색 패턴을 사용할 수 있습니다.
즉, 특수문자 '|'로 구분된 정규 표현식 중 어느 하나에만 일치해도 검색됩니다.

$subject = "ABCdefGHIjkl";
// 문자열 'abc' 또는 'def' 또는 'jkl'만을 검색함.
// 즉, 위의 세 문자열 중 어느 하나에만 일치해도 검색됨.
preg_match_all('/abc|def|jkl/', $subject, $match);
var_dump($match);

//array(1) { [0]=> array(2) { [0]=> string(3) "def" [1]=> string(3) "jkl" } }

문자 클래스(character class)

문자 클래스(character class)란 정규 표현식에서 명시된 범위에 해당하는 한 문자만을 선택하기 위해 사용되는 문자입니다.

이러한 문자 클래스는 꺾쇠 괄호([])를 사용하여 표현합니다.

/[0-3]/        // 0부터 3까지의 숫자에 해당하는 한 문자
/[a-z]/        // 영문 소문자에 해당하는 한 문자
/[0-9a-zA-Z]/  // 숫자 또는 영문 대소문자에 해당하는 한 문자

<클래스>
[3579]	3, 5, 7, 9
[2-9]	2, 3, 4, 5, 6, 7, 8 ,9 
[^2-9]	0, 1
[0-9a-zA-Z]	모든 숫자/문자
[\f\r\t\n\v]	모든 공백

$subject = "@ap";
preg_match("/.ap/", $subject, $match_01);        // "ap" 문자열 앞에 임의의 한 문자가 나타나는 경우를 검색함.
preg_match("/[a-zA-Z]ap/", $subject, $match_01); // "ap" 문자열 앞에 영문자 한 문자가 나타나는 경우를 검색함.

위의 예제에서 사용된 특수 문자 '.'는 줄 바꿈 문자를 제외한 임의의 문자 하나를 의미합니다.
이 문자를 잘 사용하면, 검색하고자 하는 문자의 범위를 더욱 제한할 수 있습니다.


POSIX 문자 클래스

앞서 살펴본 문자 클래스 이외에도 POSIX 정규 표현식에서만 사용할 수 있는 문자 클래스가 존재합니다.
POSIX에서만 사용할 수 있는 문자 클래스는 다음과 같습니다.

위의 표와 같이 POSIX에서만 사용할 수 있는 문자 클래스는 기본적으로 꺾쇠 괄호([])를 포함하고 있습니다.
따라서 이러한 POSIX 전용 문자 클래스는 다음과 같이 꺾쇠 괄호를 두 번 사용하여 표현됩니다.

문자클래스설명동일
[:alnum:]영문자와 숫자에 포함되는지를 확인[0-9a-zA-Z]
[:alpha:]영문 대소문자에 포함되는지를 확인[a-zA-Z]
[:lower:]영문 소문자에 포함되는지를 확인함.[a-z]
[:upper:]영문 대문자에 포함되는지를 확인함.[A-Z]
[:digit :]십진법 숫자에 포함되는지를 확인함.[0-9]
[:xdigit:]16진법 숫자나 문자에 포함되는지를 확인함.[0-9a-fA-F]
[:punct:]구두점에 포함되는지를 확인함.
[:blank:]탭과 띄어쓰기에 포함되는지를 확인함.
[:space:]공백 문자에 포함되는지를 확인함.
[:cntrl:]제어 문자에 포함되는지를 확인함.
[:print :]출력할 수 있는 문자에 포함되는지를 확인함.
[:graph:]띄어쓰기를 제외한 모든 출력할 수 있는 문자에 포함되는지를 확인함.
/[[:alpha:]][[:digit:]]/ // 첫 번째 문자가 영문자이고, 
						// 두 번째 문자가 숫자인 길이가 2인 문자열
                        // a1, a2, ..., b1, b2, ...
$subject = "Hello PHP is cool!";
// 첫 번째와 세 번째 문자가 영문 소문자이고, 두 번째 문자가 띄어쓰기인 경우를 검색함.

preg_match_all('/[[:lower:]][[:space:]][[:lower:]]/', $subject, $match_01);
// 첫 번째 문자가 영문 소문자이고, 두 번째 문자가 띄어쓰기, 세 번째 문자가 영문 대문자인 경우를 검색함.
preg_match('/[[:lower:]][[:space:]][[:upper:]]/', $subject, $match_02);

//array(1) { [0]=> array(1) { [0]=> string(3) "s c" } }
//array(1) { [0]=> string(3) "o P" }

3. 정규 표현식의 활용

정규 표현식의 활용
앞서 살펴본 정규 표현식을 활용하면, 다음과 같은 데이터가 해당 형식에 맞는지를 손쉽게 확인할 수 있습니다.

  1. 전화번호
  2. 이메일 주소

전화번호 확인

정규 표현식을 이용하면 해당 전화번호가 유효한 형식의 전화번호인지를 확인할 수 있습니다.

정규표현식
1) /^[[:digit:]]{2}-[[:digit:]]{4}-[[:digit:]]{4}/
=> 02-1234-5678, ...
2) /^[[:digit:]]{2,3}-[[:digit:]]{3,4}-[[:digit:]]{4}/
=>02-1234-5678, 031-123-4567, ...

2)은 맨앞자리가2자리나 3자리이고, 국번도 3자리나 4자리인 전화번호까지 검색할 수 있습니다.

$tel = "02-1234-5678";
$cell = "010-1234-5678";

$pattern_01 = "/^[[:digit:]]{2}\-[[:digit:]]{4}\-[[:digit:]]{4}/";
if(preg_match($pattern_01,$tel,$matches_01)){
	var_dump($matches_01);
}else{
echo "{$tel}은 유효한 형식의 전화번호가 아닙니다.<br>";
}
if(preg_match(preg_match($pattern_01,$cell,$matches_02)){
	var_dump($matches_02);
}else{
echo"{$cell}은 유효한 형식의 전화번호가 아닙니다.<br>";
}

//array(1) { [0]=> string(12) "02-1234-5678" }
//010-1234-5678은 유효한 형식의 전화번호가 아닙니다.

$pattern_02 = "^[[:digit:]]{2,3}\-[[:digit:]]{4}\-[[:digit:]]{4}";
if(preg_match($pattern_01, $tel, $matches_03)){
	var_dump($matches_03);
}else{
echo "{$tel}은 유효한 형식의 전화번호가 아닙니다.<br>";
}
if (preg_match($pattern_02, $cell, $matches_04)) {
	var_dump($matches_04);
} else {
echo "{$cell}은 유효한 형식의 전화번호가 아닙니다.<br>";
}
//array(1) { [0]=> string(12) "02-1234-5678" }
//array(1) { [0]=> string(13) "010-1234-5678" }

정규 표현식에서 '\'문자 바로 뒤에 일반 문자가 나오면, 해당 문자는 특수 문자로 인식됩니다.
또한, '\'문자 바로 뒤에 특수 문자가 나오면, 해당 문자는 일반 문자로 인식됩니다.
위의 예제에서 사용된 '-'문자는 범위를 나타내는 특수 문자이므로, '-'는 단순히 전화번호에서 사용되는 '-'기호로 인식됩니다.


이메일 주소 확인

전화번호뿐만 아니라 이메일 주소도 특정 형식을 가지고 있습니다.
따라서 정규 표현식을 이용하면 해당 이메일 주소가 유효한 형식의 이메일 주소인지를 확인할 수 있습니다.

정규표현식
① /([0-9a-zA-Z_-]+)@([0-9a-zA-Z_-]+).([0-9a-zA-Z_-]+)/ // help@abcd.com, ...
② /([0-9a-zA-Z_-]+)@([0-9a-zA-Z_-]+)(.[0-9a-zA-Z_-]+){1,2}/ // help@abcd.com, help@abcd.co.kr, ...

$com = "help@abcd.com";
	$co = "help@abcd.co.kr";
	
	$pattern_01 = "/([0-9a-zA-Z_-]+)@([0-9a-zA-Z_-]+)\.([0-9a-zA-Z_-]+)/";
	if (preg_match($pattern_01, $com, $matches_01)) {
		var_dump($matches_01[0]);
	} else {
		echo "{$com}은 유효한 형식의 이메일 주소가 아닙니다.<br>";
	}
	echo "<br><br>";

	if (preg_match($pattern_01, $co, $matches_02)) {
		var_dump($matches_02[0]);
	} else {
		echo "{$co}은 유효한 형식의 이메일 주소가 아닙니다.<br>";
	}
	echo "<br><br>";

	$pattern_02 = "/([0-9a-zA-Z_-]+)@([0-9a-zA-Z_-]+)(\.[0-9a-zA-Z_-]+){1,2}/";
	if (preg_match($pattern_02, $com, $matches_03)) {
		var_dump($matches_03[0]);
	} else {
		echo "{$com}은 유효한 형식의 이메일 주소가 아닙니다.<br>";
	}
	echo "<br><br>";

	if (preg_match($pattern_02, $co, $matches_04)) {
		var_dump($matches_04[0]);
	} else {
		echo "{$co}은 유효한 형식의 이메일 주소가 아닙니다.<br>";
	}
    
//string(13) "help@abcd.com"
//string(12) "help@abcd.co"
//string(13) "help@abcd.com"
//string(15) "help@abcd.co.kr"

PHP에서는 유효한 형식의 이메일 주소인지를 확인하기 위해 정규 표현식뿐만 아니라 filter_var() 함수를 제공하고 있습니다.

$com = "help@abcd.com";
$co = "help@abcd.co.kr";

if(filter_var($com,FILTER_VALIDATE_EMAIL)){
echo $com;
}else{
	echo "{$com}은 유효한 형식의 이메일 주소가 아닙니다.<br>";
}

if(filter_var($co,FILTER_VALIDATE_EMAIL)){
		echo $co;
}else{
echo "{$co}은 유효한 형식의 이메일 주소가 아닙니다.<br>";
}

//help@abcd.com
//help@abcd.co.kr

한글 확인

해당 문자가 한글인지를 확인하는 정규 표현식은 다음과 같습니다.

정규표현식
① /[가-힣]/
// 한글 소리 마디(초성 19개, 중성 21개, 종성 28개로 이루어지는 총 11,172개의 한글 문자)
//언어 설정이 한글이 아닌 시스템에서는 동작이 안 될 수도 있습니다.
② /[\x{ac00}-\x{d7af}]/u
// 한글 소리 마디의 유니코드 범위 목록값
//②번 정규 표현식처럼 'u'플래그를 추가하여 해당 정규 표현식 문자열을 UTF-8로 인코딩된 것처럼 취급할 수 있습니다.

$eng = "gil-dong Hong";
$kor = "홍길동";

$pattern = "/[가-힣]+/"; //한글소리마디
if(preg_match($pattern,$eng,$matches_01)) {
	var_dump($matches_01);
} else {
	  echo "{$eng}에는 한글이 포함되어 있지 않습니다.<br>";
}
//gil-dong Hong에는 한글이 포함되어 있지 않습니다.


if (preg_match($pattern, $kor, $matches_02)) {
    var_dump($matches_02);
} else {
    echo "{$eng}에는 한글이 포함되어 있지 않습니다.<br>";
}
//array(1) { [0]=> string(9) "홍길동" }

해당 문자열에서 한글만을 제거하는 예제입니다.

$text = "123가나abc다라";
$pattern = "/[\\x{ac00}-\\x{d7af}]+/u"; //한글소리마디
$arr = preg_match_all('/./u', $text, $matches); 
// 줄 바꿈 문자(\n)를 제외한 임의의 한 문자씩 검색함.
echo preg_match_replace($pattern,'',$text);
// 해당 문자가 한글이면, 빈 문자열로 대체함.

//결과 : 123abc

0개의 댓글