[첫화면으로]Perl/정규표현식

마지막으로 [b]

Perl 정규표현식 Regular Expression

1. perlretut (Perl regular expressions tutorial) 번역
1.1. Part 1: 기본 The basics
1.1.1. 간단한 단어 매칭 Simple word matching
1.1.2. 캐릭터 클래스 Using character classes
1.1.3. 이것 아니면 저것에 매치 Matching this or that
1.1.4. 그룹짓기와 계층적 매칭 Grouping things and hierarchical matching
1.1.5. 매치된 부분 추출 Extracting matchers
1.1.6. 백레퍼런스 Backreferences
1.1.7. 상대적 백레퍼런스 Relative backreferences
1.1.8. 이름을 붙인 백레퍼런스 Named backreferences
1.1.8.1. 이름을 붙인 백레퍼런스 보충
1.1.9. 캡처 그룹 번호 리셋 Alternative capture group numbering
1.1.9.1. 그룹 번호 리셋 내용 보충
1.1.10. 매치 위치 정보 Position information
1.1.11. 캡처되지 않는 그룹 Non-capturing groupings
1.1.12. 반복 매치 Matching repetitions
1.1.13. 독점적 수량자 Possessive quantifiers
1.1.13.1. 독점적 수량자 내용 보충
1.1.14. 정규표현식 구성하기 Building a regexp
1.1.15. Using regular expression in Perl
1.1.15.1. 치환 방지 Prohibiting substitution
1.1.15.2. 글로벌 매칭 Global matching
1.1.15.3. 검색과 치환 Search and replace
1.1.15.4. split 연산자 The split operator
1.1.15.5. 패턴 평가 최적화 Optimizing pattern evaluation
1.2. Part 2: 강력한 도구들 Power tools
1.2.1. 캐릭터, 스트링, 캐릭터 클래스 More on characters, strings, and character class
1.2.2. 정규표현식 컴파일 및 저장 Compiling and saving regular expressions
1.2.3. 런타임에 정규표현식 생성하기 Composing regular expressions at runtime
1.2.4. 정규식 내에 주석과 modifier 삽입 Embedding comments and modifiers in a regular expression
1.2.5. 앞뒤 살펴보기 Looking ahead and looking behind
1.2.6. 백트래킹을 금지하는 독립적인 부분표현식 Using independent subexpressions to prevent backtracking
1.2.7. 조건부 정규표현식 Conditional expressions
1.2.7.1. 조건부 표현식 내용 보충
1.2.8. 패턴 정의하고 이름붙이기 Defining named patterns
1.2.9. 재귀적 패턴 Recursive patterns
1.2.9.1. 재귀적 패턴 보충
1.2.10. 정규표현식 안에서 Perl 코드 실행하기 A bit of magic: executing Perl code in a regular expression
1.2.11. 백트래킹 제어 명령어 Backtracking control verbs
1.2.12. 프라그마와 디버깅 Pragmas and debugging
1.3. 버그들 BUGS
1.4. 참고 SEE ALSO
2. Mastering Perl
2.1. /g , /c modifier 예 - 간단한 파서
2.2. lookaround
3. 그 외 이런저런 주제들
3.1. qr// 에 대하여
3.1.1. aero 님의 코멘트
3.2. 매치 변수와 성능 문제
3.3. non-greedy quantifier를 쓸 때 주의
3.4. /g, /c, \G, 포지션 리셋에 관한 보충
4. 기타
5. comments

1. perlretut (Perl regular expressions tutorial) 번역

Perl 정규표현식 튜토리알 문서 번역, 정리

Description

정규표현식은 패턴을 묘사하는 문자열이다. Perl에서 패턴은 문자열을 검색하거나, 문자열에서 원하는 부분을 추출하거나, 검색 및 치환 연산을 하는 데 사용된다.

1.1. Part 1: 기본 The basics

1.1.1. 간단한 단어 매칭 Simple word matching

가장 단순한 정규식은 하나의 단어, 좀 더 일반적으로 말하면 문자들로 이뤄진 스트링이다. 한 단어로 아뤄진 정규식은 그 단어를 포함하고 있는 스트링에 매치된다:

/regexp/(2), =~(3), !~(4)

"Hello World" =~ /World/;   # 단순한 단어의 일치 여부 검색. 일치하므로 위 expression은 true
"Hello World" !~ /World/;   # 반대

if ("Hello World" =~ /World/) {
        print "It matches\n";
}
else {
        print "It doesn't match\n";
}

문자열 리터럴이 들어갈 자리에 변수를 사용할 수 있다:

$greeting = "World";
if ("Hello World" =~ /$greeting/) {   # 스트링 자리에 변수 사용 가능
    print "match\n";
} else {
    print "not match\n";
}

스페샬 변수 $_(5)에 대해 매치검사를 하는 경우, $_ =~ 부분을 생략할 수 있다:

$_ = "Hello World";
if (/World/) {               # "$_ =~" 는 생략가능
    print "match\n";
} else {
    print "not match\n";
}

앞에 m을 붙이면 // 대신에 임의의 구분자를 쓸 수 있다: m!regexp!(6)

"Hello World" =~ m!World!;   # 매치됨, 구분자는 '!'
"Hello World" =~ m{World};   # 매치됨, 구분자는 '{}'
"/usr/bin/perl" =~ m"/perl"; # '/usr/bin' 뒷부분에 매치됨, '/'는 평범한 문자로 간주됨.

정규식은 스트링의 일부에 정확하게 매치되어야 구문이 참이 된다:

"Hello World" =~ /world/;  # 일치하지 않음. 대소문자가 구분된다.
"Hello World" =~ /o W/;    # 일치함
"Hello World" =~ /oW/;     # 일치하지 않음
"Hello World" =~ /World /; # 일치하지 않음

스트링(7)의 두 곳 이상에서 정규식이 매치될 경우, 항상 가장 먼저 일치하는 곳에서 매치된다.

"Hello World" =~ /o/;       # 'Hello'의 'o'에 매치됨
"That hat is red" =~ /hat/; # 'That'의 'hat'에 매치됨

metacharacter(8)s

다음 캐릭터들은 문자 그대로 매치되지 않고, 정규식 표기에 사용하도록 예약되어있다:

{}[]()^$.|*+\? 

메타캐릭터를 일치시킬 때는 앞에 백슬래쉬를 붙인다:

"2+2=4" =~ /2+2/;    # 매치되지 않는다, + 는 메타캐릭터
"2+2=4" =~ /2\+2/;   # 매치된다, \+는 평범한 +로 간주된다
"The interval is [0,1)." =~ /[0,1)./     # 문법 에러!
"The interval is [0,1)." =~ /\[0,1\)\./  # 매치됨
"#!/usr/bin/perl" =~  /#!\/usr\/bin\/perl/;  # 매치됨
"#!/usr/bin/perl" =~ m!#\!/usr/bin/perl!;    # 이렇게 하는 게 읽기 더 좋다
'C:\WIN32' =~ /C:\\WIN/;   # 백슬래쉬도 메타캐릭터

Escape Sequences(9)

"1000\t2000" =~ m(0\t2)   # 매치됨
"1000\n2000" =~ /0\n20/   # 매치됨
"1000\t2000" =~ /\000\t2/ # 매치되지 않음, "0"과 "\000"는 같지 않다
"cat"   =~ /\o{143}\x61\x74/ # ASCII 시스템에서 매치되나, cat을 표기하는 이상한 방법이다.

Perl에서 정규식은 대부분 이중 따옴표로 표시된 스트링(double-quoted string)으로 간주된다. 이스케이프 시퀀스나 변수를 처리할 때도 마찬가지이다. 정규식에 포함된 변수는 정규식의 매치 검사를 하기 전에 변수의 값으로 먼저 치환된다.

$foo = 'house';
'housecat' =~ /$foo/;      # 매치됨
'cathouse' =~ /cat$foo/;   # 매치됨
'housecat' =~ /${foo}cat/; # 매치됨

예제: 간단한 Unix의 grep 프로그램을 흉내내는 매우 간단한 형태

#!/usr/bin/perl
$regexp = shift;           # 첫번째 명령행 인자를 취함
while (<>) {               # 두번째 인자를 파일명으로 하여 파일을 읽음
    print if /$regexp/;    # print $_ if $_ =~ /$regexp/;
}
% simple_grep abba /usr/dict/words
Babbage
cabbage
cabbages
sabbath
Sabbathize
Sabbathizes
sabbatical
scabbard
scabbards

anchor(10) metacharacter(11)

스트링의 '어느 지점'에 매치시킬지를 지정

"housekeeper" =~ /keeper/;    # 매치됨
"housekeeper" =~ /^keeper/;   # 매치되지 않음
"housekeeper" =~ /keeper$/;   # 매치됨
"housekeeper\n" =~ /keeper$/; # 매치됨

^$가 동시에 쓰이면 스트링의 시작과 끝에 매치되어야 하므로, 정규식이 스트링 전체에 매치된다.

"keeper" =~ /^keep$/;      # 매치되지 않음
"keeper" =~ /^keeper$/;    # 매치됨
""       =~ /^$/;          # ^$ 는 빈 스트링에 매치됨

bert라는 이름의 친구를 찾고 있다고 가정하자:

"dogbert" =~ /bert/;   # 매치되지만, 우리가 원하는 게 아님
"dilbert" =~ /^bert/;  # 이런 건 매치되지 않게 했음, 그러나..
"bertram" =~ /^bert/;  # 이런 게 매치됨, 여전히 문제가 있음
"bertram" =~ /^bert$/; # 매치되지 않음, 좋다
"dilbert" =~ /^bert$/; # 매치되지 않음, 좋다
"bert"    =~ /^bert$/; # 매치됨, 완벽하군

1.1.2. 캐릭터 클래스 Using character classes

character class(17) : [...] 형태

매치될 수 있는 하나 이상의 캐릭터들의 집합을 표시

/cat/;       # matches 'cat'
/[bcr]at/;   # matches 'bat, 'cat', or 'rat'
/item[0123456789]/;  # matches 'item0' or ... or 'item9'
"abc" =~ /[cab]/;    # matches 'a' ([cab]의 순서라고 해서 'c'가 매치하는 게 아님)
/[yY][eE][sS]/;      # 'yes', 'Yes', 'YES' 등, 대소문자를 구분하지 않고 'yes'에 매치 

character class(19) 내의 특수문자는 앞에서 봤던 경우와 다르다:

-]\^$
/[\]c]def/; # ']def' or 'cdef'에 매치됨
$x = 'bcr';
/[$x]at/;   # 'bat', 'cat', or 'rat'에 매치됨
/[\$x]at/;  # '$at' or 'xat'에 매치됨
/[\\$x]at/; # '\at', 'bat, 'cat', or 'rat'에 매치됨

-(25)를 사용하여 연속된 문자들의 집합을 표시:

/item[0-9]/;    # 'item0' or ... or 'item9'에 매치됨
/[0-9bx-z]aa/;  # '0aa', ..., '9aa',
                # 'baa', 'xaa', 'yaa', or 'zaa'에 매치됨
/[0-9a-fA-F]/;  # 16진수 숫자들에 매치됨
/[0-9a-zA-Z_]/; # Perl의 변수명 등에 쓰이는 "word" 캐릭터들에 매치됨

-가 클래스 제일 앞 또는 제일 뒤에 오면 일반 문자 "-"

^(26)가 클래스 제일 앞에 오면 negated character class. 대괄호 안에 있는 문자들을 제외한 모든 문자들을 의미

/[^a]at/;  # 'aat'와 'at'에는 매치되지 않고,
           # 그 외 모든 경우 'bat', 'cat, '0at', '%at' 등에 매치
/[^0-9]/;  # 숫자가 아닌 캐릭터들에 매치
/[a^]at/;  # 'aat' 또는 '^at'에 매치됨; 여기서 '^'는 보통의 문자

보편적으로 사용되는 캐릭터 클래스들에 대해 별도의 축약형 제공. 5.10.0(27)부터는 이 클래스는 ASCII범위 밖의 Unicode(28) 문자에도 매치된다. 5.14.0(29)이상에서는 //a(30) 변경자를 사용하여 ASCII범위로 매치를 국한시킬 수 있다.

Perl 5.14.0(44)부터 제공되는 //a(45)를 사용하면 \d,\s,\w가 ASCII범위에 있는 문자에만 매치되도록 제한할 수 있다. 영어권 텍스트만 처리하는 경우 전체 Unicode(46)에 노출되지 않도록 하는데 유용하다. ("a"를 두번 써서 //aa처럼 할 경우 더 강한 제한을 두어, ASCII문자와 비ASCII문자가 대소문자 구분없는 매치 검사에 매치되는 것을 방지할 수 있다; 이 옵션을 주지 않을 경우 유니코드 "Kelvin Sign"문자가 "k"나 "K"에 매치되게 된다)

주인장 보충: Kelvin Sign을 구글링해보니 유니코드 U+212A에 해당하고, 생김새는 "K"와 비슷하다[1]. 이것이 /i 옵션을 주었을 때는 알파벳 'k'나 'K'에 매치된다. (Perl 5.8.8에서도 마찬가지임을 확인했음)

Strawberry Perl 5.12에서 테스트:

use 5.012;
$_ = chr(0x212A);   # Kelvin Sign
say "match /k/"  if /k/;
say "match /K/"  if /K/;
say "match /k/i" if /k/i;
say "match /K/i" if /K/i;

match /k/i
match /K/i

이런 축약형 \d\s\w\D\S\W는 캐릭터 클래스 안과 밖 양쪽에서 다 사용가능:

/\d\d:\d\d:\d\d/; # matches a hh:mm:ss time format
/[\d\s]/;         # matches any digit or whitespace character
/\w\W\w/;         # matches a word char, followed by a
                  # non-word char, followed by a word char
/..rt/;           # matches any two chars, followed by 'rt'
/end\./;          # matches 'end.'
/end[.]/;         # same thing, matches 'end.'

Word anchor(47) \b(48)

$x = "Housecat catenates house and cat";
$x =~ /cat/;     # 'housecat'의 cat에 매치
$x =~ /\bcat/;   # 'catenates'의 cat에 매치
$x =~ /cat\b/;   # 'housecat'의 cat에 매치
$x =~ /\bcat\b/; # 스트링 마지막 'cat'의 cat에 매치

어째서 .가 매치되는 것 중 \n는 제외되는가? 그 이유는 종종 사용자는 여러 줄에 대해 매칭을 수행하며 뉴라인 캐릭터를 무시하고자 할 때가 있기 때문이다. 예를 들어 스트링은 "\n" 한 줄을 나타내지만, 이것을 비어 있는 걸로 간주하고 싶은 것이다. 따라서 다음과 같이 판정된다:

""   =~ /^$/;     # 매치됨
"\n" =~ /^$/;     # 매치됨, $는 "\n" 앞에 걸린다

