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

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

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

PHP 정규식(PCRE)의 모든 것 - 일회성 서브 패턴(Once-only subpatterns)

유영재

Once-only subpatterns (단일 서브 패턴)

반복을 최대/최소화하면서 일치가 실패하면 반복되는 항목을 재평가하여 다른 반복 횟수로 나머지 패턴을 일치시킬 수 있는지 확인한다. 때로는 패턴의 작성자가 반복적인 일치를 수행 할 필요가 없음을 알 때는 이를 방지하거나 일치 성질을 변경하거나 다른 방법보다 먼저 실패하게하는 것이 유용하다.

예를 들어, 문자열 "123456bar"에 \d+foo 패턴을 적용하는 것을 고려해 보자.

6자리 모두를 일치한 다음 "foo"와 일치하지 않으면, 정규식의 정상적인 동작은 \d+ 항목과 일치하는 5자리 숫자로 다시 시도한다. 그리고는 4자리로 다시 시도하여 궁극적으로 실패한다. Once-only 서브 패턴은 패턴의 일부가 일치되면 이 방법으로 재평가 되지 않게하기 위해서 최초로 "foo"와의 일치가 실패 했을 경우는 즉시 포기할 것을 지정하는 수단을 제공한다. 이 표기법은 또 다른 종류의 특수 괄호로 (?>\d+)bar와 같이 (?>로 시작한다.

이런 종류의 괄호는 패턴이 일치하면 패턴의 일부를 잠근다("locks up"). 그리고 패턴이 더 이상의 실패하는 패턴으로 되돌아가는 것을 방지한다. 그러나 이전 항목으로 이동(Backtracking)하면 정상적으로 작동한다.

또 다른 설명은 이 유형의 서브 패턴이 문자열의 현재 위치에 고정되어 있으면 동일한 독립형 패턴이 일치하는 문자의 문자열과 일치한다는 것이다.

Once-only 서브 패턴은 서브 패턴을 캡처하지 않는다. 위의 예제와 같은 간단한 경우는 가능한 모든 것을 일치 해야하는 최대화 반복으로 생각할 수 있다. 그래서 \d+\d+?는 둘 다 나머지 패턴을 일치시키기 위해 일치하는 자릿수를 조정할 준비가되어 있다.

물론이 구조는 임의로 복잡한 서브 패턴을 포함할 수 있으며 중첩 될 수 있다.

Once-only 서브 패턴은 look-behind 어설션과 함께 사용하여 문자열 끝에 효율적인 일치를 지정한다. 일치하지 않는 긴 문자열에 적용 할 때 abcd$와 같은 간단한 패턴을 고려하자. 매칭은 왼쪽에서 오른쪽으로 진행되기 때문에, PCRE는 대상의 각 "a"를 찾아 다음 패턴이 나머지 패턴과 일치하는지 확인한다.

패턴이 ^.*abcd$로 지정되면 초기 .*는 처음에는 전체 문자열과 일치한다. 그러나 이것이 실패 할 때(다음 "a"가 없기 때문에) 마지막 문자를 제외한 모든 문자와 일치하도록 역 추적하고, 마지막 문자는 제외하고 모두 반복한다. 다시 한 번 "a"에 대한 검색은 오른쪽에서 왼쪽으로 전체 문자열을 포함하므로 더 나아지지 않는다. 그러나 패턴이 ^(?>.*)(?<=abcd)로 쓰여진 경우 .* 항목에 대해 역 추적이 불가능 할 수 있다. 그것은 전체 문자열만 일치시킬 수 있다. 후속 look-behind 어설션은 마지막 네 문자에 대해 단일 테스트를 수행한다. 실패하면 즉시 일치하지 않는다. 긴 문자열의 경우 이 접근 방식은 처리 시간에 중요한 차이를 만든다.

패턴이 무한히 반복 될 수 있는 서브 패턴 내부에 무제한 반복을 포함하는 패턴은 한 번만 서브 패턴을 사용하는 것이 매우 오랜 시간 동안 실패한 일부 일치를 피할 수 있는 유일한 방법이다. 패턴 (\D+|<\d+>)*[!?]는 숫자가 아닌 문자 또는 <>로 묶인 숫자 뒤에 ! 또는 ? 문자가 일치하는 부분 문자열을 무제한으로 일치한다. 이 일치가 성공한다면 빠르게 실행이 완료된다. 그러나 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"에 적용되면 실패를 보고 하기까지 시간이 오래 걸린다. 이것은 문자열이 여러 가지 방법으로 두 반복 사이에서 나눌 수 있기 때문에 모두 시도해야한다. (PCRE와 Perl은 단일 문자가 사용될 때 빠른 오류를 허용하는 최적화 기능을 가지고 있기 때문에 끝에 있는 단일 문자 대신 [!?]를 사용한다. 패턴이 ((?>\D+)|<\d+>)*[!?]로 변경되면 일치하는데 필요한 마지막 단일 문자를 기억하고 숫자가 아닌 시퀀스는 깨질 수 없으므로 오류가 빠르게 발생한다.


comments powered by Disqus