profile

이 세상에 하나는 남기고 가자

세상에 필요한 소스코드 한줄 남기고 가자

PHP 정규식(PCRE)의 모든 것 - 반복(Repetition)

유영재

Repetition (반복)

반복은 다음 항목 중 하나를 따를 수 있는 한정 기호로 지정된다.

일반적으로 반복 수량어는 괄호({}) 안에 쉼표로 구분된 두 개의 숫자를 입력하여 허용되는 일치의 최소 및 최대 수를 지정한다. 숫자는 65536 보다 작아야 하며 첫 번째 숫자는 두 번째 숫자보다 작거나 같아야 한다. 예를 들어, z{2,4}는 "zz", "zzz" 또는 "zzzz"와 일치한다.

단독으로 닫는 중괄호는 특수 문자가 아니다. 두 번째 숫자가 생략 되었으나 쉼표가 있으면 상한이 없다. 두 번째 숫자와 쉼표가 둘 다 생략되면 수량 기호는 필요한 일치 항목의 정확한 수를 지정한다. 따라서 [aeiou]{3,}는 최소 3개의 연속 모음과 일치하지만 \d{8}은 정확히 8자리 숫자와 일치한다. 한정 기호가 허용되지 않는 위치나 한정 기호의 구문과 일치하지 않는 위치에 나타나는 여는 중괄호({)는 리터럴 문자로 사용된다. 예를 들어, {,6}은 한정 기호가 아니라 4자의 리터럴 문자열이다.

한정자 {0}이 허용되어 표현식이 이전 항목 및 한정 기호가 없는 것처럼 동작한다.

편의상 (그리고 전통적인 호환성) 세 가지 가장 일반적인 수량 기호는 한 문자로 된 단일 표현을 사용한다 :

Single-character quantifiers

문자 설명
* {0,}와 같다
+ {1,}와 같다
? {0,1}와 같다

상한이 없는 수량 기호와 일치하지 않는 서브 패턴을 따라 무한 루프를 구성 할 수 있다(예 : (a?)*).

이전 버전의 Perl과 PCRE는 그러한 패턴을 컴파일 할 때 오류를 내기도 했다. 그러나 이 방법이 유용 할 수 있기 때문에 이러한 패턴이 이제 허용되지만 실제로 서브 패턴의 반복이 실제로 문자와 일치하지 않으면 루프가 강제로 끊어진다.

기본적으로 한정 기호는 "greedy" 즉 패턴의 나머지 부분을 실패시키지 않고 가능한 한 최대 허용 횟수까지 일치시킨다. 이것이 문제를 일으키는 곳의 고전적인 예는 C 프로그램의 주석과 일치하는 것이다. 시퀀스 /**/ 사이에 나타나며 시퀀스 내에서 개별 */ 문자가 나타날 수 있다. 첫 번째 주석 */ 주석 /* 두 번째 주석 */에 문자열 /**.*\*/ 패턴을 적용하여 C 주석을 일치 시키려는 시도는 .*의 탐욕("greedy")으로 인해 전체 문자열과 일치하기 때문에 실패한다.

그러나 수량 기호 다음에 물음표가 오는 경우에는 게으르며(lazy) 가능한 최소 횟수와 일치하므로 패턴 /\*.*?\*/는 C 주석과 함께 올바른 작업을 수행한다. 다양한 한정어의 의미는 다른 방식으로 변경되지 않으며 선호하는 일치 개수만 변경된다. 이 ? 사용을 그 자체로 한정 기호로 사용하는 것과 혼동하지 말아야 한다. 두 가지 용도로 사용되기 때문에 \d??\d와 같이 두 글자로 표시되는 경우가 있다. 한 글자가 기본 설정과 일치하지만 나머지 패턴 일치하는 경우 두 글자와 일치할 수 있다.

PCRE_UNGREEDY 옵션이 설정되어 있으면 (Perl에서는 사용할 수없는 옵션) 수량 한정자는 기본적으로 탐욕(greedy)이 없다. 하지만 개별적인 물음표는 물음표를 따라 가면서 탐욕이 적용된다. 즉, 기본 동작을 반전한다.

+를 따르는 한정 기호는 "possessive"이다. 가능한 한 많은 문자와 일치하고 나머지 패턴과 일치하도록 돌아가지 않는다. 따라서 .*abc는 "aabc"와 일치하지만 .*+는 전체 문자와 일치하기 때문에 .*+abc는 일치하지 않는다. Possessive 지정자는 처리 속도를 높이는 데 사용할 수 있다.

<?php
$str = 'aabc';
preg_match('/.*abc/', $str, $matches);
print_r($matches);
// Array
// (
//  [0] => aabc
// )

preg_match('/.*+abc/', $str, $matches);
print_r($matches);
// Array
// (
// )

괄호로 둘러싼 서브 패턴이 2 이상의 최소 반복이나 최대 제한을 가지면, 컴파일한 패턴은 최소 / 최대의 크기에 따라서 더 많은 공간이 필요하다.

패턴이 .* 또는 .{0,}으로 시작하고 PCRE_DOTALL 옵션(Perl의 /s와 동일)이 설정되어 .이 개행 문자와도 일치하게 되면 패턴은 암시적으로 고정된다. 왜냐하면 뒤에 오는 것은 문자열의 모든 문자 위치에 대해 시도된다. 따라서 첫 번째 이후의 모든 위치에서 전체 일치를 다시 시도할 필요가 없다. PCRE(Perl Compatible Regular Expressions)는 앞에 \A가 붙은 것처럼 패턴을 취급한다. 대상 문자열에 개행 문자가 없는 것을 알 경우, 최적화를 위해 .*로 시작하는 패턴에 PCRE_DOTALL을 설정하거나 ^를 사용하여 명시적으로 고정을 나타낼 수 있다.

캡처 서브 패턴이 반복되면 캡처된 문자열은 마지막 반복에 일치한다. 예를 들어, (tweedle[dume]{3}\s*)+가 "tweedledum tweedledee"와 일치하면 캡처된 부분 문자열의 값은 "tweedledee"이다. 그러나 중첩된 캡처 서브 패턴이 있으면 해당 캡처 값이 이전 반복에서 설정 되었을 수 있다. 예를 들어, /(a|(b))+/가 "aba"와 일치하면 두 번째로 캡처된 하위 문자열의 값은 "b"가 된다.

<?php
preg_match('/(tweedle[dume]{3}\s*)+/', 'tweedledum tweedledee', $matches);
print_r($matches);
// Array
// (
//  [0] => tweedledum tweedledee
//  [1] => tweedledee
// )

preg_match('/(a|(b))+/', 'aba', $matches);
print_r($matches);
// Array
// (
//  [0] => aba
//  [1] => a
//  [2] => b
// )

comments powered by Disqus