""   =~ /./;      # 매치되지 않음; 캐릭터 하나가 필요함
""   =~ /^.$/;    # 매치되지 않음; 캐릭터 하나가 필요함
"\n" =~ /^.$/;    # 매치되지 않음; "\n" 이외의 캐릭터 하나가 필요함
"a"  =~ /^.$/;    # 매치됨
"a\n"  =~ /^.$/;  # 매치됨, $는 "\n" 앞에 걸린다

매칭할 때 개행 문자(\n(50))를 고려하거나, ^$가 전체 스트링이 아니라 각 라인의 시작과 끝에 매치되길 바랄 때가 있다.

$x = "There once was a girl\nWho programmed in Perl\n";

$x =~ /^Who/;   # 매치되지 않음, "Who"가 스트링의 시작 부분에 있지 않음
$x =~ /^Who/s;  # 매치되지 않음, "Who"가 스트링의 시작 부분에 있지 않음
$x =~ /^Who/m;  # 매치됨, "Who"가 두번째 라인의 시작 부분에 있음
$x =~ /^Who/sm; # 매치됨, "Who"가 두번째 라인의 시작 부분에 있음

$x =~ /girl.Who/;   # 매치되지 않음, "."가 "\n"에 매치되지 않음
$x =~ /girl.Who/s;  # 매치됨, "."가 "\n"에 매치됨
$x =~ /girl.Who/m;  # 매치되지 않음, "."가 "\n"에 매치되지 않음
$x =~ /girl.Who/sm; # 매치됨, "."가 "\n"에 매치됨
$x = "There once was a girl\nWho programmed in Perl\n";

$x =~ /^Who/m;   # matches, "Who" at start of second line
$x =~ /\AWho/m;  # doesn't match, "Who" is not at start of string

$x =~ /girl$/m;  # matches, "girl" at end of first line
$x =~ /girl\Z/m; # doesn't match, "girl" is not at end of string

$x =~ /Perl\Z/m; # matches, "Perl" is at newline before end
$x =~ /Perl\z/m; # doesn't match, "Perl" is not at end of string

1.1.3. 이것 아니면 저것에 매치 Matching this or that

서로 다른 단어들 또는 캐릭터 스트링들에 동시에 매치될 수 있는 정규식.

alternation(63) metacharacter(64) |(65)을 사용

"cats and dogs" =~ /cat|dog|bird/;  # "cat"에 매치됨
"cats and dogs" =~ /dog|cat|bird/;  # "cat"에 매치됨
"cats"          =~ /c|ca|cat|cats/; # matches "c"
"cats"          =~ /cats|cat|ca|c/; # matches "cats"
"cab" =~ /a|b|c/ # "c"에 매치됨
                 # /a|b|c/ == /[abc]/

1.1.4. 그룹짓기와 계층적 매칭 Grouping things and hierarchical matching

정규식의 일부에 대해서만 alternation을 적용하고 싶은 경우.

grouping(66) metacharacter ( )(67) - 정규표현식의 일부분을 하나의 유닛으로 최급

/(a|b)b/;    # matches 'ab' or 'bb'
/(ac|b)b/;   # matches 'acb' or 'bb'
/(^a|b)c/;   # matches 'ac' at start of string or 'bc' anywhere
/(a|[bc])d/; # matches 'ad', 'bd', or 'cd'

/house(cat|)/;  # matches either 'housecat' or 'house'
/house(cat(s|)|)/;  # matches either 'housecats' or 'housecat' or
                    # 'house'.  그룹은 중첩될 수 있다.

/(19|20|)\d\d/;  # 년도를 나타내는 19xx, 20xx, 또는 xx에 매치
"20" =~ /(19|20|)\d\d/;  # "20"은 null alternative '()\d\d'에 매치된다,
                         # 왜냐하면 '20\d\d'은 매치될 수 없기 때문이다.

한 후보가 매치되는지 살펴보고, 매치되지 않을 경우, 스트링에서 그 후보를 테스트했던 지점으로 되돌아와서 다음 후보를 테스트하기 위해 준비하는 과정을 backtracking이라고 부른다. 이 용어는 정규식을 매치시키는 과정이 숲에서 걷는 것과 비슷하다는 데에서 나왔다. 정규식을 성공적으로 매치하는 것은 목적지에 도달하는 것과 같다. 숲에서는 수많은 시작지점이 있고 각각은 스트링의 각 위치에 대응되며, 왼쪽에서 오른쪽 순서로 테스트된다. 각 시작점마다 수많은 경로가 있을 수 있고, 그 중 일부는 목적지에 이어지며, 일부는 막다른 길이다. 한 길을 따라 걷다가 막다른 길에 도달하면, 그 길을 따라 되돌아나와(backtrack) 시작지점으로 와서 다음 길을 시도해본다. 목적지에 도달할 경우, 그 즉시 나머지 경로에 대해서는 잊어버린다. 모든 시작점에서 출발하는 모든 경로를 다 시도해보고 끝내 목적지에 도달하지 못했을 경우 실패를 선언한다.

정규표현식을 매칭하는 단계별 분석:

"abcde" =~ /(abd|abc)(df|d|de)/;

이 과정에서 눈여겨 볼 것들.

1.1.5. 매치된 부분 추출 Extracting matchers

( )(68)의 또다른 용도: 매치된 부분을 추출해낸다. 각 괄호에 해당하는 값이 matching variable(69) $1(70),$2, ...에 들어감.

# 시, 분, 초를 추출
if ($time =~ /(\d\d):(\d\d):(\d\d)/) {  # match hh:mm:ss format
    $hours = $1;
    $minutes = $2;
    $seconds = $3;
}
=~(71) 구문은 scalar context(72)에서는 참/거짓을 반환하며 list context(73)에서는 매치 변수들의 리스트를 반환한다. 따라서 다음과 같이 할 수 있다:
# 시, 분, 초를 추출
($hours, $minutes, $second) = ($time =~ /(\d\d):(\d\d):(\d\d)/);

그룹이 중첩되는 경우, 제일 왼쪽에 있는 여는 괄호에 의해 생긴 그룹이 $1에 대응되고, 두번째로 나오는 여는 괄호가 $2에 대응된다.

    /(ab(cd|ef)((gi)|j))/;
     1  2      34

$+(74)$^N(75)

주인장 추가:

테스트 결과, 위의 예제에서 $4가 undef일 경우 $+는 $3에 대응된다. 다만 상황에 따라 매치 변수의 값이 빈 스트링 ""일 때도 있고 이 때는 $+도 이 변수에 대응된다. undef인 경우와 구분해야 함

추가로 작성해 본 예문

$time = "12:34:56";
$time =~ /(\d\d):(\d\d):((\d)\d)/;
          1      2      34
print "$1-$2-$3-$+-$^N";           # 12-34-56-5-56

1.1.6. 백레퍼런스 Backreferences

backreference(76) \g1(77), \g2... - 정규표현식 내부에서 쓸 수 있는 매칭변수. 앞에 무엇이 매치되었느냐에 따라 뒤에 매치할 것이 달라지게 만들 수 있다.

주인장 추가: 이전 버전에서는 \1, \2 이런 식이었는데, Perl 5.14.0(78) 문서에서부터 표기법이 이렇게 바뀌었다. \g{N} 형태 자체는 Perl 5.10.0(79)에서부터 생겼음[2]

3글자짜리 단어가 공백을 사이에 두고 두 번 연속으로 나오는 경우에 매치되는 정규식:

/\b(\w\w\w)\s\g1\b/;    # "hey hey" "wow wow" 등에 매치

같은 말이 두 번 반복된 단어를 골라내는 예:

% simple_grep '^(\w\w\w\w|\w\w\w|\w\w|\w)\g1$' /usr/dict/words
beriberi
booboo
coco
mama
murmur
papa

$1,$2,..는 정규표현식 바깥에서만, \1,\2,..는 정규표현식 안에서만 사용할 것.

1.1.7. 상대적 백레퍼런스 Relative backreferences

여는 괄호를 앞에서부터 세어서 백레퍼런스의 번호를 매기는 것은, 괄호를 사용한 캡처 그룹 둘 이상 있으면 오류를 범하기 쉽다. Perl 5.10.0(80)부터는 상대적 백레퍼런스(relative backreference(81)를 사용할 수 있음.

현재 위치에서 바로 앞에 있는 캡처 그룹을 가리킬 때는 \g{-1}(82)로 표기. 그 앞에 있는 것은 \g{-2}, 등.

가독성 뿐 아니라 코딩 시의 에러도 줄일 수 있다. 다음 패턴을 살펴보자:

$a99a = '([a-z])(\d)\g2\g1'; # a11a, g22g, x33x, 등에 매치됨

패턴을 간편하게 스트링으로 변수에 저장했으니, 다른 패턴의 일부로 사용을 할 수 있다:

$line = "code=e99e";
if ($line =~ /^(\w+)=$a99a$/){      # 기대와 다르게 동작!
        print "$1 is valid\n";
} else {
        print "bad line: '$line'\n";
}

위 코드는 의도한 대로 동작하지 않는다. (\w+)가 1번 그룹이 되어 버리고 $a99a에 있는 그룹들은 번호가 1씩 뒤로 밀린다. 상대적 백레퍼런스를 사용하면 이 문제를 피할 수 있다:

$a99a = '([a-z])(\d)\g{-1}\g{-2}';  # 다른 곳에 삽입되어도 안전함

1.1.8. 이름을 붙인 백레퍼런스 Named backreferences

Perl 5.10.0(83)부터 named backreference(84) 지원

세 가지 패턴 yyyy-mm-dd, mm/dd/yyyy, dd.mm.yyyy 로 주어지는 달력의 날짜를 매치하여 년월일을 추출하는 예:

$fmt1 = '(?<y>\d\d\d\d)-(?<m>\d\d)-(?<d>\d\d)';
$fmt2 = '(?<m>\d\d)/(?<d>\d\d)/(?<y>\d\d\d\d)';
$fmt3 = '(?<d>\d\d)\.(?<m>\d\d)\.(?<y>\d\d\d\d)';
for my $d qw( 2006-10-21 15.01.2007 10/31/2005 ){
        if ( $d =~ m{$fmt1|$fmt2|$fmt3} ){
                print "day=$+{d} month=$+{m} year=$+{y}\n";
        }
}

1.1.8.1. 이름을 붙인 백레퍼런스 보충

자동으로 부여되는 번호와 달리, 프로그래머가 이름을 붙이면 다음 두 가지 문제를 생각해 볼 수 있다.

이 두 사안에 대해서 프로그래밍 언어나 라이브러리에 따라 다르게 처리한다. 자세한 것은 [Regex Tutorial - Named Capturing Groups - Backreference Names] 참고. Perl의 경우는 Perldoc:perlre 에서 짧게 언급하고 있다.

첫번째 문제의 경우, 두번 이상 사용된 이름을 가리키는 백레퍼런스는, "정의된 그룹 중 가장 왼쪽에 있는 그룹"을 가리키게 된다.

'abcd' =~ /(a)(?<x>b)?(c)(?<x>d)/;
print "x[$+{x}]\n";                 # "b"

'acd' =~ /(a)(?<x>b)?(c)(?<x>d)/;   # 여기선 두번째 그룹이 매치가 안 되고, 해당 캡처 그룹에 대한 레퍼런스는 정의되지 않음.
print "x[$+{x}]\n";                 # "d"

두번째 문제의 경우, Perl에서는 이름이 붙은 그룹도 이름이 없는 그룹들과 함께 처리되어 왼쪽에서부터 번호가 부여된다. (위 참고 링크에 따르면 .NET 스타일 정규식의 경우 일단 이름없는 그룹들부터 번호를 부여한 후, 그 다음 이름 붙은 그룹들 번호를 매긴다고)

'acd' =~ /(a)(?<x>b)?(c)(?<x>d)/;
print "[$1][$2][$3][$4]\n";      # [a][b][c][d], .NET스타일 정규식에선 [a][c][b][d]

1.1.9. 캡처 그룹 번호 리셋 Alternative capture group numbering

캡처 그룹에 번호를 매기는 또다른 방법. 역시 Perl 5.10.0(89)에서 추가됨. 여러 후보 패턴들의 집합 안에 있는 그룹을 가리킬 때 요긴. (branch-reset group 이라고도 부름)

민간(hh:mm)또는 군대(hhmm) 스타일로 된 시각을 매치하는 패턴의 예:

if ( $time =~ /(\d\d|\d):(\d\d)|(\d\d)(\d\d)/ ){
        # 시와 분을 추출하여 처리
}
위 코드에서는, $1과 $2, 또는 $3과 $4 중 어느쪽이 데이타를 담고 있는지를 검사하기 위해 추가로 if 구문이 필요하다. 두번째 후보에 있는 그룹에도 번호1과 2를 부여할 수 있으면 편할 것이다.

(?|...)(90) 내에 있는 각 캡처 그룹은, 각 후보들마다 동일한 번호로 시작한다. 그룹이 끝난 후에는, 후보들 중 가장 번호가 많이 부여된 그룹의 번호 다음번호부터 부여된다.

if ( $time =~ /(?|(\d\d|\d):(\d\d)|(\d\d)(\d\d))\s+([A-Z][A-Z][A-Z])/ ){
        print "hour=$1 minute=$2 zone=$3\n";
}

1.1.9.1. 그룹 번호 리셋 내용 보충

주인장 보충:

# before  ---------------branch-reset----------- after
/ ( a )  (?| x ( y ) z | (p (q) r) | (t) u (v) ) ( z ) /x
# 1            2         2  3        2     3     4

/
(?|
        (Buster)       |  # $1, $2는 undef
        (Mimi)(Roscoe) |  # $1, $2
        (Ella)            # $1, $2는 undef
)
(                         # $3
        Ginger
)
/x

그런데 5.14.0(91) 이전 버전에는 버그가 있다.

아래 코드는 단지 그룹 내 부분 그룹의 순서를 바꿨을 뿐인데, 결과가 달라진다.

while (1) {
    my $input = <STDIN>;
    chomp $input;
    if ( $input =~ /(?|(a)(b)|(c))(d)/ ) {
#    if ( $input =~ /(?|(c)|(a)(b))(d)/ ) {
        print "1[$1] 2[$2] 3[$3]\n";
    }
}

(첫번째 if 문을 사용했을 때)
abd
1[a] 2[b] 3[d]
cd
Use of uninitialized value $2 in concatenation (.) or string at d:\Temp\test.pl line 13, <STDIN> line 2.
1[c] 2[] 3[d]

(두번째 if 문을 사용했을 때)
abd
Use of uninitialized value $3 in concatenation (.) or string at d:\Temp\test.pl line 13, <STDIN> line 1.
1[a] 2[d] 3[]
cd
Use of uninitialized value $3 in concatenation (.) or string at d:\Temp\test.pl line 13, <STDIN> line 2.
1[c] 2[d] 3[]

이 문제를 피하려면, 그룹에 부여되는 번호가 가장 큰 (즉 캡처 그룹의 수가 가장 많은) 후보를 제일 마지막에 쓰면 안 된다. 또는 빈 그룹 ()을 채워넣어서 모든 후보의 그룹 갯수를 똑같이 맞춰주거나.

5.13.11에서 고쳐졌다고 함. (thanks to Twitter:am0c)

참고링크:

1.1.10. 매치 위치 정보 Position information

매치되는 위치를 추출하는 방법: @-(92), @+(93)

$x = "Mmm...donut, thought Homer";
$x =~ /^(Mmm|Yech)\.\.\.(donut|peas)/;
foreach $expr (1..$#-) {
    print "Match $expr: '${$expr}' at position ($-[$expr],$+[$expr])\n";
}

출력:
Match 1: 'Mmm' at position (0,3)
Match 2: 'donut' at position (6,11)

$`(96) $&(97) $'(98) - 패턴 일치 후 다음 변수를 써서, 일치된 부분과 그 앞뒤의 부분을 얻어낼 수 있다.

$x = "the cat caught the mouse";
$x =~ /cat/;  # $` = 'the ', $& = 'cat', $' = ' caught the mouse'
$x =~ /the/;  # $` = '', $& = 'the', $' = ' cat caught the mouse'

1.1.11. 캡처되지 않는 그룹 Non-capturing groupings

non-capturing(104) : (?:regexp)(105)

그룹 내의 정규식을 하나의 유닛으로 처리하게 하는 것은 같지만, 캡처 그룹을 생성하지 않는다. 즉 매치된 내용이 추출되지 않으며 $1등의 matching variable(106)에 할당되지도 않는다. 따라서 속도가 더 빠르고, 정규식 내에서 추출되어야 할 부분과 아닌 부분을 정확히 표기하는데도 유용하다:

    # 숫자에 매치, $1-$4가 설정되지만, 우리는 단지 $1만 필요
    /([+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)/;
    # 숫자에 매치, $1만 설정됨
    /([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/;
    # 숫자에 매치, $1 = 전체 숫자, $2 = 지수부
    /([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE]([+-]?\d+))?)/;

split(107)에서 grouping(108)이나 alternation(109)을 위해 괄호를 사용하면서 구분자 자체를 배열에 포함하지 않게 하는 데도 쓰임

        $x = '12aba34ba5';
        @num = split /(a|b)+/, $x;    # @num = ('12','a','34','a','5')
        @num = split /(?:a|b)+/, $x;  # @num = ('12','34','5')

extended pattern(110)과 같이 사용: (?i-m:regexp) 등

1.1.12. 반복 매치 Matching repetitions

quantifier(111) metacharacter(112) : ?, *, +, { }

/[a-z]+\s+\d*/;  # 소문자로 된 단어, 하나 이상의 공백, 그 다음 임의 갯수의 숫자들
/(\w+)\s+\g1/;   # 임의 글자의 단어가 두 번 반복되는 형태
/y(es)?/i;       # 'y', 'Y', 또는 대소문자 구분없이 'yes'에 매치
$year =~ /^\d{2,4}$/;        # 년도는 최소 2자리 최대 4자리
$year =~ /^\d{4}$|^\d{2}$/;  # 더 나은 형태. 3자리 숫자는 매치되지 않게 함
$year =~ /^\d{2}(\d{2})?$/;  # 다른 형태. 이 경우는 마지막 두 자리가 $1에 캡처됨

% simple_grep '^(\w+)\g1$' /usr/dict/words   # 더 쉬워졌죠?
beriberi
booboo
coco
mama
murmur
papa

이 수량자들은 maximal match(119) (or, greedy(120)) quantifier 이다 - 전체 정규식 매치가 성공할 수 있는 선에서, 가능한 가장 긴 스트링(121)에 매치된다.

다음 두 매치를 비교:

$x = "the cat in the hat";
$x =~ /^(.*)(cat)(.*)$/; # 매치됨,
                         # $1 = 'the '
                         # $2 = 'cat'
                         # $3 = ' in the hat'

$x = "the cat in the hat";
$x =~ /^(.*)(at)(.*)$/;   # 매치됨,
                          # $1 = 'the cat in the h'
                          # $2 = 'at'
                          # $3 = ''   (0개의 캐릭터가 매치)

정규표현식이 둘 이상의 방식으로 매치가 가능할 때는, 아래 원칙에 따라 어떤 방식이 선택될지 알 수 있다.

예제:

$x = "The programming republic of Perl";
$x =~ /^(.+)(e|r)(.*)$/;  # matches,
                          # $1 = 'The programming republic of Pe'
                          # $2 = 'r'     (e|r)에서 r이 사용된다.
                          # $3 = 'l'
정규식은 'T'에서부터 매치되고, 첫번째 수량자 부분이 가장 긴 스트링을 사용하게 되므로 두번째 그룹에서 왼쪽 후보인 e가 아니라 r이 매치된다.

$x =~ /(m{1,2})(.*)$/;  # matches,
                        # $1 = 'mm'
                        # $2 = 'ing republic of Perl'
'programming'의 첫번째 'm'에서부터 매치가 되고, m{1,2}는 최대로 매치할 수 있는 mm에 매치된다.

$x =~ /.*(m{1,2})(.*)$/;  # matches,
                          # $1 = 'm'
                          # $2 = 'ing republic of Perl'
이 경우는 스트링의 시작부분부터 매치되고, 첫번째 수량자 .*가 최대한 많이 사용하므로 두번째 수량자 m{1,2}에는 한 글자 'm'만 매치된다.

$x =~ /(.?)(m{1,2})(.*)$/;  # matches,
                            # $1 = 'a'
                            # $2 = 'mm'
                            # $3 = 'ing republic of Perl'
.?는 매치가 가능한 위치에서 최댓값인 한 글자를 사용하므로 'programming'의 'a'에 매치가 되고, m{1,2}는 두 m에 매치된다.

"aXXXb" =~ /(X*)/; # 매치됨, $1 = ''
X*는 스트링의 시작 부분에서 'X'의 0회 반복에 매치되어 버린다. 최소 하나의 'X'에 매치되길 원하면 X+를 사용하라.

minimal match(125) (or, non-greedy(126)) quantifier - 일치하는 가장 짧은 스트링(127)에 매치

minimal quantifier 예:

$x = "The programming republic of Perl";
$x =~ /^(.+?)(e|r)(.*)$/; # matches,
                          # $1 = 'Th'
                          # $2 = 'e'
                          # $3 = ' programming republic of Perl'
스트링의 시작 ^와 두번째 그룹의 alternation을 만족시킬 수 있는 최소 길이 스트링은 Th이고, e|re가 매치되고, 마지막 .*는 나머지 스트링 전부를 사용한다.

$x =~ /(m{1,2}?)(.*?)$/;  # matches,
                          # $1 = 'm'
                          # $2 = 'ming republic of Perl'
첫번째 'm'부터 매치되고, m{1,2}?는 하나의 'm'만 매치된다. 두번째 수량자 .*?는 0개의 캐릭터에 매치되는 걸 더 원했겠지만, 여기서는 $(134) 때문에 제약을 받아 나머지 스트링 전부에 매치된다.

$x =~ /(.*?)(m{1,2}?)(.*)$/;  # matches,
                              # $1 = 'The progra'   (.*?)이지만 $1=''가 아니다!! 원칙0이 적용
                              # $2 = 'm'
                              # $3 = 'ming republic of Perl'
이 경우, ^앵커가 없기 때문에 첫번째 minimal quantifier .*?가 (첫번째 'm'바로 앞에 있는) 빈 스트링에 매치될 거라 기대했을 수 있지만, 원칙0이 적용된다. 전체 정규식을 스트링의 제일 첫부분부터 매치시키는 게 가능하기 때문에 실제로 그렇게 매치시키고, 첫번째 수량자는 첫번째 'm'바로 앞 부분까지 스트링 전부에 대응된다. 두번째 수량자는 하나의 'm'에 매치되고, 세번째 것은 나머지 스트링 전부에 매치된다.

$x =~ /(.??)(m{1,2})(.*)$/;  # matches,
                             # $1 = 'a'
                             # $2 = 'mm'
                             # $3 = 'ing republic of Perl'
직전 예와 마찬가지로, .??가 매치될 수 있는 가장 앞부분이 'a'이므로 거기에 매치된다.

non-greedy quantifier 를 고려하여, 원칙3을 다음과 같이 수정할 수 있다:

alternation처럼 quantifier도 backtracking에 연관된다. 다음 예제 참고

$x = "the cat in the hat";
$x =~ /^(.*)(at)(.*)$/; # matches,
                        # $1 = 'the cat in the h'
                        # $2 = 'at'
                        # $3 = ''   (0 matches)

대부분의 경우 이러한 정규식 매칭 과정은 매우 빠르게 수행되지만, 정확한 숫자가 지정되지 않은 quantifier(135) (*, +, {n,m}등) 가 중첩되어 사용될 경우, 검색 시간은 스트링(136) 길이의 지수함수로 증가한다. 따라서 사용을 자제할 것.

/(a|b+)*/;

1.1.13. 독점적 수량자 Possessive quantifiers

매치될 부분을 찾기 위해 반복적으로 백트래킹하는 것은 시간낭비일 수 있다. 특히 매치가 실패할 운명일 때 더욱 그렇다. 다음 예를 보자:

/^\w+\s+\w+$/; # 단어, 공백, 단어

우리가 찾고자 하는 패턴에 해당되지 않는 "abc ""abcdef " 등에 이 정규식을 적용할 경우, 스트링의 각 캐릭터마다 백트랙하게 될 것이다. 그러나 절대 매치되지 않을 것을 알고 있다.

5.10.0(137)부터는 possessive(138) quantifier 제공:

독점적 수량자는 뒤에 나올 independent subexpression 중 특수한 경우를 나타낸다.

독점적 수량자를 쓰는 적합한 예로, 따옴표로 둘러쌓인 인용문을 매치하는 경우를 생각할 수 있다. 이 때 백슬래쉬는 자기 다음 문자가 다른 문자들처럼 문자 그대로 해석되도록 이스케이프한다. 따라서, 여는 따옴표 뒤에, 이스케이프되지 않은 따옴표나 백슬래쉬를 제외한 나머지 문자와, 이스케이프 캐릭터가 각각 후보인 alternative가 (0번 이상) 반복되는 형태로 생각될 수 있다.

/"(?:[^"\\]++|\\.)*+"/;

1.1.13.1. 독점적 수량자 내용 보충
주인장 추가:

possessive quantifier에 대해 perlretut 문서의 내용이 잘 와닿지 않는다. 다음 두 문서를 참고하면 좋다.

일단, 독점 수량자는 greedy한 수량자와 서로 다른 매치 결과를 낼 수 있으니 주의하라:

'aaaa' =~ /a++a/

독점적 수량자가 유용한 경우의 예는 다음과 같다.

'aaaa' =~ /a++b/

따라서 독점적 수량자를 사용할 경우에는, 독점적 수량자를 적용하는 부분의 패턴이, 그 뒷부분에 있는 패턴과 매치되지 않는 게 확실한지 확인해야 한다. 위 예에서는 a++a가 뒤에 있는 패턴 b와 일치될 수 없다. 즉, 'a++을 매치시키는 동안, 실수로 스트링 내에 있는 'b'를 잡아먹어 버렸을 리가 없다'는 것이다. 반면 .++b 같은 패턴은 .b에도 매치되므로 의도하지 않은 결과를 낼 수 있다.

1.1.14. 정규표현식 구성하기 Building a regexp

"수"에 매치되는 정규표현식을 작성하는 예

먼저, 매치시키고 싶은 것과 제외하고 싶은 것을 결정한다.

문제를 단순화 시킨다.

        /[+-]?\d+/;  # 정수에 매치
       /[+-]?\d+\./;     # 1., 321., etc.
       /[+-]?\.\d+/;     # .1, .234, etc.
       /[+-]?\d+\.\d+/;  # 1.0, 30.56, etc.

세 가지 후보를 결합하여 alternation(145) 형태로 만들 수 있다.

       /[+-]?(\d+\.\d+|\d+\.|\.\d+)/;  # 지수부가 없는 부동소수
                                       # "\d+\.\d+"가 제일 앞에 와야만 한다.

지수가 있는 경우를 고려한다. 지수부는 정수와 소숫점이 있는 소수 양쪽에 다 붙을 수도 있다. 따라서 앞에 매치되는 숫자에 소숫점이 있는지 없는지에 무관하며, 유효숫자부와 별개로 처리할 수 있다.

        /^(optional sign)(integer | f.p. mantissa)(optional exponent)$/;

지수부의 형태는 e 또는 E 뒤에 정수가 오는 것이다.

       /[eE][+-]?\d+/;  # 지수부

따라서 전체 정규표현식은

       /^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/;  # 짠~

위와 같이 복잡한 정규식은, //x(146) modifier(147)를 사용하여 공백과 주석을 추가하여 읽기 쉽게 만들 수 있다.

       /^
          [+-]?         # 먼저, 부호에 매치
          (             # 그 다음 정수 또는 소수의 유효숫자부에 매치
              \d+\.\d+  # a.b 형태의 유효숫자
             |\d+\.     # a. 형태의 유효숫자
             |\.\d+     # .b 형태의 유효숫자
             |\d+       # a 형태의 정수
          )
          ([eE][+-]?\d+)?  # 끝으로, 추가적으로 올 수 있는 지수부에 매치
       $/x;

이 때 정규식 내의 스페이스를 넣고 싶으면 \ 또는 [ ]로 표기한다. 마찬가지로 #은 각각 \# 또는 [#]으로 표기한다.

       /^
          [+-]?\ *      # 먼저, 부호와 *공백*에 매치
          (             # 그 다음 정수 또는 소수의 유효숫자부에 매치
              \d+\.\d+  # a.b 형태의 유효숫자
             |\d+\.     # a. 형태의 유효숫자
             |\.\d+     # .b 형태의 유효숫자
             |\d+       # a 형태의 정수
          )
          ([eE][+-]?\d+)?  # 끝으로, 추가적으로 올 수 있는 지수부에 매치
       $/x;

alternation 그룹의 1,2,4번째 후보는 다 \d+로 시작하므로, 이것을 뽑아내어 더 단순하게 만들 수 있다.

       /^
          [+-]?\ *      # 먼저, 부호와 공백에 매치
          (             # 그 다음 정수 또는 소수의 유효숫자부에 매치
              \d+       # a로 시작하면서...
              (
                  \.\d* # a.b 또는 a. 형태의 유효숫자
              )?        # ?는 a 형태의 정수가 오는 경우를 고려한 것
             |\.\d+     # .b 형태의 유효숫자
          )
          ([eE][+-]?\d+)?  # 끝으로, 추가적으로 올 수 있는 지수부에 매치
       $/x;

또는 압축된 형태로 다시 쓸 수 있다:

        /^[+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;

이렇게 해서 정규식을 완성하였다. 다시 정리하면 정규식을 만들기 위하여 다음의 과정을 거쳤다.

이 과정들은 컴퓨터 프로그램을 작성할 때도 거치게 되는 과정이다. 완전히 통하는 면이 있는 것이, 정규표현식은 본질적으로 패턴을 명세하는 용도의 컴퓨터 언어로 짠 프로그램이기 때문이다.

1.1.15. Using regular expression in Perl

정규표현식을 사용하는 데 관련된 Perl의 문법들

현재까지 다룬 내용

이 섹션의 남은 부분에서는 매칭 연산자에 대해 추가로 알아둘 만한 것들을 다룬다.

1.1.15.1. 치환 방지 Prohibiting substitution

변수 치환이 전혀 일어나지 않게 하고 싶으면, 특별한 구분자인 m'regexp'(158)을 사용하라:

        @pattern = ('Seuss');
        while (<>) {
            print if m'@pattern';  # 문자 그대로 '@pattern'에 매치된다, 'Seuss'가 아니라.
        }

스트링(159)에서와 마찬가지로 m''는 아포스트로피처럼 동작하고, 그 외 다른 m구분자들은 따옴표처럼 동작한다.

정규표현식을 평가했더니 빈 스트링인 경우, 마지막으로 매치에 성공했던 정규식이 대신 사용된다1

        "dog" =~ /d/;     # 'd'에 매치됨
        "dogbert" =~ //;  # 이전에 사용되었던 'd' 정규식에 매치됨

1.1.15.2. 글로벌 매칭 Global matching

//g(160) //c(161) modifier - 매칭을 반복하는 동작

공백으로 구분된 여러 단어로 이루어진 스트링이 있다고 하자. 단어의 갯수를 알면 그룹을 써서 각 단어들을 추출할 수 있다:

        $x = "cat dog house"; # 3 words
        $x =~ /^\s*(\w+)\s+(\w+)\s+(\w+)\s*$/; # matches,
                                               # $1 = 'cat'
                                               # $2 = 'dog'
                                               # $3 = 'house'

단어의 갯수를 모른다면? 단순한 정규식 (\w+)를 구성한 후 /(\w+)/g를 사용하여 매치되는 모든 곳에 대해 루프를 돌아라:

        while ($x =~ /(\w+)/g) {
            print "Word is $1, ends at position ", pos $x, "\n";
        }

출력은 다음과 같다

        Word is cat, ends at position 3
        Word is dog, ends at position 7
        Word is house, ends at position 13

매치에 실패하거나 타겟스트링이 바뀌면 포지션은 리셋된다2. 매치에 실패해도 리셋되지 않게 하려면 /regexp/gc와 같이 //c(164) modifier를 같이 사용한다. 현재 포지션 값은 정규식이 아니라 각 스트링에 결합되어 있다. 서로 다른 스트링은 서로 다른 포지션 값을 갖고 있고, 각각을 독립적으로 설정하거나 읽을 수 있다.

list context(165)에서 //g(166)는 매치된 그룹들의 리스트를 반환한다. 패턴 내에 그룹이 없을 경우 전체 정규식에 일치하는 문자열의 리스트를 반환한다.

        @words = ($x =~ /(\w+)/g);  # matches,
                                    # $word[0] = 'cat'
                                    # $word[1] = 'dog'
                                    # $word[2] = 'house'

\G(167) - 직전에 사용된 //g(168)에 의해 일치한 부분의 끝에 일치한다. 현재는 정규식의 제일 첫 부분에 사용되었을 때만 제대로 지원이 된다.

\G를 써서 문맥에 의존하는 매칭을 할 수 있다:

        $metric = 1;              # 미터법을 쓰도록 설정
        ...
        $x = <FILE>;              # 측정값을 읽음
        $x =~ /^([+-]?\d+)\s*/g;  # 수치 부분을 읽음
        $weight = $1;
        if ($metric) {            # 에러 체크
            print "Units error!" unless $x =~ /\Gkg\./g;
        }
        else {
            print "Units error!" unless $x =~ /\Glbs\./g;
        }
        $x =~ /\G\s+(widget|sprocket)/g;  # 계속 진행

//g\G를 조합하여, 스트링의 일부를 먼저 처리하고 그 다음 무엇을 할 것인지 Perl 논리구조를 사용하여 결정할 수 있다.

\G는 또한 고정된 길이의 레코드를 정규식으로 처리할 때 유용하다. DNA 영역에서 TGA 코돈(codon, 3-문자 순열)을 모두 찾는 경우의 예: DNA 단편을 3글자짜리 레코드들의 순열로 생각할 수 있다.

        # expanded, this is "ATC GTT GAA TGC AAA TGA CAT GAC"
        $dna = "ATCGTTGAATGCAAATGACATGAC";
        $dna =~ /TGA/;
위 매치는 아무 위치에서나 TGA에 매치되므로 안 된다.

개선된 풀이는

        while ($dna =~ /(\w\w\w)*?TGA/g) {  # note the minimal *?
            print "Got a TGA stop codon at position ", pos $dna, "\n";
        }

다음과 같이 출력된다:

        Got a TGA stop codon at position 18
        Got a TGA stop codon at position 23

18번째 캐릭터 포지션은 맞다. 그런데 23번은 틀렸다. 위의 정규식은 마지막으로 제대로 매치된 시점 이후부터 제대로 동작하지 않는다. 그 이후 정규식이 TGA 코돈에 매치에 실패하고, 스트링의 포지션을 한 글자 이동하여 재시도하게 된다. 이 문제를 해결하는 방법은 \G를 사용하여 코돈 경계 부분에 앵커를 설정하는 것이다:

        while ($dna =~ /\G(\w\w\w)*?TGA/g) {
            print "Got a TGA stop codon at position ", pos $dna, "\n";
        }
출력:
        Got a TGA stop codon at position 18

위의 예제는, 원하는 것에 매치하는 것 뿐 아니라 원하지 않는 것을 매치되지 않게 하는 것도 중요함을 보여준다.

(//o(169), //d(170), //l(171) 등 정규식에서 사용 가능한 modifier가 몇 개 더 있으나, 이 문서의 범위를 벗어난다)

1.1.15.3. 검색과 치환 Search and replace

정규표현식은 검색과 치환 연산을 할 때도 큰 역할을 한다.

s///(172) - s/regexp/replacement/modifier

        $x = "Time to feed the cat!";
        $x =~ s/cat/hacker/;   # $x 는 "Time to feed the hacker!"
        if ($x =~ s/^(Time.*hacker)!$/$1 now!/) {
            $more_insistent = 1;
        }
        $y = "'quoted words'";
        $y =~ s/^'(.*)'$/$1/;  # 작은따옴표를 벗겨냄
                               # $y 는 "quoted words"

global modifier인 s///g(176)를 사용하여 스트링 내에 regexp에 매치되는 모든 부분을 치환

        $x = "I batted 4 for 4";
        $x =~ s/4/four/;   # 한번만 치환:
                           # $x 는 "I batted four for 4"
        $x = "I batted 4 for 4";
        $x =~ s/4/four/g;  # 전부 치환:
                           # $x 는 "I batted four for four"

이 튜토리알 문서에서 'regexp'라는 표현보다 'regex'가 더 좋다면, 다음 프로그램을 써서 치환할 수 있다3:

% cat > simple_replace
#!/usr/bin/perl
$regexp = shift;
$replacement = shift;
while (<>) {
    s/$regexp/$replacement/g;
    print;
}
^D

% simple_replace regexp regex perlretut.pod

비파괴 치환 수정자(non-destructive substitute modifier) s///r(177)을 사용하여, s///가 원본 변수를 수정해버리는 것을 막을 수 있다. s///r은 치환된 횟수를 반환하는 것이 아니라 최종적으로 치환된 스트링을 반환한다.

        $x = "I like dogs.";
        $y = $x =~ s/dogs/cats/r;
        print "$x $y\n";

아무것도 치환되지 않았다면 원본 스트링이 반환된다:

        $x = "I like dogs.";
        $y = $x =~ s/elephants/cougars/r;
        print "$x $y\n"; # prints "I like dogs. I like dogs."

s///r을 써서 연쇄 치환을 할 수 있다:

        $x = "Cats are great.";
        print $x =~ s/Cats/Dogs/r =~ s/Dogs/Frogs/r =~ s/Frogs/Hedgehogs/r, "\n";
        # prints "Hedgehogs are great."

치환 연산에서만 사용가능한 evaluation modifier s///e(178)

한 라인에서 문자들의 등장 빈도를 세는 예:

        $x = "Bill the cat";
        $x =~ s/(.)/$chars{$1}++;$1/eg;  # 마지막 $1는 각 문자를 그 문자 자신으로 치환
        print "frequency of '$_' is $chars{$_}\n"
            foreach (sort {$chars{$b} <=> $chars{$a}} keys %chars);
출력:
        frequency of ' ' is 2
        frequency of 't' is 2
        frequency of 'l' is 2
        frequency of 'B' is 1
        frequency of 'c' is 1
        frequency of 'e' is 1
        frequency of 'h' is 1
        frequency of 'i' is 1
        frequency of 'a' is 1

s///m//처럼 다양한 구분자를 쓸 수 있다.

1.1.15.4. split 연산자 The split operator

split(183) 함수에서도 정규표현식이 사용된다.

        $x = "Calvin and Hobbes";
        @words = split /\s+/, $x;  # $word[0] = 'Calvin'
                                   # $word[1] = 'and'
                                   # $word[2] = 'Hobbes'
        $x = "/usr/bin/perl";
        @dirs = split m!/!, $x;  # $dirs[0] = ''
                                 # $dirs[1] = 'usr'
                                 # $dirs[2] = 'bin'
                                 # $dirs[3] = 'perl'
        @parts = split m!(/)!, $x;  # $parts[0] = ''
                                    # $parts[1] = '/'
                                    # $parts[2] = 'usr'
                                    # $parts[3] = '/'
                                    # $parts[4] = 'bin'
                                    # $parts[5] = '/'
                                    # $parts[6] = 'perl'

1.1.15.5. 패턴 평가 최적화 Optimizing pattern evaluation

이 섹션은 5.14문서에서는 삭제되었음. 기록용으로 남겨둠

1.2. Part 2: 강력한 도구들 Power tools

자주 쓰이지는 않으나 더 강력한 기능들.

1.2.1. 캐릭터, 스트링, 캐릭터 클래스 More on characters, strings, and character class

\l(185) \u(186) - 바로 다음 캐릭터를 소문자 또는 대문자로 변환. 패턴 내에서도 사용 가능

        $x = "perl";
        $string =~ /\u$x/;  # $string의 'Perl'에 매치
        $x = "M(rs?|s)\\."; # 백슬래쉬를 두 번 쓴 것에 유의
        $string =~ /\l$x/;  # 'mr.', 'mrs.', 'ms.'에 매치

\L(187) \U(188) - 뒤에 이어지는 (\E(189)를 만나거나, 또다른 \U\L을 만나거나, 그 외에는 스트링의 끝까지) 전체 문자열을 소문자 또는 대문자로 변환

        $x = "This word is in lower case:\L SHOUT\E";
        $x =~ /shout/;       # 매치됨
        $x = "I STILL KEYPUNCH CARDS FOR MY 360"
        $x =~ /\Ukeypunch/;  # matches punch card string

\c(190) - 콘트롤 캐릭터를 이스케이프. (예: Ctrl+Z - \cZ)

\Q(191)...\E(192)

        $x = "\QThat !^*&%~& cat!";
        $x =~ /\Q!^*&%~&\E/;  # check for rough language

"\Q", "\L", "\l", "\U", "\u", "\E"는 사실 큰따옴표 인용 문법의 일부이지, 정규표현식 문법의 일부가 아니다. 이것들이 프로그램에 직접 삽입된 정규식에 나타날 경우는 제대로 동작하겠지만, 스트링 안에 들어 있다가 패턴 내에서 삽입될 경우는 동작하지 않는다.

(주인장 보충:)

바로 위 문단의 말은, (주인장이 제대로 이해했다면), 다음과 같은 코드로 확인할 수 있다:

my $str1 = "\uhello";   # $str1 = 'Hello'
my $str2 = '\uhello';   # $str2 = '\uhello'
say "match" if 'Hello' =~ /$str1/;    # 매치됨
say "match" if 'Hello' =~ /$str2/;    # 런타임 에러
match
Unrecognized escape \u passed through in regex; marked by <-- HERE in m/\u <-- HERE hello/ at ...
$str2 변수 안에 들어있는 "\u"는 정규식에서는 인식하지 못하는 이스케이프 시퀀스로 취급되어 에러가 난다.

perlretut 문서에 위 문단이 추가된 시점은 [여기서 처음], [여기서 개정]

Perl 5.6.0(193)부터는 정규표현식에서 Unicode(194)를 지원

사용자가 알아야 할 것: 1) 정규식에서 유니코드 문자를 표현하는 법 2) 매칭 연산은 검색할 스트링을 바이트가 아니라 캐릭터의 시퀀스로 취급한다는 점

1) 정규식에서 유니코드 문자를 표현하는 법

        /\x{263a}/;  # match a Unicode smiley face :)

16진수로 표현된 유니코드 문자를 보면서 이 문자가 뭔지 파악하는 것이 쉽지 않다. 따라서 유니코드 문자를 "named character" Escape Sequences(199) \N{name}(200)를 사용해 표현할 수 있다. name은 표준에 명시된 유니코드 문자의 이름이다.

   use charnames ":full";   # use named chars with Unicode full names
   $x = "abc\N{MERCURY}def";
   $x =~ /\N{MERCURY}/;     # matches
        use charnames ':full';
        print "\N{GREEK SMALL LETTER SIGMA} is called sigma.\n";

        use charnames ":short";
        print "\N{greek:Sigma} is an upper-case sigma.\n";

        use charnames qw(greek);
        print "\N{sigma} is Greek sigma\n";

2) 매칭 연산이 스트링을 캐릭터의 시퀀스로 간주하는 부분에 대하여:

Unicode(205) 문자들처럼 유니코드 character class(206)도 이름으로 표현할 수 있다.

        use charnames ":full"; # use named chars with Unicode full names
        $x = "BOB";
        $x =~ /^\p{IsUpper}/;   # matches, uppercase char class
        $x =~ /^\P{IsUpper}/;   # doesn't match, char class sans uppercase
        $x =~ /^\p{IsLower}/;   # doesn't match, lowercase char class
        $x =~ /^\P{IsLower}/;   # matches, char class sans lowercase

Perl에서 사용하는 클래스 이름들과, 전통적인 유닉스 클래스들과의 관계:

           Perl 클래스이름   유니코드 클래스 이름 또는 정규 표현식

           IsAlpha          /^[LM]/
           IsAlnum          /^[LMN]/
           IsASCII          $code <= 127
           IsCntrl          /^C/
           IsBlank          $code =~ /^(0020|0009)$/ || /^Z[^lp]/
           IsDigit          Nd
           IsGraph          /^([LMNPS]|Co)/
           IsLower          Ll
           IsPrint          /^([LMNPS]|Co|Zs)/
           IsPunct          /^P/
           IsSpace          /^Z/ || ($code =~ /^(0009|000A|000B|000C|000D)$/
           IsSpacePerl      /^Z/ || ($code =~ /^(0009|000A|000C|000D|0085|2028|2029)$/
           IsUpper          /^L[ut]/
           IsWord           /^[LMN]/ || $code eq "005F"
           IsXDigit         $code =~ /^00(3[0-9]|[46][1-6])$/

공식적인 유니코드 클래스 이름을 \p(209)\P(210)와 같이 사용할 수도 있다.

유니코드는 다양한 문자집합들로 나눌 수 있으며, \p{...}(특정 집합에 포함되는지) 또는 \P{...}(포함되지 않는지)으로 테스트할 수 있다.

\p{...}과 같은 단일 형태 외에, \p{name=value}(211)또는 \p{name:value}(212) 형태의 복합 형태를 사용할 경우도 있다.

\X(213) - 'Unicode extended grapheme cluster'를 포함하는 캐릭터 클래스 시퀀스

유니코드에 대한 최신 정보는 유니코드 표준 문서 또는 유니코드 콘소시엄 웹사이트 http://www.unicode.org/ 참조

Perl에서는 POSIX-style(214) 스타일의 캐릭터 클래스도 정의되어 있음

        /\s+[abc[:digit:]xyz]\s*/;  # match a,b,c,x,y,z, or a digit
        /^=item\s[[:digit:]]/;      # match '=item',
                                    # followed by a space and a digit
        /\s+[abc\p{IsDigit}xyz]\s+/;  # match a,b,c,x,y,z, or a digit
        /^=item\s\p{IsDigit}/;        # match '=item',
                                      # followed by a space and a digit

1.2.2. 정규표현식 컴파일 및 저장 Compiling and saving regular expressions

컴파일된 정규식은 한 번 저장한 후 계속 사용할 수 있는 자료구조이다.

regexp quote qr//(221)

           $reg = qr/foo+bar?/;  # reg는 컴파일된 정규식을 저장
           $x = "fooooba";
           $x =~ $reg;     # 매치됨, /foo+bar?/와 같다
           $x =~ /$reg/;   # 형태만 다르지 동일함
           $x =~ /(abc)?$reg/;  # 여전히 매치됨

다양한 구분자 사용가능

매번 컴파일할 필요 없는 동적인 매칭 연산을 생성할 때 유용

        #!/usr/bin/perl
        # grep_step - match <number> regexps, one after the other
        # usage: grep_step <number> regexp1 regexp2 ... file1 file2 ...

        $number = shift;
        $regexp[$_] = shift foreach (0..$number-1);
        @compiled = map qr/$_/, @regexp;
        while ($line = <>) {
            if ($line =~ /$compiled[0]/) {
                print $line;
                shift @compiled;
                last unless @compiled;
            }
        }
        % grep_step 3 shift print last grep_step
        $number = shift;
                print $line;
                last unless @compiled;

1.2.3. 런타임에 정규표현식 생성하기 Composing regular expressions at runtime

여러 개의 정규식이 있고, 그 중 아무 것에나 매치되면 된다면, 이 정규식들을 여러 후보들(alternatives)의 집합으로 합칠 수 있다. 만일 각각의 정규식이 입력 데이타로 들어온다면, join 연산을 사용하면 될 것이다.

simple_grep의 개선판: 여러 패턴을 매칭하는 프로그램

        #!/usr/bin/perl
        # multi_grep - match any of <number> regexps
        # usage: multi_grep <number> regexp1 regexp2 ... file1 file2 ...

        $number = shift;
        $regexp[$_] = shift foreach (0..$number-1);
        $pattern = join '|', @regexp;

        while ($line = <>) {
            print $line if $line =~ /$pattern/;
        }
        % multi_grep 2 shift for multi_grep
        $number = shift;
        $regexp[$_] = shift foreach (0..$number-1);

가끔은, 분석할 입력을 패턴으로 구성하고, 허용되는 값들을 매칭 연산의 좌변에 두는 게 더 나을 때가 있다. 이렇게 다소 역설적으로 들리는 상황의 예로, 입력으로 명령어를 받고 이 명령어는 사용 가능한 명령어들 중 하나여야 한다고 하자. 또한 명령어들은 혼동의 여지가 없는 한 축약해서 쓸 수 있다고 하자:

        #!/usr/bin/perl
        # keymatch
        $kwds = 'copy compare list print';
        while( $command = <> ){
            $command =~ s/^\s+|\s+$//g;  # 앞뒤에 공백을 제거
            if( ( @matches = $kwds =~ /\b$command\w*/g ) == 1 ){
                print "command: '@matches'\n";
            } elsif( @matches == 0 ){
                print "no such command: '$command'\n";
            } else {
                print "not unique: '$command' (could be one of: @matches)\n";
            }
        }
        % keymatch
        li
        command: 'list'
        co
        not unique: 'co' (could be one of: copy compare)
        printer
        no such command: 'printer'
입력을 각 키워드들에 일일이 매치하지 않고, 키워드들을 합친 집합을 입력에 매치하였다. $kwds =~ /\b($command\w*)/g 연산은 한번에 여러가지 일을 수행한다.

1.2.4. 정규식 내에 주석과 modifier 삽입 Embedding comments and modifiers in a regular expression

Perl의 extended pattern(222) : (?char...) 형태

        /(?# Match an integer:)[+-]?\d+/;

        /(?i)yes/;  # 대소문자 구분 없이 'yes'에 매치
        /yes/i;     # 위와 동일
        /(?x)(          # 정수를 나타내는 정규식
                 [+-]?  # 선택적으로 올 수 있는 부호에 매치
                 \d+    # 숫자의 시퀀스에 매치
             )
        /x;

embedded modifier의 장점:

1) 각각의 정규식 패턴마다 고유의 변경자를 둘 수 있고, 변경자를 동적으로 바꿀 수 있다. 이것은 서로 다른 변경자가 적용되어야 하는 정규식들의 배열을 매칭할 때 아주 유용하다.

        $pattern[0] = '(?i)doctor';
        $pattern[1] = 'Johnson';
        ...
        while (<>) {
            foreach $patt (@pattern) {
                print if /$patt/;
            }
        }

2) 패턴 내의 특정 그룹에만 변경자의 효과를 지정할 수 있다. (//p(234)는 예외. 이것은 전체 정규식에 적용된다)

        /Answer: ((?i)yes)/;  # matches 'Answer: yes', 'Answer: YES', etc.

현재 적용되고 있는 modifier의 효과를 없애기: (?-i)

여러 modifier를 동시에 사용하기: (?s-i)

삽입된 modifier는 non-capturing(235) 그룹이다. 예를 들어 (?i-m:regexp)regexp에 대소문자 구분 없고 multi-line 모드를 끈 상태에서 매치되는, 캡처되지 않는 그룹이다.

주인장 보충 - [Let perl create your regex stringification | The Effective Perler]의 내용

정규표현식을 스트링으로 표현(stringify)할 때, 이전에는 적용 안 된 옵션들을 명시하는 형태였다:

$ perl5.12.2 -le 'print qr/Buster|Mimi/;'
(?-xism:Buster|Mimi)
이 경우 문제점은, Perl 버전이 올라가면서 새로운 변경자가 추가되면, 스트링 표현에 그 변경자가 추가되어야 하므로 동일한 정규표현식이 버전에 따라 서로 다르게 표현될 있다는 점이다

5.14.0부터는 적용되는 옵션들만을 표시하기 때문에 항상 동일하게 표현된다.

$ perl5.13.6 -le 'print qr/Buster|Mimi/i'
(?^i:Buster|Mimi)
프로그래머 입장에서는, 애초에 스트링 표현을 명시적으로 하지 말고 Perl이 하도록 하면 딱히 신경쓰지 않아도 된다

1.2.5. 앞뒤 살펴보기 Looking ahead and looking behind

Perl 정규표현식에서 대부분의 정규식 요소들은 자기가 매치될 때 매치되는 스트링을 '먹어치운다'. 예를 들어 [abc}]는 매치될 때 스트링의 캐릭터 하나를 먹으며, Perl은 매치 후 스트링의 그 다음 캐릭터 위치로 이동한다. 그러나 매치될 때 캐릭터들을 먹지(즉 캐릭터 포지션을 이동시키지) 않는 요소들이 일부 있다. 위에서 봤던 anchor(238)들이 그 예다. 앵커 ^(239)는 라인의 시작 부분에 매치되나, 어떤 캐릭터도 삼키지 않는다. \b(240)도 유사하게 \w에 매치되는 캐릭터와 매치되지 않는 캐릭터가 접한 위치에 매치되지만, 캐릭터를 삼키진 않는다. 앵커는 zero-width(241) assertion(242)의 예이다

숲 속을 산책하는 것에 정규식 매칭을 비유하면 대부분의 정규식 요소들은 경로를 따라 이동하는 것과 같고, 앵커는 잠시 멈춰서 주변을 확인하는 것과 같다. 만일 주변 환경이 테스트에 통과하면 계속 진행할 수 있고, 통과하지 못한다면 백트랙해야 한다.

환경을 체크하는 것은 경로의 앞, 뒤, 또는 양쪽을 다 살펴보는 일을 수반한다. ^는 뒤쪽을 살펴보고 뒤쪽에 아무 캐릭터도 없는지 확인한다. $는 앞쪽을 살펴보고 앞에 캐릭터가 남아 있지 않는 것을 확인한다. \b는 앞뒤를 다 살펴보고, 앞뒤에 있는 캐릭터들이 word인지 여부가 서로 다른지 확인한다.

lookahead와 lookbehind는 앵커 개념을 일반화한 것이며, zero-width assertion으로써 우리가 테스트하려는 캐릭터가 무엇인지 명시할 수 있다.

        $x = "I catch the housecat 'Tom-cat' with catnip";
        $x =~ /cat(?=\s)/;   # matches 'cat' in 'housecat'
        @catwords = ($x =~ /(?<=\s)cat\w+/g);  # matches,
                                               # $catwords[0] = 'catch'
                                               # $catwords[1] = 'catnip'
        $x =~ /\bcat\b/;  # matches 'cat' in 'Tom-cat'
        $x =~ /(?<=\s)cat(?=\s)/; # doesn't match; no isolated 'cat' in
                                  # middle of $x
           $x = "foobar";
           $x =~ /foo(?!bar)/;  # doesn't match, 'bar' follows 'foo'
           $x =~ /foo(?!baz)/;  # matches, 'baz' doesn't follow 'foo'
           $x =~ /(?<!\s)foo/;  # matches, there is no \s before 'foo'

다음 예에서는 한 스트링에 빈칸으로 구분된 단어들, 숫자들, 그리고 한칸짜리 대시들이 포함되어 있고, 이것을 각 성분을 쪼개려 한다. 대시와 대시 사이, 또 대시와 단어 사이에는 공백이 없기 때문에 /\s+/만 사용하는 걸로는 제대로 분리되지 않는다. 분리할 위치를 lookahead와 lookbehind를 사용하여 지정해 주고 있다:

        $str = "one two - --6-8";
        @toks = split / \s+              # 연속된 공백
                      | (?<=\S) (?=-)    # 공백이 아닌 문자와 '-'사이
                      | (?<=-)  (?=\S)   # '-'와 공백이 아닌 문자 사이
                      /x, $str;          # @toks = qw(one two - - - 6 - 8)

1.2.6. 백트래킹을 금지하는 독립적인 부분표현식 Using independent subexpressions to prevent backtracking

(?>regexp)(252)

일반적인 정규표현식과 비교

           $x = "ab";
           $x =~ /a*ab/;  # 매치됨
           $x =~ /(?>a*)ab/;  # 매치되지 않음!
           $x = "ab";
           $x =~ /a*/g;   # 매치됨, 'a'를 먹어버림
           $x =~ /\Gab/g; # 매치되지 않음, 매치할 'a'가 남아있지 않다

독립적 부분표현식이 백트래킹을 방지하는 특성은, 검색 소요 시간을 줄이는 데 유용하다

           $x = "abc(de(fg)h";  # 쌍이 맞지 않는 괄호들
           $x =~ /\( ( [^()]+ | \([^()]*\) )+ \)/x;
           $x =~ /\( ( (?>[^()]+) | \([^()]*\) )+ \)/x;

1.2.7. 조건부 정규표현식 Conditional expressions

conditional expression(256)

condition을 나타내는 여러 가지 형태:

정수 또는 이름으로 된 형태의 조건은, 앞에서 무엇이 매치되었냐에 따라 뒤에서 무엇을 매치할 것인가를 보다 유연하게 선택할 수 있게 한다. 아래의 예는, "$x$x" 또는 "$x$y$y$x" 형태의 단어들을 검색한다:

        % simple_grep '^(\w+)(\w+)?(?(2)\g2\g1|\g1)$' /usr/dict/words
        beriberi
        coco
        couscous
        deed
        ...
        toot
        toto
        tutu

lookbehind 조건은, 백레퍼런스와 같이 사용되어, 앞에서 매치된 내용이 뒤의 매치에 영향을 주게 한다:

        /[ATGC]+(?(?<=AA)G|C)$/;

1.2.7.1. 조건부 표현식 내용 보충

주인장 보충:

[Regex Tutorial - If-Then-Else Conditionals]의 두번째 절에 흥미로운 예제가 있다.

패턴 (a)?b(?(1)c|d)은, 첫번째 그룹 (a)가 매치되면 b 뒤에 c가, 그렇지 않으면 d가 매치된다. 이 때 눈여겨 볼 점은

1.2.8. 패턴 정의하고 이름붙이기 Defining named patterns

어떤 정규표현식은 같은 서브패턴이 여러번 사용된다. Perl 5.10.0(267)부터는 서브패턴을 정의하고 이름을 붙인 후 패턴 내 어느 곳에서든 이름을 사용하여 불러올 수 있게 지원한다.

아래 예는 앞에서 봤던 부동소수를 나타내는 패턴을 이 기능을 사용하여 표현한 것이다. 두 번 이상 사용되는 세 가지 서브 패턴이 있는데 부호, 정수에 사용되는 숫자열, 소수부이다. 패턴 아래쪽의 DEFINE 그룹에서 그 세가지 서브패턴을 정의한다. 소수부 패턴에서는 정수 패턴을 재사용하는 것을 눈여겨보라.

       /^ (?&osg)\ * ( (?&int)(?&dec)? | (?&dec) )
          (?: [eE](?&osg)(?&int) )?
        $
        (?(DEFINE)
          (?<osg>[-+]?)         # 생략 가능한 부호
          (?<int>\d++)          # 정수
          (?<dec>\.(?&int))     # 소수부
        )/x

1.2.9. 재귀적 패턴 Recursive patterns

5.10.0(270)부터 recursive pattern(271) 지원.

(?group-ref)(272)을 사용하여 패턴 내의 다른 캡처 그룹을 참조하면, 참조된 그룹 내의 패턴이 참조하는 자리에서 독립적 서브패턴으로 사용된다. 그룹 레퍼런스는 자기가 참조하는 그 그룹 내에 존재할도 수 있기 때문에, 지금까지 재귀적 해석기(parser)가 필요했던 작업을 패턴 매칭을 적용하여 할 수 있게 되었다.

회문(palindrome)을 포함한 스트링에 매치되는 패턴을 예로 들자. (회문은 공백이나 문장 부호를 무시하였을 때, 앞에서 읽으나 뒤에서 읽으나 똑같은 단어나 문장이다) 먼저 빈 스트링이나, 한 글자짜리 스트링은 회문이다. 그 외의 경우는 가운데 어떤 회문이 있고, 그 회문의 앞과 뒤에 같은 글자가 있어야 한다.

        /(?: (\w) (?...Here be a palindrome...) \g{-1} | \w? )/x

무시해야 될 것들을 제거하기 위해 앞뒤에 \W*만 붙여주면, 전체 패턴이 완성된다:

        my $pp = qr/^(\W* (?: (\w) (?1) \g{-1} | \w? ) \W*)$/ix;
        for $s ( "saippuakauppias", "A man, a plan, a canal: Panama!" ){
            print "'$s' is a palindrome\n" if $s =~ /$pp/;
        }
(?...) 안에는 백레퍼런스의 절대 번호나 상대 번호 둘 다 쓸 수 있다. 전체 패턴은 (?R)(273) 또는 (?0)(274) 으로 표시할 수 있다. 그룹에 이름을 붙였을 경우 (?&name)(275)으로 그 그룹을 재귀호출할 수 있다.

1.2.9.1. 재귀적 패턴 보충

주인장 보충:

재귀적 정규표현식을 사용하는 예를 찾기가 힘든데, 주로 balanced text, 즉 좌우에 대칭되는 표식에 의해 둘러싸여 있으면서, 또다른 balanced text가 중첩될 수 있는 상황을 풀 수 있는 식을 볼 수 있었다.

그러나 일반적으로, 심지어 perlfaq 글에서마저, 애초에 이런 형태의 텍스트, 특히 html,xml 등 태그가 포함된 텍스트를 다루는 것은 정규식이 아니라 그 용도로 나온 모듈을 사용하는 게 훨씬 낫다고 하고 있다.

어쨌거나 perlfaq에 있는 예제를 보면, 부등호로 둘러쌓인 <텍스트> 부분을 추출한다. 이 때도 역시 중첩되어 있을 수 있다.

my $string =<<"HERE";
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
HERE

my @groups = $string =~ m/
        (                   # 캡처 버퍼 1의 시작
        <                   # 여는 부등호에 매치
            (?:
                [^<>]++     # 부등호를 제외한 문자들이 반복, 백트래킹하지 않음
                  |
                (?1)        # < 또는 > 발견, 캡처 버퍼 1을 재귀적으로 부름
            )*
        >                   # 닫는 부등호에 매치
        )                   # 캡처 버퍼 1의 끝
        /xg;

$" = "\n\t";
print "Found:\n\t@groups\n";
실행결과:
Found:
        <brackets in <nested brackets> >
        <another group <nested once <nested twice> > >

이제, 위의 "조건부 정규표현식" 절에서 언급된, "조건"을 묘사하는 세번째 패턴을 테스트해보자. (이건 주인장이 만들어본 예제라서, 정확하지 않을 수 있음)

실행 결과:
Found:
        <nested brackets>
        <nested once <NESTED TWICE> >

R1이나 R&name 형태는, 재귀적으로 호출할 때 (?번호)또는 (?&이름)으로 호출했다면 그 번호 또는 이름이, R 뒤에 있는 번호 또는 이름과 일치할 때 참이 된다. 예를 들어 ((패턴)) 이 형태는 바깥쪽 괄호나 안쪽 괄호 어느 쪽을 재귀호출해도 결국 똑같이 매칭이 되긴 하지만, 호출을 (?2)로 했는데 조건검사를 (?(R1)..)으로 하면 이건 거짓으로 판정된다. 또한, 이름으로 호출하고 번호로 검사하거나 그 반대로 하는 것은 허용되는 걸로 보인다.

1.2.10. 정규표현식 안에서 Perl 코드 실행하기 A bit of magic: executing Perl code in a regular expression

(?{code})(276) - code evaluation(277) expression. Perl 구문으로 구성된 code를 정규표현식의 일부로 사용

예:

        $x = "abcdef";
        $x =~ /abc(?{print "Hi Mom!";})def/; # 매치됨,
                                             # 'Hi Mom!'출력
        $x =~ /aaa(?{print "Hi Mom!";})def/; # 매치되지 않음,
                                             # 'Hi Mom!' 출력않음

다음 두 예는 주의:

        $x =~ /abc(?{print "Hi Mom!";})ddd/; # doesn't match,
                                             # no 'Hi Mom!'
                                             # but why not?
        $x =~ /abc(?{print "Hi Mom!";})[dD]dd/; # doesn't match,
                                                # but _does_ print

다른 예:

        $x =~ /(?{print "Hi Mom!";})/;       # matches,
                                             # prints 'Hi Mom!'
        $x =~ /(?{$c = 1;})(?{print "$c";})/;  # matches,
                                               # prints '1'
        $x =~ /(?{$c = 1;})(?{print "$^R";})/; # matches,
                                               # prints '1'

정규표현식이 코드 표현식이 있는 부분을 백트래킹하게 되고, 그 코드 표현식 안에 사용된 변수가 local 키워드를 사용하여 지역화되어 있다면, 코드 표현식에 의하여 변수값이 바뀌었던 것도 취소된다.

        $x = "aaaa";
        $count = 0;  # initialize 'a' count
        $c = "bob";  # test if $c gets clobbered
        $x =~ /(?{local $c = 0;})         # initialize count
               ( a                        # match 'a'
                 (?{local $c = $c + 1;})  # increment count
               )*                         # do this any number of times,
               aa                         # but match 'aa' at the end
               (?{$count = $c;})          # copy local $c var into $count
              /x;
        print "'a' count is $count, \$c variable is '$c'\n";
        'a' count is 2, $c variable is 'bob'
        'a' count is 4, $c variable is 'bob'

취소될 수 있는 건 지역화된 변수의 값 변화 뿐이며, 코드 표현식에 의한 다른 부효과는 영구히 남게 된다:

           $x = "aaaa";
           $x =~ /(a(?{print "Yow\n";}))*aa/;
          Yow
          Yow
          Yow
          Yow

$^R은 자동으로 지역화되며, 백트래킹이 이뤄질 때도 올바르게 동작한다.

코드 표현식을 조건으로 사용하여 영어와 독어의 정관사를 매치하는 예:

        $lang = 'DE';  # use German
        ...
        $text = "das";
        print "matched\n"
            if $text =~ /(?(?{
                              $lang eq 'EN'; # is the language English?
                             })
                           the |             # if so, then match 'the'
                           (der|die|das)     # else, match 'der|die|das'
                         )
                        /xi;

코드 표현식과 변수 치환을 동시에 사용할 경우 문제가 있다:

           $bar = 5;
           $pat = '(?{ 1 })';
           /foo(?{ $bar })bar/; # compiles ok, $bar not interpolated
           /foo(?{ 1 })$bar/;   # compile error!
           /foo${pat}bar/;      # compile error!

           $pat = qr/(?{ $foo = 1 })/;  # precompile code regexp
           /foo${pat}bar/;      # compiles ok

이런 제한의 이유는, 변수 치환과 코드 표현식을 동시에 사용하는 것이 보안 문제를 야기할 수 있기 때문이다.

           $regexp = <>;       # read user-supplied regexp
           $chomp $regexp;     # get rid of possible newline
           $text =~ /$regexp/; # search $text for the $regexp
           use re 'eval';       # throw caution out the door
           $bar = 5;
           $pat = '(?{ 1 })';
           /foo(?{ 1 })$bar/;   # compiles ok
           /foo${pat}bar/;      # compiles ok

pattern code expression(280) (??{code})(281)

           $length = 5;
           $char = 'a';
           $x = 'aaaaabb';
           $x =~ /(??{$char x $length})/x; # matches, there are 5 of 'a'

마지막 예문:

        $x = "1101010010001000001";
        $z0 = ''; $z1 = '0';   # initial conditions
        print "It is a Fibonacci sequence\n"
            if $x =~ /^1         # match an initial '1'
                        (?:
                           ((??{ $z0 })) # match some '0'
                           1             # and then a '1'
                           (?{ $z0 = $z1; $z1 .= $^N; })
                        )+   # repeat as needed
                      $      # that is all there is
                     /x;
        printf "Largest sequence matched was %d\n", length($z1)-length($z0);
           It is a Fibonacci sequence
           Largest sequence matched was 5

변수 $z0$z1은 코드 표현식 바깥쪽에 있는 보통의 변수들과 달리, 정규식이 컴파일될 때 치환되지 않는다. 대신 Perl 이 매치되는 것을 찾아 검색하는 동안 코드 표현식을 만나게 되면 그 때 평가된다.

//x(282)를 쓰지 않고 위 정규식을 쓰면 다음과 같다.

        /^1(?:((??{ $z0 }))1(?{ $z0 = $z1; $z1 .= $^N; }))+$/

1.2.11. 백트래킹 제어 명령어 Backtracking control verbs

Perl 5.10.0(283)에서는 백트래킹 과정을 세부적으로 제어할 수 있는 여러 제어 명령어가 추가되었다. 이 기능들은 전부 실험적인 기능이고 향후에 변경되거나 없어질 수 있다. 자세한 설명은 [perlre문서의 Special Backtracking Control Verbs]를 읽어보라.

여기서는 (*FAIL)(284) 명령어를 사용한 예를 보인다.

       %count = ();
       "supercalifragilisticexpialidoceous" =~
           /([aeiou])(?{ $count{$1}++; })(*FAIL)/i;
       printf "%3d '%s'\n", $count{$_}, $_ for (sort keys %count);

저 패턴은 일부 문자들에 매치되는 클래스로 시작된다. 여기에 매치되면, $count{'a'}++와 같은 구문이 실행되어 매치된 글자의 카운터를 증가시킨다. 그 다음 (*FAIL)이 말 그대로 매칭을 실패하게 만들고, 정규식 엔진은 지금껏 배운 대로 스트링이 끝나기 전까지 계속 포지션을 이동시키며 다음 모음으로 이동한다. 따라서, 매치되든 되지 않든 상관없이 정규식 엔진은 전체 스트링을 다 검사할 때까지 계속 진행한다.

위 방법이 다음과 같은 다른 해결책에 비해 눈에 띄게 빠르다는 것은 주목할 만 하다12:

       $count{lc($_)}++ for split('', "supercalifragilisticexpialidoceous");
       printf "%3d '%s'\n", $count2{$_}, $_ for ( qw{ a e i o u } );

1.2.12. 프라그마와 디버깅 Pragmas and debugging

정규표현식을 제어하고 디버그하는 데 사용되는 pragma(285)들.

use re 'eval'

use re 'taint'

        use re 'taint';
        $tainted = <>;
        @parts = ($tainted =~ /(\w+)\s+(\w+)/; # 이제 @parts도 오염되었음

re '/flags'

        use re '/m';   # 또는 다른 어떤 플래그라도
        $multiline_string =~ /^foo/; # /m 이 묵시적으로 적용된다

re 'debug'

debug 예:

컴파일 단계:

1             Compiling REx 'a*b+c'
2             size 9 first at 1
3                1: STAR(4)
4                2:   EXACT <a>(0)
5                4: PLUS(7)
6                5:   EXACT <b>(0)
7                7: EXACT <c>(9)
8                9: END(0)

매칭 연산 전에 수행하는 휴리스틱, 최적화:

1             floating 'bc' at 0..2147483647 (checking floating) minlen 2
2             Guessing start of match, REx 'a*b+c' against 'abc'...
3             Found floating substr 'bc' at offset 1...
4             Guessed: match at offset 0

실제 매칭 연산이 이뤄지는 과정:

 1             Matching REx 'a*b+c' against 'abc'
 2               Setting an EVAL scope, savestack=3
 3                0 <> <abc>             |  1:  STAR
 4                                        EXACT <a> can match 1 times out of 32767...
 5               Setting an EVAL scope, savestack=3
 6                1 <a> <bc>             |  4:    PLUS
 7                                        EXACT <b> can match 1 times out of 32767...
 8               Setting an EVAL scope, savestack=3
 9                2 <ab> <c>             |  7:      EXACT <c>
10                3 <abc> <>             |  9:      END
11             Match successful!
12             Freeing REx: 'a*b+c'

정규표현식을 디버그하는 다른 방법: print 구문을 삽입:

           "that this" =~ m@(?{print "Start at position ", pos, "\n";})
                            t(?{print "t1\n";})
                            h(?{print "h1\n";})
                            i(?{print "i1\n";})
                            s(?{print "s1\n";})
                                |
                            t(?{print "t2\n";})
                            h(?{print "h2\n";})
                            a(?{print "a2\n";})
                            t(?{print "t2\n";})
                            (?{print "Done at position ", pos, "\n";})
                           @x;
           Start at position 0
           t1
           h1
           t2
           h2
           a2
           t2
           Done at position 4

1.3. 버그들 BUGS

code evaluation(287), conditional expression(288), independent subexpression(289)실험적인 상태이다. 상용 코드에 사용하지 말라.

1.4. 참고 SEE ALSO

2. Mastering Perl

[Mastering Perl: Advanced Regular Expressions]에서 몇 가지.

2.1. /g , /c modifier 예 - 간단한 파서

//g(290), //c(291), \G(292) 를 사용한 예

# 간단한 파서
my $line = "Just another regex hacker, Perl hacker, and that's it!\n";

while( 1 )
    {
    my( $found, $type )= do {
        if( $line =~ /\G([a-z]+(?:'[ts])?)/igc )
            { ( $1, "a word"           ) }
        elsif( $line =~ /\G (\n) /xgc             )
            { ( $1, "newline char"     ) }
        elsif( $line =~ /\G (\s+) /xgc            )
            { ( $1, "whitespace"       ) }
        elsif( $line =~ /\G ( [[:punct:]] ) /xgc  )
            { ( $1, "punctuation char" ) }
        else
            { last; ()                   }
        };

    print "Found a $type [$found]\n";
    }

2.2. lookaround

"Wilma"와 "Fred"라는 문자열이 순서에 관계없이 둘 다 포함되어 있는 스트링을 찾는 경우:
$_ = "Here come Wilma and Fred!";
print "Matches" if /Fred.*Wilma|Wilma.*Fred/;   # alternation을 사용한 하나의 정규표현식
print "Matches" if /Fred/ && /Wilma/;           # 두개의 정규표현식을 사용한 것이 위의 표현보다 더 간단하다.

print "Matches" if /(?=.*Wilma).*Fred/;         # positive lookahead assertion 을 사용한 하나의 정규표현식
# 잘못된 경우
print "Matches" if /(?Wilma).*Fred/;            # 이것은 Wilma가 Fred보다 앞에 있는 경우에만 매치된다.

zero-width(294)특성을 이용하여 Perldoc:split 함수에서 사용하는 예:

my @words = split /(?=[A-Z])/, 'CamelCaseString';       # "C", "C", "S"가 없어지지 않는다.
print join '_', map { lc } @words; # camel_case_string

3. 그 외 이런저런 주제들

3.1. qr// 에 대하여

위의 perldoc 문서에서는 qr//(295)에 대해서, 스트링을 정규표현식으로 해석하여 저장한다고 막연하게 나와 있는데, 그냥 정규표현식이 담겨 있는 스칼라 변수를 사용할 때와 어떤 차이가 있는지 알기 쉽진 않다.

my $pattern = "foo";
my $qr      = qr/foo/;

# 아래 네 가지는 항상 같은 결과
$str =~ /$pattern/;  # 흔히 보는 예
$str =~ $pattern;    # 희한하게 생겼지만 이것도 잘 동작한다

$str =~ $qr;
$str =~ /$qr/;       # 이 둘도 동치

단순 문자열 패턴 뿐 아니라 캐릭터 클래스나 괄호나 기타 등등도 그대로 동일하게 적용된다.

my $str = "my name is hong";

my $pattern = 'name is (\w+)$';
my $qr      = qr/name is (\w+)$/;

if ( $str =~ $pattern ) { print "[$1]\n"; }   # hong
if ( $str =~ $qr      ) { print "[$1]\n"; }   # 이것도 hong

modifier(296) 사용도 마찬가지. 표현만 달라지지 똑같은 효과를 낼 수 있다:

my $str = "my NAME is hong";             # NAME 이 대문자가 되었음

my $pattern = '(?i)name is (\w+)$';      # 확장 패턴 사용
my $qr      = qr/name is (\w+)$/i;       # modifier 사용

if ( $str =~ $pattern ) { print "[$1]\n"; }
if ( $str =~ $qr      ) { print "[$1]\n"; }   # 둘 다 hong 검출

요컨데 딱히 qr//을 써야만 매칭을 할 수 있고 스트링 스칼라로는 할 수 없는 경우가 따로 있는 것 같지는 않다. (주인장의 추정일 뿐이긴 하지만)

qr//의 장점은 perldoc 에서 언급했듯이 "미리 컴파일하여 저장"하고 "사용할 때는 컴파일하지 않는다"라는 점이다. 이것이 어떤 의미인지는 직접 코드를 확인해보자:

#!/usr/bin/perl
use re 'debug';        # Perl 내부에서 정규표현식을 처리하는 과정을 STDERR로 출력함
local $| =1;

my $str = "iname";

#################
# qr//을 사용하는 예
#################
my @reg_qr = ( qr/a/, qr/b/, qr/c/ );             # 이 시점에서 세 개의 정규표현식이 컴파일된다.

# 이하의 내용에서 print 문은 단지 각 구문의 실행을 추적하기 위해 넣었음
for my $c ( 10 .. 11 ) {
    print "\n\n-- $c --------------------\n\n";
    for ( 0 .. 2 ) {
        print "------------------------------\n";
        my $reg = $reg_qr[$_];
        if ( $str =~ $reg ) {                      # 여기서는 컴파일하지 않고 바로 사용한다
            print "MATCH!!!!!!!!!!\n";
        }
        else {
            print "NOT MATCH~~~~~~\n";
        }
    }
}

print "\n\n\n", "="x30, "\n\n\n";

#################
# 스트링 스칼라 변수를 사용하는 예
#################
my @reg_str = ( "a", "b", "c" );

for my $c ( 10 .. 11 ) {
    print "\n\n-- $c --------------------\n\n";
    for ( 0 .. 2 ) {
        print "------------------------------\n";
        my $reg = $reg_str[$_];
        if ( $str =~ $reg ) {                # =~ 연산을 할 때마다 컴파일한다
            print "MATCH!!!!!!!!!!\n";
        }
        else {
            print "NOT MATCH~~~~~~\n";
        }
    }
}
보다시피, 두 번의 for 루프는 각각 패턴 "a", "b", "c" 를 각각 비교하는 과정을 다시 두 번 반복한다.

실행결과는 다음과 같다:

길어서 접음. 클릭

결과에서 보면 qr을 사용한 테스트에서는 세 개의 패턴을 한번씩만 컴파일한 후에, 두번째 루프를 돌 때는 컴파일 없이 바로 사용하고 있다. 그러나 스칼라 변수를 사용한 테스트에서는 변수의 값이 매번 바뀔 때마다 다시 컴파일을 하기 때문에 총 여섯 번 컴파일한다.

Perldoc:Benchmark 를 사용하여 성능을 비교해보면 (위 코드에서 print 문을 전부 제거한 후 비교하였음) 속도가 두 배 이상 차이난다.

          Rate scalar     qr
scalar 42309/s     --   -57%
qr     97303/s   130%     --

추가로, 만일 스칼라 변수를 사용한 곳에서 //o(297) modifier를 사용하여 강제로 재컴파일을 금지시키면 어떻게 될까?

        my $reg = $reg_str[$_];
        if ( $str =~ /$reg/o ) {             # /o 사용
        }
이 경우는 디버그 메시지를 가지고 확인해보면, 여섯 번의 비교 중에 제일 첫번째 비교를 할 때 컴파일을 한 후 나머지 다섯번의 비교에서는 컴파일을 하지 않는 것을 볼 수 있다. 속도도 따라서 qr 보다 1.5배 정도 빠르게 나온다. 그러나 여섯번 모두 "a" 패턴을 가지고 비교를 하므로, 잘못된 실행 결과가 나온다 (여섯번 모두 매치하는 걸로 판정됨)

3.1.1. aero 님의 코멘트

(Perl 고수 [aero님]의 코멘트. 감사합니다!)

다음이 실마리가 될듯합니다.

일반스트링과 컴파일된 정규식의 비교

aero@doc:~$ perl -MDevel::Peek -e '$s="foo";Dump($s)'
SV = PV(0x8cd9700) at 0x8cf9e38
  REFCNT = 1
  FLAGS = (POK,pPOK)
  PV = 0x8cf5f28 "foo"\0
  CUR = 3
  LEN = 4

aero@doc:~$ perl -MDevel::Peek -e '$s=qr/foo/;Dump($s)'
SV = IV(0x8776e3c) at 0x8776e40
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x8759520
  SV = REGEXP(0x87844a0) at 0x8759520
    REFCNT = 1
    FLAGS = (OBJECT,POK,FAKE,pPOK)
    IV = 0
    PV = 0x877a548 "(?-xism:foo)"
    CUR = 12
    LEN = 0
    STASH = 0x8758fe0   "Regexp"

qr은 컴파일된 정규식 Regexp객체임을 알 수 있다.

정규식 매칭의 표현의 코드 해석

aero@doc:~$ perl -MO=Deparse -e '$p="foo bar";$s="foo";$p=~$s'
$p = 'foo bar';
$s = 'foo';
$p =~ /$s/;
-e syntax OK

aero@doc:~$ perl -MO=Deparse -e '$p="foo bar";$s=qr/foo/;$p=~$s'
$p = 'foo bar';
$s = qr/foo/;
$p =~ /$s/;
-e syntax OK

정규식 매칭 표현구문에서 구분자가 생략되어도 Perl은 알아서 넣어서 해석한다.

마지막 예제는 컴파일된 정규식 같은경우는 $reg에 넣으면 그 자체가 컴파일된 정규식 객체를 가르키게되어서 그대로 사용가능하지만 그냥 스칼라 스트링을 $reg에 넣으면 설령 그것이 정규표현식 구문에서 컴파일 되었다고 해도 $reg는 어떤 객체를 가르키고 있는 레퍼런스가 아니라 복사된 스칼라값일 뿐이므로 컴파일된 정규식이 원래값이 있는 곳을 찾아 컴파일된 정규식으로 교체할 수 없기 때문에 결과는 당연해 보입니다. o modifier는 이제 deprecated된 feature로 쓰지 않아도 Perl이 필요하다면 자동적으로 최적화 시키고, 어떤 변화된 값이 오는 곳에 o modifier를 쓰는 건 예상치 못한 side effect를 가져온다고 이미 조심을 해야된다고 알려진터라 실제에는 그렇게 사용할 일도 없을 것 같고요.

qr의 유용한 점은 마지막 예에서 처럼 여러 set들의 정규식이 반복적으로 사용되어져야 할때( 예: http://www.effectiveperlprogramming.com/blog/183 )유용하겠죠 :)

-- aero 2010-4-22 10:39 pm

3.2. 매치 변수와 성능 문제

이 섹션의 출처:

매치 변수 $`(298), $&(299), $'(300) 세 가지는 프로그램에 심각한 성능 문제(301)를 가져온다:

'My cat is Buster Bean' =~ m/Buster/;
print "I matched $&\n";   # 여기서 매치 변수를 사용해버렸기 때문에

while (<>) {
   next unless /Bean/;    # 매치할 때마다 매번 오버헤드가 발생
}

따라서:

Perldoc:English 모듈을 사용할 경우 주의:

5.10.0(306)에서는:

use 5.010;

'My cat is Buster Bean' =~ m/\s\w+\sBean/p;   # /p 옵션 사용
say "I matched ${^MATCH}";

while (<>) {
   next unless /Bean/;     # 오버헤드 없음
}

5.10.0(311) 이전 버전에서는, 정규식 자체를 수정하여 명시적으로 ( )(312)를 사용하여 캡처링하라:
_____사용하지 말 것_____ _____5.10 이전 버전에서는_____ _____5.10 이하 버전에서는_____
/pattern/ 이후 $` /(.*?)pattern/ 이후 $1 /pattern/p 이후 ${^PREMATCH}
/pattern/ 이후 $& /(pattern)/ 이후 $1 /pattern/p 이후 ${^MATCH}
/pattern/ 이후 $' /pattern(.*)/ 이후 $+ /pattern/p 이후 ${^POSTMATCH}

기존에 사용 중이던 프로그램에 매치 변수가 쓰였는지를 확인하고 싶다면 다음 모듈들을 살펴보라

3.3. non-greedy quantifier를 쓸 때 주의

my $str = qq|aaa "bbb" ccc "ddd" eee|;

# greedy 수량자 /+/
if ( $str =~ /"(.+)"/ ) {
    print "[$1]\n";
}
# 출력은 당연히 [bbb" ccc "ddd]

# non-greedy 수량자 /+?/
if ( $str =~ /"(.+?)"/ ) {
    print "[$1]\n";
}
# 출력은 [bbb]


# greedy 수량자 /+/
if ( $str =~ /"(.+)" eee/ ) {
    print "[$1]\n";
}
# 역시 당연히 [bbb" ccc "ddd]

# non-greedy 수량자 /+?/
if ( $str =~ /"(.+?)" eee/ ) {
    print "[$1]\n";
}
# [ddd]가 아니라, [bbb" ccc "ddd]이다


# 이 경우는 . 대신 [^"]를 써야 한다.
# greedy 수량자 /+/
if ( $str =~ /"([^"]+)" eee/ ) {
    print "[$1]\n";
}
# non-greedy 수량자 /+?/
if ( $str =~ /"([^"]+?)" eee/ ) {
    print "[$1]\n";
}
# 위 둘 다 결과는 [ddd]

non-greedy 수량자를 쓰는 경우라도, 일단 첫번째 원칙은 스트링의 가장 왼쪽에서부터 매치를 하는 것이므로, "bbb" c/"(.+?)" eee/와 매치되지 않는 게 확인된 시점에서, 그 다음은 .+?의 범위를 늘려나가며 재시도를 하게 된다.

3.4. /g, /c, \G, 포지션 리셋에 관한 보충

포지션이 리셋된다거나, //c(313) modifier를 써서 리셋되지 않게 한다는 것이 무슨 의미이고 어떤 때 사용할 수 있는지 쉽게 와닿지 않았다가, 최근에 이 부분을 실제 코드에서 경험하게 되어서, 여기에 간단한 예를 만들어 적어 둔다.

샘플 코드 전체는 [GitHub]에서 받을 수 있음

코드1:

my $str = "a1a2a3a4a5";
# a<숫자> 형태의 문자열을 전부 찾아내어 어떤 처리를 하고 싶다
while ( $str =~ /(a\d)/ ) {
    print "[$1]\n";    # 어떤 처리 : 출력
}

[a1]
[a1]
... 무한 루프

위 코드는 매번 $str의 시작 부분부터 검사를 하기 때문에 "a1"만 찾으며 무한 루프를 돈다.

코드2:

my $str = "a1a2a3a4a5";
while ( $str =~ /(a\d)/g ) {
    # /g modifier
    print "[$1] at position ", pos($str), "\n";
}

[a1] at position 2
[a2] at position 4
[a3] at position 6
[a4] at position 8
[a5] at position 10

이런 경우에 가장 흔하게 사용하는 패턴이다.

코드3:

my $str = "a1a2a3a4a5";
while ( $str =~ /(a\d)/gc ) {
    print "[$1] at position ", pos($str), "\n";
}

# /c 때문에 $str의 포지션은 여전히 10이고, 따라서 아래 루프의 매치는 실패
while ( $str =~ /(a\d)/g ) {
    print "Again, [$1] at position ", pos($str), "\n";
}

/c 변경자가 없었다면 두번째 루프에서 다시 "a1"부터 출력되었을 것이다. 그러나 /c 변경자를 첫번째 루프에서 사용했기 때문에, "a5"를 찾은 후 그 다음 매치가 실패했을 때 $str의 포지션 정보가 리셋되지 않은 채로 남아 있고, 두번째 루프에서 그 지점(오프셋 10)에서 매치를 시작하기 때문에 곧바로 실패하여 루프를 빠져나온다.

[a1] at position 2
[a2] at position 4
[a3] at position 6
[a4] at position 8
[a5] at position 10

코드4:

발견한 문자열이 "a2"인 경우에 한해서, 그 바로 뒤에 "a3"이 따라붙으면 이 "a3"은 다음 번 검사에서 매치시키지 않고 스킵하고 싶다고 하자.

언제나 "a3"은 처리하지 않겠다면 애초에 정규식을 a[012456789]로 만들거나, 루프 안에서 if로 $1 eq 'a3'인 경우를 따로 처리할 수 있지만, 지금과 같이 <바로 앞에 "a2"가 있었던 경우에만>라는 조건이 붙으면 복잡해진다. 직전에 발견되었던 게 "a2"인지 여부를 저장하는 변수를 추가로 써야 하거나...

이런 경우 /g 변경자를 써서 포지션을 넘겨 버릴 수 있다.

# 이 코드는 불완전
my $str = "a1a2a3a4a5";
while ( $str =~ /(a\d)/g ) {
    print "[$1] at position ", pos($str), "\n";
    if ( $1 eq "a2" ) {
        # a3 을 매치시켜서 포지션 이동
        $str =~ /a3/g;
    }
}

[a1] at position 2
[a2] at position 4
[a4] at position 8
[a5] at position 10

언뜻 보면 성공한 것 같지만, 이 코드는 문제가 있다.

먼저, 만일 "a2" 뒤에 "a3"이 없다면? if 블록 안의 정규식 검사가 매치에 실패하기 때문에 포지션이 리셋되고, 다음 검사는 다시 문자열의 처음부터 검사하게 된다.

my $str = "a1a2a!!a4a5";   # a2 뒤에 a3이 없다

[a1] at position 2
[a2] at position 4
[a1] at position 2
[a2] at position 4
... 무한 루프

따라서, "a3"을 찾는 검사에서 실패하더라도 포지션을 리셋하지 않도록 해야 한다.

    if ( $1 eq "a2" ) {
        # /c - a3 매치에 실패하더라도 포지션을 리셋하지 않음
        $str =~ /a3/gc;
    }

이제 무한 루프는 해결했지만, 여전히 문제가 남아 있다. 이 코드는 "a2"를 발견한 시점에서 그 다음 나타나는 첫번째 "a3"까지 검사를 진행하기 때문에, "a2"와 "a3" 사이에 다른 패턴이 들어가 있으면 그걸 전부 건너뛰어 버릴 것이다.

my $str = "a1a2a9a3a4a5";   # a2와 a3 사이에 a9가 끼어 있다.

[a1] at position 2
[a2] at position 4
[a4] at position 10  <-- a9 까지 스킵해버린다.
[a5] at position 12

따라서 현재 포지션 바로 직후에 있는 "a3"만 찾기 위해 앵커가 필요하고, ^는 답이 아니다. 이럴 때 \G(314)를 사용할 수 있다.

코드5:

my $str = "a1a2a9a3a4a5";
while ( $str =~ /(a\d)/g ) {
    print "[$1] at position ", pos($str), "\n";
    if ( $1 eq "a2" ) {
        # a3 을 매치시켜서 포지션 이동
        # /c - a3 매치에 실패하더라도 포지션을 리셋하지 않음
        # \G - a2 매치에 성공한 그 지점을 나타내는 앵커
        $str =~ /\Ga3/gc;
    }
}

[a1] at position 2
[a2] at position 4
[a9] at position 6
[a3] at position 8
[a4] at position 10
[a5] at position 12

어떻게든 정규식 패턴만으로 동일한 동작을 하게 하려면... 아래에 있는 코드는 주인장이 떠올린 몇 가지 패턴들이고, 테스트 케이스에 대해서는 다 잘 동작하지만... 어떤 버그가 숨겨져 있을지도.

코드9:

foreach my $str (
    "a1a2a3a4a5",
    "a1a2!!a4a5",       # a2 뒤에 a3이 없는 경우
    "a1a2a9a3a4a5",     # a2 와 a3 사이에 다른 매치가 있는 경우
    "a1a3a4a5",         # a2가 없는 경우
) {
    print "---------- $str ----------\n";
    # code_05 에서 사용한 /c, \G
    print " 1. using /c, \\G\n";
    while ( $str =~ /(a\d)/g ) {
        print "[$1] at position ", pos($str), "\n";
        if ( $1 eq "a2" ) {
            $str =~ /\Ga3/gc;
        }
    }

    # 백레퍼런스에 이름을 붙여 사용
    # a2a3 이 매치되면 그 중 a2에,
    # 그 외의 경우는 a<숫자>에 PAT 백레퍼런스가 생긴다
    print " 2. named backreference\n";
    while ( $str =~ /(?<PAT>a2)a3|(?<PAT>a\d)/g ) {
        print "[$+{PAT}] at position ", pos($str), "\n";
    }

    # 또는
    # 캡처그룹 번호 리셋 (?| ... )
    print " 3. alternative capture group numbering\n";
    # 그룹번호:         $1     $1
    while ( $str =~ /(?|(a2)a3|(a\d))/g ) {
        print "[$1] at position ", pos($str), "\n";
    }

    # 또는
    # 바로 앞에 a2가 있지 않은(부정적 룩비하인드) a3, 또는 a<3이 아닌 숫자>
    print " 3. lookbehind\n";
    while ( $str =~ /((?<!a2)a3|(?:a[012456789]))/g ) {
        print "[$1] at position ", pos($str), "\n";
    }
}

4. 기타

정규표현식을 테스트할 수 있는 도구

5. comments

얼마전 SET Escape '\' 이란걸 배워서 스크립트에 적용했는데요
테이블앞에 붙은 버전을 변수로 받기위해 &1\_JIBUN_CODE 이런식으로 변수뒤에 백슬래쉬가 붙었는데
스크립트 중간중간에 정규식쓰면서 백슬래쉬를 여기저기에 사용한게 많더라고요..
그래서 정규식에 쓰인 것까지 Escape 해버려서 결과값이 다르게 나오는것 같아요
전체스크립트 상단에 SET Escape '\' 설정되어있고 중간에 이런 replace문들이 있는거죠

SELECT
DISTINCT AS CODE, AS ITEMNAME,
TRIM(ADM_EMD) AS NAME1,
REGEXP_REPLACE(ADM_EMD, '([^0-9,.]*)([^동]*)(동|읍|면)', '\1') AS NAME2
FROM &1\_JIBUN_CODE
WHERE
USE_FLAG IN ('1', '2') AND SUBSTR(ADM_CODE,6,10) <> '00000' AND
LENGTH(REGEXP_REPLACE(ADM_EMD, '([^0-9,.]*)([^동]*)(동|읍|면)', '\1'))> 1

그냥 set escape off 할수도없고..저 replace 문들도 사용해야하고..좋은 방법은 없나요.. ㅜ

시도1) set escape 쓸때 백슬래쉬말고 다른기호를 쓴다 => #로 바꿔보니 에러나더라구요
시도2) regexp_replace 쓸때 백슬래쉬말고 다른표현을 쓴다 => 이건 어떻게 해야 동일하게 나올지 잘안되구,자칫 잘못꼬이면 큰일나구,

-- 정규식 검색하다가 들러서 질문남깁니다.. 근데 여기에 질문해도 되는건가요..

-- Daff 2015-6-15 11:35 am

질문해도 상관은 없습니다만 답은 제가 모르겠네요ㅋ

오라클 쪽인가본데,
1) 직관적으로 생각해서 백슬래시가 이스케이프 접두사로 쓰이는 상황이라면, 백슬래시 자체를 나타내기 위해서는 \\를 쓰겠지요. 그러니 저 regexp_replace 쪽에서 '\1'이라고 쓴 걸 '\\1' 로 바꿔서 해볼 법 하고요. (그럼 \\가 먼저 \로 바뀌고, 그 상태에서 함수 인자로 들어가 동작하여 성공...하는 게 이론적인 희망)

2) [이 댓글]을 보면 저 시도1에서 말씀하신 것처럼 이스케이프 접두어를 #으로 바꾸는 게 가능해보이는데요. 에러가 난 이유는 다른 데 있었던 게 아닐까요.
-- Raymundo 2015-6-15 9:15 pm

regexp_replace 쪽에서 '\1'이라고 쓴 걸 '\\1' 로 바꿔서 => 이렇게 해보니 해결되었어요, 정말 급했는데.. 감사합니다!!!

-- Daff 2015-6-16 12:47 pm

1.1.1 에서 보면
"keeper" =~ /^keep$/; # 매치됨

^와$를 사용하면 스트링 전체를 할 수 있다고 해서 위와 같이 매칭이 된다고 되어 있는데 매칭이 되지 않음이 맞지 않을까 합니다.

-- duraboy 2016-6-13 2:44 pm

헉 맞습니다. 이 문서가 만들어진 후 몇 년간 아무도 눈치채지 못하고 지나간 것을... 감사합니다.
-- Raymundo 2016-6-14 1:54 pm
이름:  
Homepage:
내용:
 


컴퓨터분류

각주:
1. 매치에 성공한 스트링이 아니라 패턴 자체가 재사용됨
2. 즉, pos $x 를 했을 때 undef이 반환된다
3. Linux,Unix 환경에서
4. Perl 5.14에서 추가된 문구이고, 이전 버전에서는 //o modifier를 사용하여 한번만 컴파일하도록 지시했었다. 튜토리알에 포함하기에는 좀 생뚱맞은 문구 같음. 기존 버전에 익숙한 사람들을 위해서 문서 후반에 따로 정리하는게 나았을 듯
5. 5.12까지는 eval {...}로 둘러싼 후 실행하여 결과를 가지고 치환환다고 설명되었다가, 5.14에서 바뀌었음
6. Mastering Perl, Chapter 02
7. 5.8 버전에서는 펄이 UTF-8인코딩을 사용한다는 문구가 있었는데, 내부적으로 처리하는 방식이 그렇다는 얘기라서, 사용자가 여기서 알 필요가 없다고 판단했는지 삭제되었음
8. 5.8버전에서는 Names.txt라고 잘못 기록되어 있는데 수정되었음. 주인장이 쓰는 리눅스 시스템에서는 /usr/lib/perl5/5.8.8/unicore/NamesList.txt에, 스트로베리 펄 5.10.0에서는 C:\strawberry\perl\lib\unicore\NamesList.txt에 있었음
9. 5.8버전 문서에는 이 단락에 다음과 같은 내용이 있었는데 삭제되었다: 스트링이 유니코드 문자의 시퀀스로 간주되는 상황에서, 한 바이트의 매치를 해야 되는 경우라면 \C를 사용한다. \C.와 유사하나, 0-255 사이의 어떤 바이트에도 매치된다는 게 다르다.
10. 아래에 주인장이 추가로 작성한 qr// 에 대하여 섹션 참조
11. 5.8버전의 문서에서는 \C에 대한 언급이 유니코드 얘기할 때 나온다. \C는 유니코드 스트링을 대상으로 매칭할 때 한 "바이트"에 매치되는 캐릭터 클래스이다. 5.14버전에서는 그 언급이 사라졌는데 여기에는 삭제되지 않았다
12. 주인장 컴퓨터에서 Strawberry Perl 5.12에서 Cpan:Benchmark 모듈로 테스트해보면, print 구문을 제외했을 때 속도 차이가 두 배가 넘었다
13. Perl/디버깅 참조
찾아보기:
$_   5, 174=~   3, 71, 151, 173
  !~   4, 152
anchor   10, 238
  $   14, 56, 134
  \A   59
  \B   49
  \b   48, 240
  \G   167, 254, 292, 314
  \Z   60
  \z   62
  ^   12, 55, 239
assertion   242, 262
backreference   76, 259
  (?<name>)   85
  (?'name')   86
  \g1   77
  \g{-1}   82
  \g{name}   87
  named backreference   84, 260
  relative backreference   81
backtracking control   
  (*FAIL)   284
capture group number reset   
  (?|...)   90
character class   17, 19, 206
  $   24
  -   20, 25
  .   37, 54
  [:name:]   215
  \   22
  \C   251
  \c   190
  \D   34, 220
  \d   31, 216
  \E   189, 192
  \L   187
  \l   185
  \N   40
  \N{name}   200
  \P   210
  \p   209
  \p{name:value}   212
  \p{name=value}   211
  \P{name}   208
  \p{name}   207
  \Q   191
  \S   35
  \s   32, 217
  \U   188
  \u   186
  \W   36
  \w   33, 218
  \X   213
  ]   21
  ^   23, 26
  POSIX-style   214
code evaluation   265, 277, 287
  (??{code})   281
  (?{code})   276
  pattern code expression   280
conditional expression   256, 279, 288
  (?(condition)yes-regexp)   257
  (?(condition)yes-regexp|no-regexp)   258
context   
  list context   73, 165, 181
  scalar context   72, 162, 182
definition group   
  (?&name)   269
  (?(DEFINE)(?<name>pattern)...)   268
Escape Sequences   9, 199extended pattern   110, 222
  (?#text)   224
  (?^...:)   236
  (?i)   226
  (?m)   228
  (?s)   230
  (?x)   232
  embedded comment   223
independent subexpression   289
  (?>regexp)   252
lookahead   243, 263, 293, 315
  (?!regexp)   249
  (?<!fixed-regexp)   250
  (?<=fixed-regexp)   246
  (?=regexp)   244
  lookbehind   245, 264
matching operator   148
  /regexp/   2, 149
  m!regexp!   6, 150
  m'regexp'   158
matching postion   
  $+[n]   95
  $-[n]   94
  @+   93
  @-   92
matching variable   69, 106, 175
  $&   97, 299
  $'   98, 300
  $+   74
  $1   70, 248, 302
  $^N   75
  $`   96, 298
  ${^MATCH}   101, 308
  ${^POSTMATCH}   102, 309
  ${^PREMATCH}   100, 307
  %+   88
  성능 문제   301
metacharacter   8, 11, 64, 112
  ( )   67, 68, 312
  alternation   63, 109, 123, 145
  grouping   66, 108, 184, 303
  Word anchor   47
  |   65
modifier   53, 147, 153, 296
  //a   30, 45, 219
  //c   161, 164, 291, 313
  //d   170
  //g   160, 166, 168, 253, 290
  //i   18, 156, 227
  //l   171
  //m   52, 57, 155, 229
  //o   169, 297
  //p   103, 234, 310
  //s   39, 42, 51, 154, 231
  //u   203
  //x   146, 157, 225, 233, 282
  s///e   178
  s///g   176
  s///r   177, 317
new line   
  \n   38, 41, 50, 61
non-capturing   104, 235, 247
  (?:regexp)   105, 304
Perl   
  5.10.0   27, 79, 80, 83, 89, 99, 137, 267, 270, 283, 306, 311
  5.12.0   43
  5.14.0   29, 44, 78, 91, 196, 202, 237, 286, 316
  5.14.1   1
  5.5.0   305
  5.6.0   193, 198, 201
pos()   163pragma   204, 285qr//   221, 295quantifier   111, 124, 135, 255
  *   114
  *+   142
  *?   129
  +   115
  ++   143
  +?   130
  ?   113
  ?+   140
  ??   128
  greedy   120
  maximal match   119
  minimal match   125
  non-greedy   126
  possessive   138
  {n,m}   116
  {n,m}+   139
  {n,m}?   131
  {n,}   117
  {n,}+   141
  {n,}?   132
  {n}   118
  {n}+   144
  {n}?   133
recursive pattern   266, 271
  (?&name)   275
  (?0)   274
  (?group-ref)   272
  (?R)   273
s///   172, 180
  s'''   179
split   107, 183Unicode   28, 46, 194, 205
  \o{oct}   197
  \x{hex}   195
zero-width   241, 261, 278, 294스트링   7, 13, 15, 16, 58, 121, 122, 127, 136, 159  

마지막 편집일: 2024-5-15 9:05 pm (변경사항 [d])
126408 hits | Permalink | 변경내역 보기 [h] | 페이지 소스 보기