Perl 정규표현식 튜토리얼

Perl 정규표현식 튜토리얼 (perlretut)

Perl에서 정규표현식을 이해하고, 만들고, 사용하는 기본 튜토리얼이에요. 정규표현식 레퍼런스 문서인 perlre의 보완 역할을 해요. 정규표현식은 m//, s///, qr//, split 연산자의 핵심 부분이므로, 이 튜토리얼은 perlop의 "Regexp Quote-Like Operators"perlfunc의 "split"과도 겹쳐요.

Perl은 텍스트 처리의 탁월함으로 널리 알려져 있고, 정규표현식은 이 명성 뒤의 큰 요인 중 하나예요. Perl 정규표현식은 대부분의 다른 컴퓨터 언어에 없는 효율성과 유연성을 보여줘요. 정규표현식의 기초만 익혀도 놀라울 정도로 쉽게 텍스트를 다룰 수 있게 돼요.

정규표현식이 뭘까요? 가장 기본적으로 정규표현식은 문자열이 특정 특성을 가졌는지 결정하는 데 쓰는 템플릿이에요. 문자열은 대개 어떤 텍스트예요. 줄, 문장, 웹 페이지, 심지어 책 전체일 수도 있지만, 꼭 그래야 하는 건 아니에요. 예를 들어 이진 데이터일 수도 있어요. 생물학자들은 길고 긴 DNA 서열에서 패턴을 찾는 데 Perl을 자주 써요.

변수 $var의 텍스트가 문자 시퀀스 m u s h r o o m(읽기 편하라고 공백 추가)을 포함하는지 확인하고 싶다고 가정해볼게요. Perl에서는 이렇게 쓸 수 있어요:

$var =~ m/mushroom/

이 표현식의 값은 $var가 그 문자 시퀀스를 어디에든 포함하면 TRUE, 아니면 FALSE예요. '/' 문자로 감싼 부분이 우리가 찾는 특성을 나타내요. 그걸 _패턴(pattern)__이라고 불러요. 패턴이 문자열에 있는지 보는 과정을 _매칭(matching)_이라고 하고, "=~" 연산자와 m//가 Perl에게 패턴을 문자열에 매칭해보라고 지시해요. 패턴도 문자열이지만 아주 특별한 종류의 문자열이라는 점을 기억하세요. 패턴은 요즘 흔히 쓰여요. 웹 페이지를 찾으려 검색 엔진에 입력하는 패턴이나, 디렉토리 파일을 나열하는 데 쓰는 패턴, 예를 들어 "ls *.txt"나 "dir *.*" 같은 게 그 예예요. Perl에서 정규표현식이 설명하는 패턴은 문자열을 검색할 뿐 아니라, 문자열의 원하는 부분을 추출하고, 검색·바꾸기 연산을 하는 데도 쓰여요.

정규표현식은 추상적이고 이해하기 어렵다는 과분한 평판을 갖고 있어요. 이건 실제로는 그것을 표현하는 표기법이 간결하고 빽빽하기 때문이지, 내재적 복잡성 때문이 아니에요. 우리는 /x 정규표현식 수정자(아래 설명)와 충분한 공백을 함께 써서 빽빽함을 줄이고 읽기 쉽게 만들길 권장해요. 정규표현식은 조건문과 루프 같은 단순한 개념으로 구성되고, Perl 언어 자체의 대응하는 if 조건문과 while 루프보다 이해하기 어렵지 않아요.

이 튜토리얼은 정규표현식 개념을 그 표기법과 함께 한 번에 하나씩, 많은 예시와 함께 논의해 학습 곡선을 완만하게 해요. 튜토리얼의 첫 부분은 가장 단순한 단어 검색에서 기본 정규표현식 개념까지 진행돼요. 첫 부분을 마스터하면 약 98%의 요구를 해결하는 데 필요한 모든 도구를 갖게 돼요. 둘째 부분은 기초에 익숙하고 더 강력한 도구에 굶주린 분들을 위한 것이에요. 더 고급 정규표현식 연산자를 논의하고 최첨단 혁신을 소개해요.

참고: 시간을 절약하기 위해 "regular expression"은 종종 regexp나 regex로 줄여 써요. Regexp가 regex보다 더 자연스러운 약어지만 발음하기는 더 어려워요. Perl pod 문서는 regexp 대 regex 사이에서 반반으로 갈려요. Perl에는 그걸 줄여 쓰는 방법이 하나 이상 있으니까요. 이 튜토리얼에서는 regexp를 쓸게요.

v5.22부터 새로 use re 'strict'는 정규표현식 패턴을 컴파일할 때 그렇지 않을 때보다 더 엄격한 규칙을 적용해요. 합법적이면서도 의도한 것이 아닐 수 있는 것들을 찾아낼 수 있어요.

출처: Perl 공식 문서 - perlretut

Part 1: 기초 (The basics)

단순 단어 매칭 (Simple word matching)

가장 단순한 regexp는 그냥 단어, 더 일반적으로는 문자 문자열이에요. 단지 단어로 이뤄진 regexp는 그 단어를 포함하는 어떤 문자열이든 매칭해요:

"Hello World" =~ /World/;  # matches

이 Perl 문장은 뭐에 관한 걸까요? "Hello World"는 단순한 큰따옴표 문자열이에요. World는 정규표현식이고, /World/를 감싸는 //가 Perl에게 문자열에서 매치를 검색하라고 말해요. =~ 연산자는 문자열을 regexp 매치와 연결하고, regexp가 매치되면 참 값을, 매치되지 않으면 거짓을 만들어요. 우리 경우 World"Hello World"의 두 번째 단어와 매치되므로 표현식은 참이에요. 이 같은 표현식은 조건문에서 유용해요:

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

이 테마에는 유용한 변형들이 있어요. !~ 연산자를 써서 매치의 방향을 뒤집을 수 있어요:

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

regexp의 문자 그대로 문자열은 변수로 바꿀 수 있어요:

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

특수 기본 변수 $_에 대해 매칭한다면 $_ =~ 부분을 생략할 수 있어요:

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

마지막으로 매치용 기본 구분자 //를 앞에 'm'을 붙여 임의의 구분자로 바꿀 수 있어요:

"Hello World" =~ m!World!;   # matches, delimited by '!'
"Hello World" =~ m{World};   # matches, note the paired '{}'
"/usr/bin/perl" =~ m"/perl"; # matches after '/usr/bin',
                             # '/' becomes an ordinary char

/World/, m!World!, m{World} 모두 같은 것을 나타내요. 예를 들어 인용부호('"')를 구분자로 쓸 때 슬래시 '/'는 평범한 문자가 되어 이 regexp에서 문제없이 쓸 수 있어요.

서로 다른 regexp가 "Hello World"를 어떻게 매칭할지 생각해볼게요:

"Hello World" =~ /world/;  # doesn't match
"Hello World" =~ /o W/;    # matches
"Hello World" =~ /oW/;     # doesn't match
"Hello World" =~ /World /; # doesn't match

첫 regexp world는 regexp가 기본적으로 대소문자를 구분하므로 매치되지 않아요. 둘째 regexp는 부분 문자열 'o W'"Hello World" 문자열에 발생하므로 매치돼요. 공백 문자 ' '는 regexp에서 다른 어떤 문자처럼 취급되고 이 경우 매치에 필요해요. 공백 문자의 부재가 세 번째 regexp 'oW'가 매치되지 않는 이유예요. 네 번째 regexp "World "는 regexp 끝에 공백이 있지만 문자열 끝에는 없으므로 매치되지 않아요. 여기서 배울 교훈은 regexp는 문장이 참이 되기 위해 문자열의 일부를 정확히 매치해야 한다는 거예요.

regexp가 문자열의 여러 곳에서 매치되면 Perl은 항상 문자열에서 가능한 가장 이른 지점에서 매치해요:

"Hello World" =~ /o/;       # matches 'o' in 'Hello'
"That hat is red" =~ /hat/; # matches 'hat' in 'That'

문자 매칭에 관해, 알아야 할 몇 가지가 더 있어요. 먼저 모든 문자가 매치에서 "그대로" 쓰일 수 있진 않아요. 메타문자(metacharacters) 라고 불리는 어떤 문자들은 보통 regexp 표기법에 쓰도록 예약되어 있어요. 메타문자는

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

이 목록은 보이는 것만큼(또는 다른 문서에서 주장되는 것만큼) 결정적이지 않아요. 예를 들어 "#"/x 패턴 수정자(아래 설명)를 쓸 때만 메타문자이고, "}""]" 둘 다 각각 여는 "{" 또는 "["와 짝을 이룰 때만 메타문자예요. 다른 함정들도 적용돼요.

각각의 의미는 튜토리얼 나머지에서 설명될 거지만, 지금은 메타문자 앞에 백슬래시를 붙여 그대로 매치할 수 있다는 것만 알면 충분해요. 이걸 "이스케이프(escaping)" 또는 "인용(quoting)"이라고 불러요. 몇 가지 예:

"2+2=4" =~ /2+2/;    # doesn't match, + is a metacharacter
"2+2=4" =~ /2\+2/;   # matches, \+ is treated like an ordinary +
"The interval is [0,1)." =~ /[0,1)./     # is a syntax error!
"The interval is [0,1)." =~ /\[0,1\)\./  # matches
"#!/usr/bin/perl" =~ /#!\/usr\/bin\/perl/;  # matches

마지막 regexp에서 슬래시 '/'도 백슬래시 처리됐는데, regexp를 구분하는 데 쓰이기 때문이에요. 이건 LTS(leaning toothpick syndrome, 기울어진 이쑤시개 증후군)로 이어질 수 있는데, 구분자를 바꾸는 게 더 읽기 쉬울 때가 많아요.

"#!/usr/bin/perl" =~ m!#\!/usr/bin/perl!;  # easier to read

백슬래시 문자 '\' 자체도 메타문자이고 백슬래시 처리돼야 해요:

'C:\WIN32' =~ /C:\\WIN/;   # matches

특정 메타문자가 평소 의미하는 바가 말이 안 되는 상황에서는, 자동으로 메타문자성을 잃고 문자 그대로 매치될 평범한 문자가 돼요. 예를 들어 '}''{' 메타문자의 짝일 때만 메타문자예요. 그 외에는 문자 그대로의 오른쪽 중괄호로 취급돼요. 이건 예상치 못한 결과로 이어질 수 있어요. use re 'strict'가 이 중 일부를 잡아낼 수 있어요.

메타문자 외에도, 인쇄 가능한 문자 등가물이 없어서 대신 이스케이프 시퀀스 로 표현되는 ASCII 문자들이 있어요. 흔한 예는 탭용 \t, 개행용 \n, 캐리지 리턴용 \r, 벨(경고)용 \a예요. 문자열을 임의 바이트의 시퀀스로 보는 게 더 낫다면 8진수 이스케이프 시퀀스, 예를 들어 \033, 또는 16진수 이스케이프 시퀀스 \x1B가 바이트의 더 자연스러운 표현일 수 있어요. 이스케이프의 몇 가지 예:

"1000\t2000" =~ m(0\t2)   # matches
"1000\n2000" =~ /0\n20/   # matches
"1000\t2000" =~ /\000\t2/ # doesn't match, "0" ne "\000"
"cat"   =~ /\o{143}\x61\x74/ # matches in ASCII, but a weird way
                             # to spell cat

Perl을 좀 해봤다면, 이 이스케이프 시퀀스들의 이야기가 익숙할 거예요. 비슷한 이스케이프 시퀀스가 큰따옴표 문자열에 쓰이고, 사실 Perl의 regexp는 대부분 큰따옴표 문자열로 취급돼요. 이는 변수도 regexp에 쓸 수 있음을 뜻해요. 큰따옴표 문자열처럼, regexp의 변수 값은 매칭 목적으로 regexp가 평가되기 전에 치환될 거예요. 그래서:

$foo = 'house';
'housecat' =~ /$foo/;      # matches
'cathouse' =~ /cat$foo/;   # matches
'housecat' =~ /${foo}cat/; # matches

지금까지 좋아요. 위 지식만으로도 상상할 수 있는 거의 모든 문자 그대로 문자열 regexp로 검색을 수행할 수 있어요. 여기 Unix grep 프로그램의 매우 단순한 에뮬레이션이 있어요:

% cat > simple_grep
#!/usr/bin/perl
$regexp = shift;
while (<>) {
    print if /$regexp/;
}
^D

% chmod +x simple_grep

% simple_grep abba /usr/dict/words
Babbage
cabbage
cabbages
sabbath
Sabbathize
Sabbathizes
sabbatical
scabbard
scabbards

이 프로그램은 이해하기 쉬워요. #!/usr/bin/perl은 셸에서 perl 프로그램을 호출하는 표준 방법이에요. $regexp = shift;는 첫 번째 커맨드라인 인자를 사용할 regexp로 저장하고, 나머지 커맨드라인 인자를 파일로 취급하게 남겨둬요. while (<>)는 모든 파일의 모든 줄을 루프해요. 각 줄에 대해 print if /$regexp/;는 regexp가 줄과 매치되면 그 줄을 출력해요. 이 줄에서 print/$regexp/ 둘 다 기본 변수 $_를 암묵적으로 사용해요.

위의 모든 regexp에서, regexp가 문자열 어디에든 매치되면 매치로 간주됐어요. 하지만 때로는 regexp가 문자열 어디에서 매치를 시도해야 하는지 지정하고 싶을 때가 있어요. 이렇게 하려면 앵커 메타문자 '^''$'를 써요. 앵커 '^'는 문자열 시작에서 매치를 뜻하고 앵커 '$'는 문자열 끝, 또는 문자열 끝의 개행 앞에서 매치를 뜻해요. 사용법은 이래요:

"housekeeper" =~ /keeper/;    # matches
"housekeeper" =~ /^keeper/;   # doesn't match
"housekeeper" =~ /keeper$/;   # matches
"housekeeper\n" =~ /keeper$/; # matches

둘째 regexp는 '^'keeper를 문자열 시작에서만 매치하도록 제약하지만 "housekeeper"는 keeper가 중간에서 시작하므로 매치되지 않아요. 셋째 regexp는 '$'keeper를 문자열 끝에서만 매치하도록 제약하므로 매치돼요.

'^''$'가 동시에 쓰이면, regexp는 문자열 시작과 끝 둘 다 매치해야 해요. 즉 regexp가 문자열 전체를 매치해요. 고려해볼게요:

"keeper" =~ /^keep$/;      # doesn't match
"keeper" =~ /^keeper$/;    # matches
""       =~ /^$/;          # ^$ matches an empty string

첫 regexp는 문자열이 keep보다 더 많은 것을 갖고 있으므로 매치되지 않아요. 둘째 regexp는 정확히 문자열이므로 매치돼요. regexp에 '^''$' 둘 다 쓰는 것은 전체 문자열이 매치되도록 강제하므로, 어떤 문자열이 매치되고 어떤 게 안 되는지 완전히 통제할 수 있어요. bert라는 이름을 문자열에서 혼자 떨어진 채 찾는다고 가정해볼게요:

"dogbert" =~ /bert/;   # matches, but not what you want

"dilbert" =~ /^bert/;  # doesn't match, but ..
"bertram" =~ /^bert/;  # matches, so still not good enough

"bertram" =~ /^bert$/; # doesn't match, good
"dilbert" =~ /^bert$/; # doesn't match, good
"bert"    =~ /^bert$/; # matches, perfect

물론 문자 그대로 문자열의 경우 $string eq 'bert' 문자열 비교를 쓰는 게 더 효율적일 거예요. ^...$ regexp는 아래의 더 강력한 regexp 도구를 더할 때 정말 유용해져요.

문자 클래스 사용하기 (Using character classes)

위의 문자 그대로 문자열 regexp로도 꽤 많이 할 수 있지만, 우리는 정규표현식 기술의 표면만 긁은 것뿐이에요. 이 절과 다음 절들에서 regexp가 단일 문자 시퀀스뿐 아니라 그들의 전체 클래스 를 나타낼 수 있게 하는 regexp 개념(및 연관 메타문자 표기법)을 소개할게요.

그런 개념 하나가 문자 클래스(character class) 예요. 문자 클래스는 regexp의 특정 지점에서 단일 문자가 아니라 가능한 문자의 집합이 매치되게 해요. 자신만의 사용자 정의 문자 클래스를 정의할 수 있어요. 이건 대괄호 [...]로 표시되고, 매치될 수 있는 문자 집합이 안에 있어요. 몇 가지 예:

/cat/;       # matches 'cat'
/[bcr]at/;   # matches 'bat, 'cat', or 'rat'
/item[0123456789]/;  # matches 'item0' or ... or 'item9'
"abc" =~ /[cab]/;    # matches 'a'

마지막 문장에서 'c'가 클래스의 첫 문자이지만 'a'가 매치돼요. 문자열의 첫 문자 위치가 regexp가 매치할 수 있는 가장 이른 지점이기 때문이에요.

/[yY][eE][sS]/;      # match 'yes' in a case-insensitive way
                     # 'yes', 'Yes', 'YES', etc.

이 regexp는 흔한 작업을 보여줘요. 대소문자 무시 매치를 수행하는 거예요. Perl은 매치 끝에 'i'를 붙이기만 하면 그 모든 대괄호를 피하는 방법을 제공해요. 그러면 /[yY][eE][sS]/;/yes/i;로 다시 쓸 수 있어요. 'i'는 대소문자 무시(case-insensitive)를 뜻하고 매칭 연산의 수정자(modifier) 예시예요. 튜토리얼 후반에 다른 수정자들을 만날 거예요.

위 절에서 자기 자신을 나타내는 평범한 문자들과, 스스로를 나타내려면 백슬래시 '\'가 필요한 특수 문자들이 있었다는 걸 봤어요. 문자 클래스에서도 마찬가지지만, 문자 클래스 안의 평범한·특수 문자 집합은 문자 클래스 밖의 그것들과 달라요. 문자 클래스의 특수 문자는 -]\^$(그리고 패턴 구분자, 무엇이든)예요. ']'는 문자 클래스의 끝을 나타내므로 특수해요. '$'는 스칼라 변수를 나타내므로 특수해요. '\'는 위처럼 이스케이프 시퀀스에 쓰이므로 특수해요. 특수 문자 ]$의 처리 방법이에요:

/[\]]c]def/; # matches ']def' or 'cdef'
$x = 'bcr';
/[$x]at/;   # matches 'bat', 'cat', or 'rat'
/[\$x]at/;  # matches '$at' or 'xat'
/[\\$x]at/; # matches '\at', 'bat, 'cat', or 'rat'

마지막 두 개는 약간 까다로워요. [\$x]에서 백슬래시가 달러 기호를 보호하므로 문자 클래스는 두 멤버 '$''x'를 가져요. [\\$x]에서 백슬래시가 보호되므로 $x가 변수로 취급되어 큰따옴표 방식으로 치환돼요.

특수 문자 '-'는 문자 클래스 안에서 범위 연산자로 작용해서, 연속된 문자 집합을 범위로 쓸 수 있어요. 범위를 쓰면 다루기 힘든 [0123456789][abc...xyz]가 날씬한 [0-9][a-z]가 돼요. 몇 가지 예:

/item[0-9]/;  # matches 'item0' or ... or 'item9'
/[0-9bx-z]aa/;  # matches '0aa', ..., '9aa',
                # 'baa', 'xaa', 'yaa', or 'zaa'
/[0-9a-fA-F]/;  # matches a hexadecimal digit
/[0-9a-zA-Z_]/; # matches a "word" character,
                # like those in a Perl variable name

'-'가 문자 클래스의 첫 번째나 마지막 문자이면 평범한 문자로 취급돼요. [-ab], [ab-], [a\-b]는 모두 동등해요.

문자 클래스의 첫 위치의 특수 문자 '^'부정 문자 클래스(negated character class) 를 나타내고, 대괄호 안의 것들을 제외한 어떤 문자든 매치해요. [...][^...] 둘 다 문자를 매치해야 하고, 그렇지 않으면 매치가 실패해요. 그러면:

/[^a]at/;  # doesn't match 'aat' or 'at', but matches
           # all other 'bat', 'cat, '0at', '%at', etc.
/[^0-9]/;  # matches a non-numeric character
/[a^]at/;  # matches 'aat' or '^at'; here '^' is ordinary

이제 [0-9]조차 여러 번 쓰기엔 번거로울 수 있어서, 키 입력을 아끼고 regexp를 더 읽기 좋게 하기 위해 Perl은 아래 보이는 것처럼 흔한 문자 클래스의 여러 약어를 가져요. 유니코드 도입 이후 /a 수정자가 적용 중이지 않으면, 이 문자 클래스들은 ASCII 범위의 소수 문자보다 더 많은 것을 매치해요.

  • \d는 숫자를 매치해요. [0-9]뿐 아니라 로마자가 아닌 문자 체계의 숫자도 매치해요.
  • \s는 공백 문자를 매치해요. 집합 [\ \t\r\n\f]과 그 외의 것들.
  • \w는 단어 문자(영숫자 또는 '_')를 매치해요. [0-9a-zA-Z_]뿐 아니라 로마자가 아닌 문자 체계의 숫자와 문자도 매치해요.
  • \D는 부정 \d예요. 숫자 외의 어떤 문자든, 즉 [^\d]를 나타내요.
  • \S는 부정 \s예요. 어떤 비공백 문자든 [^\s]를 나타내요.
  • \W는 부정 \w예요. 어떤 비단어 문자든 [^\w]를 나타내요.
  • 마침표 '.'"\n"을 제외한 어떤 문자든 매치해요. (아래 설명할 /s 수정자가 적용 중이면 예외.)
  • \N은 마침표처럼 "\n"을 제외한 어떤 문자든 매치하지만, /s 수정자 적용 여부와 무관하게 그래요.

Perl 5.14부터 사용 가능한 /a 수정자는 \d, \s, \w의 매치를 ASCII 범위의 것으로만 제한하는 데 쓰여요. 영어 유사 텍스트만 처리하면 되는데 프로그램이 불필요하게 전체 유니코드(와 그에 따른 보안 고려 사항)에 노출되는 걸 막는 데 유용해요. (이 "a"는 두 번 쓸 수 있어요. /aa는 ASCII와 비-ASCII 문자 사이의 대소문자 무시 매칭을 방지하는 더 많은 제한을 제공해요. 그렇지 않으면 유니코드 "Kelvin 기호"가 대소문자 관계없이 "k"나 "K"와 매치할 거예요.)

\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.'

마침표가 메타문자이므로 평범한 마침표로 매치하려면 이스케이프돼야 해요. 예를 들어 \d\w가 문자 집합이므로, [^\d\w][\D\W]로 생각하는 건 틀렸어요. 사실 [^\d\w][^\w]와 같고, 그것은 [\W]와 같아요. 드모르간의 법칙을 생각해보세요.

실제로 마침표와 \d\s\w\D\S\W 약어는 그들 스스로 일종의 문자 클래스예요. 그래서 대괄호로 감싼 것들은 문자 클래스의 한 유형일 뿐이에요. 구별이 필요할 때는 그것들을 "대괄호 문자 클래스(bracketed character classes)"라고 불러요.

기본 regexp에 유용한 앵커는 단어 앵커 \b예요. 이건 단어 문자와 비단어 문자 \w\W 또는 \W\w 사이의 경계를 매치해요:

$x = "Housecat catenates house and cat";
$x =~ /cat/;    # matches cat in 'housecat'
$x =~ /\bcat/;  # matches cat in 'catenates'
$x =~ /cat\b/;  # matches cat in 'housecat'
$x =~ /\bcat\b/;  # matches 'cat' at end of string

마지막 예시에서 문자열 끝이 단어 경계로 간주된다는 점을 주의하세요.

자연어 처리용으로 (예를 들어 아포스트로피가 단어에 포함되도록) 대신 \b{wb}를 쓰세요:

"don't" =~ / .+? \b{wb} /x;  # matches the whole string

'.'"\n"을 제외한 모든 것을 매치할까 궁금할 수 있어요. 왜 모든 문자를 매치하지 않을까요? 이유는 종종 줄에 대해 매칭하면서 개행 문자를 무시하고 싶기 때문이에요. 예를 들어 "\n" 문자열이 한 줄을 나타내지만, 우리는 그것을 빈 것으로 생각하고 싶어요. 그러면:

""   =~ /^$/;    # matches
"\n" =~ /^$/;    # matches, $ anchors before "\n"

""   =~ /./;      # doesn't match; it needs a char
""   =~ /^.$/;    # doesn't match; it needs a char
"\n" =~ /^.$/;    # doesn't match; it needs a char other than "\n"
"a"  =~ /^.$/;    # matches
"a\n"  =~ /^.$/;  # matches, $ anchors before "\n"

이 동작은 편리한데, 줄에서 문자를 세고 매치할 때 보통 개행을 무시하고 싶기 때문이에요. 하지만 때로는 개행을 추적하고 싶을 때가 있어요. '^''$'가 문자열 시작·끝만이 아니라 문자열 안의 줄의 시작·끝에 앵커하기를 원할 수도 있어요. Perl은 /s/m 수정자를 써서 개행을 무시할지 주의할지 선택할 수 있게 해요. /s/m은 single line(단일 줄)과 multi-line(다중 줄)을 뜻하고 문자열을 하나의 연속 문자열로 취급할지, 줄의 집합으로 취급할지를 결정해요. 두 수정자는 regexp가 해석되는 두 측면에 영향을 줘요: 1) '.' 문자 클래스가 어떻게 정의되는지, 2) 앵커 '^''$'가 어디에서 매치할 수 있는지. 네 가지 가능한 조합이에요:

  • 수정자 없음: 기본 동작. '.'"\n"을 제외한 어떤 문자든 매치. '^'는 문자열 시작에서만, '$'는 끝 또는 끝의 개행 앞에서만 매치.
  • s 수정자 (/s): 문자열을 단일 긴 줄로 취급. '.'"\n"조차 어떤 문자든 매치. '^'는 문자열 시작에서만, '$'는 끝 또는 끝의 개행 앞에서만 매치.
  • m 수정자 (/m): 문자열을 여러 줄의 집합으로 취급. '.'"\n"을 제외한 어떤 문자든 매치. '^''$'는 문자열 안의 어떤 줄의 시작·끝에서 매치할 수 있음.
  • s와 m 둘 다 (/sm): 문자열을 단일 긴 줄로 취급하지만 여러 줄을 감지. '.'"\n"조차 어떤 문자든 매치. 하지만 '^''$'는 문자열 안의 어떤 줄의 시작·끝에서 매치할 수 있음.

/s/m 작동 예시:

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

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

$x =~ /girl.Who/;   # doesn't match, "." doesn't match "\n"
$x =~ /girl.Who/s;  # matches, "." matches "\n"
$x =~ /girl.Who/m;  # doesn't match, "." doesn't match "\n"
$x =~ /girl.Who/sm; # matches, "." matches "\n"

대부분의 경우 기본 동작이 원하는 것이지만, /s/m은 때때로 아주 유용해요. /m이 쓰이면 문자열 시작은 여전히 \A로, 문자열 끝은 여전히 앵커 \Z(끝과 그 앞의 개행 둘 다 매치, '$'처럼)와 \z(끝만 매치)로 매치할 수 있어요:

$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

이제 regexp에서 문자 클래스들 사이의 선택을 만드는 방법을 알았어요. 단어나 문자열 사이의 선택은 어떨까요? 그런 선택은 다음 절에서 설명해요.

이거냐 저거냐 매칭 (Matching this or that)

때로는 regexp가 서로 다른 가능한 단어나 문자열을 매치할 수 있으면 좋겠어요. 이건 대체(alternation) 메타문자 '|'로 달성해요. dog이나 cat을 매치하려면 regexp dog|cat을 만들어요. 전처럼 Perl은 문자열의 가능한 가장 이른 지점에서 regexp를 매치하려 할 거예요. 각 문자 위치에서 Perl은 먼저 첫 대체 dog을 시도해요. dog이 매치되지 않으면 다음 대체 cat을 시도해요. cat도 매치되지 않으면 매치가 실패하고 Perl은 문자열의 다음 위치로 이동해요. 몇 가지 예:

"cats and dogs" =~ /cat|dog|bird/;  # matches "cat"
"cats and dogs" =~ /dog|cat|bird/;  # matches "cat"

둘째 regexp에서 dog이 첫 대체이지만, cat이 문자열에서 더 일찍 매치할 수 있어요.

"cats"          =~ /c|ca|cat|cats/; # matches "c"
"cats"          =~ /cats|cat|ca|c/; # matches "cats"

여기서 모든 대체가 첫 문자열 위치에서 매치되므로 첫 대체가 매치되는 것이에요. 일부 대체가 다른 것의 잘림이라면, 가장 긴 것부터 앞에 두어 매치될 기회를 줘요.

"cab" =~ /a|b|c/ # matches "c"
                 # /a|b|c/ == /[abc]/

마지막 예는 문자 클래스가 문자의 대체와 같다는 걸 지적해요. 주어진 문자 위치에서 regexp 매치가 성공하게 하는 첫 대체가 매치될 거예요.

그룹화와 계층적 매칭 (Grouping things and hierarchical matching)

대체는 regexp가 대안들 중에서 선택하게 하지만, 그것만으로는 만족스럽지 않아요. 각 대체가 전체 regexp이지만, 때로는 regexp의 일부에 대해서만 대안을 원할 때가 있기 때문이에요. 예를 들어 housecats나 housekeepers를 검색하고 싶다고 가정해볼게요. regexp housecat|housekeeper가 부합하지만, house를 두 번 입력해야 해서 비효율적이에요. regexp의 일부는 house처럼 상수이고, 일부는 cat|keeper처럼 대안을 갖는 게 좋겠어요.

그룹화(grouping) 메타문자 ()가 이 문제를 해결해요. 그룹화는 regexp의 일부를 단일 단위로 취급하게 해요. regexp의 일부는 괄호로 감싸 그룹화해요. 그래서 house(cat|keeper) regexp를 형성해 housecat|housekeeper를 해결할 수 있어요. regexp house(cat|keeper)house 다음에 cat 또는 keeper 중 하나가 오는 것을 매치해요. 몇 가지 예:

/(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'.  Note groups can be nested.

/(19|20|)\d\d/;  # match years 19xx, 20xx, or the Y2K problem, xx
"20" =~ /(19|20|)\d\d/;  # matches the null alternative '()\d\d',
                         # because '20\d\d' can't match

대체는 그룹 안에서도 밖에서와 같은 방식으로 동작해요. 주어진 문자열 위치에서 regexp가 매치되게 하는 가장 왼쪽 대체가 채택돼요. 그래서 마지막 예의 첫 문자열 위치에서 "20"이 둘째 대체와 매치되지만, 다음 두 자리 \d\d를 매치할 남은 것이 없어요. 그래서 Perl은 다음 대체인 null 대체로 이동하고, "20"이 두 자리이므로 그게 작동해요.

하나의 대체를 시도하고, 매치되는지 보고, 매치되지 않으면 이전 대체를 시도했던 문자열의 위치로 돌아가며 다음 대체로 이동하는 과정을 백트래킹(backtracking) 이라고 불러요. "백트래킹"이라는 용어는 regexp 매칭이 숲 속 산책과 같다는 생각에서 나와요. regexp를 성공적으로 매칭하는 건 목적지에 도착하는 것과 같아요. 가능한 trailhead(시작점)가 많고, 문자열 위치마다 하나씩이며, 각각 왼쪽에서 오른쪽으로 순서대로 시도돼요. 각 trailhead에서 많은 경로가 있을 수 있고, 일부는 당신을 거기 데려가고 일부는 막다른 길이에요. 산책로를 따라 걷다 막다른 길을 만나면, 다른 산책로를 시도하려고 산책로를 따라 이전 지점으로 백트래킹해야 해요. 목적지에 도달하면 즉시 멈추고 다른 모든 산책로 시도를 잊어요. 당신은 끈질기고, 모든 trailhead의 모든 산책로를 시도하고 목적지에 도달하지 못했을 때만 실패를 선언해요. 구체적으로, Perl이 regexp를 매치하려 할 때 하는 일의 단계별 분석이에요:

"abcde" =~ /(abd|abc)(df|d|de)/;
  1. 문자열의 첫 글자 'a'로 시작.
  2. 첫 그룹의 첫 대체 'abd'를 시도.
  3. 'a' 다음에 'b'를 매치. 지금까지 좋아.
  4. regexp의 'd'가 문자열의 'c'와 매치되지 않음 - 막다른 길. 그래서 두 문자 백트래킹하고 첫 그룹의 둘째 대체 'abc'를 선택.
  5. 'a' 다음 'b' 다음 'c'를 매치. 순조롭고 첫 그룹을 충족. $1'abc'로 설정.
  6. 둘째 그룹으로 이동하고 첫 대체 'df'를 선택.
  7. 'd'를 매치.
  8. regexp의 'f'가 문자열의 'e'와 매치되지 않아 막다른 길. 한 문자 백트래킹하고 둘째 그룹의 둘째 대체 'd'를 선택.
  9. 'd'가 매치. 둘째 그룹이 충족되므로 $2'd'로 설정.
  10. regexp의 끝에 도달, 끝났다! 문자열 "abcde"에서 'abcd'를 매치했어.

이 분석에 대해 주목할 몇 가지가 있어요. 첫째, 둘째 그룹의 셋째 대체 'de'도 매치를 허용하지만, 우리는 그 전에 멈췄어요. 주어진 문자 위치에서는 왼쪽이 이기니까요. 둘째, 문자열의 첫 문자 위치 'a'에서 매치를 얻을 수 있었어요. 첫 위치에서 매치가 없었다면 Perl은 둘째 문자 위치 'b'로 이동해 매치를 다시 시도했을 거예요. 모든 가능한 문자 위치의 모든 가능한 경로가 소진됐을 때만 Perl은 포기하고 $string =~ /(abd|abc)(df|d|de)/;가 거짓임을 선언해요.

이 모든 작업에도 불구하고 regexp 매칭은 놀랍게도 빠르게 일어나요. 속도를 높이기 위해 Perl은 regexp를 종종 프로세서 캐시에 들어갈 수 있는 조밀한 opcode 시퀀스로 컴파일해요. 코드가 실행되면 이 opcode들은 전속력으로 달리며 매우 빠르게 검색할 수 있어요.

매치 추출하기 (Extracting matches)

그룹화 메타문자 ()는 완전히 다른 기능도 제공해요. 매치된 문자열의 부분을 추출하게 해주죠. 이건 무엇이 매치됐는지 알아내는 데, 그리고 일반적인 텍스트 처리에 아주 유용해요. 각 그룹화에 대해 안에서 매치된 부분이 특수 변수 $1, $2, 등등 으로 들어가요. 보통 변수처럼 쓸 수 있어요:

    # extract hours, minutes, seconds
    if ($time =~ /(\d\d):(\d\d):(\d\d)/) {    # match hh:mm:ss format
	$hours = $1;
	$minutes = $2;
	$seconds = $3;
    }

이제 스칼라 컨텍스트에서 $time =~ /(\d\d):(\d\d):(\d\d)/가 참 또는 거짓 값을 돌려준다는 걸 알아요. 하지만 리스트 컨텍스트에서는 매치된 값들의 리스트 ($1,$2,$3)를 돌려줘요. 그래서 더 컴팩트하게 쓸 수 있어요:

    # extract hours, minutes, seconds
    ($hours, $minutes, $second) = ($time =~ /(\d\d):(\d\d):(\d\d)/);

regexp의 그룹화가 중첩되어 있으면 $1은 가장 왼쪽 여는 괄호의 그룹을, $2는 다음 여는 괄호를 등등 가져요. 중첩 그룹이 있는 regexp:

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

이 regexp가 매치되면 $1'ab'로 시작하는 문자열을, $2'cd' 또는 'ef' 중 하나로, $3'gi' 또는 'j'와 같고, $4$3처럼 'gi'로 설정되거나 정의되지 않은 채 남아요.

편의를 위해 Perl은 $+를 가장 높은 번호의 $1, $2,... 중 할당된 것을 가진 문자열로 설정해요. (그리고 다소 관련해서, $^N을 가장 최근에 할당된 $1, $2,...의 값으로, 즉 매치에 쓰인 가장 오른쪽 닫는 괄호와 연관된 $1, $2,...로 설정해요.)

역참조 (Backreferences)

매칭 변수 $1, $2, ...와 밀접하게 연관된 것은 역참조(backreferences) \g1, \g2,...이에요. 역참조는 regexp 에서 쓸 수 있는 매칭 변수일 뿐이에요. 정말 좋은 기능인데, regexp 후반에 매치되는 것이 regexp 전반에 매치된 것에 의존하게 만들어요. "the the" 같은 중복 단어를 텍스트에서 찾고 싶다고 가정해볼게요. 다음 regexp는 사이에 공백이 있는 모든 3글자 중복을 찾아요:

/\b(\w\w\w)\s\g1\b/;

그룹화가 \g1에 값을 할당하므로, 같은 3글자 시퀀스가 두 부분 모두에 쓰여요.

비슷한 작업은 두 개의 동일한 부분으로 이뤄진 단어를 찾는 거예요:

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

regexp는 4글자 조합, 3글자 조합, 등등 을 고려하는 단일 그룹화를 갖고, \g1을 써서 반복을 찾아요. $1\g1이 같은 것을 나타내지만, 매칭 변수 $1, $2,...은 regexp 밖에서만, 역참조 \g1, \g2,...은 regexp 안에서만 쓰도록 주의해야 해요. 그렇게 하지 않으면 놀랍고 만족스럽지 않은 결과로 이어질 수 있어요.

상대 역참조 (Relative backreferences)

캡처 그룹이 여럿일 때 역참조용 올바른 번호를 얻으려 여는 괄호를 세는 건 오류가 나기 쉬워요. Perl 5.10부터 더 편리한 기법이 생겼어요. 상대 역참조예요. 바로 앞의 캡처 그룹을 참조하려면 \g-1 또는 \g{-1}을 쓰면 되고, 그 앞의 것은 \g-2 또는 \g{-2}로, 이렇게 계속돼요.

읽기 쉽고 유지보수하기 좋다는 이유 외에도 상대 역참조를 쓰는 또 다른 좋은 이유는 다음 예시에서 보여줘요. 특이한 문자열을 매칭하는 단순한 패턴이 쓰여요:

$a99a = '([a-z])(\d)\g2\g1';   # matches a11a, g22g, x33x, etc.

이제 이 패턴을 편리한 문자열로 저장했으니, 다른 패턴의 일부로 쓰고 싶을 수 있어요:

$line = "code=e99e";
if ($line =~ /^(\w+)=$a99a$/){   # unexpected behavior!
    print "$1 is valid\n";
} else {
    print "bad line: '$line'\n";
}

하지만 이건 매치되지 않아요. 적어도 예상한 방식으로는 아니에요. 보간된 $a99a를 삽입하고 결과 전체 텍스트를 살펴본 뒤에야 역참조가 역효과를 냈다는 게 분명해져요. 부분 표현식 (\w+)가 번호 1을 가로채서 $a99a의 그룹들을 한 단계씩 내려버렸어요. 이건 상대 역참조를 써서 피할 수 있어요:

$a99a = '([a-z])(\d)\g{-1}\g{-2}';  # safe for being interpolated

이름 있는 역참조 (Named backreferences)

Perl 5.10은 이름 있는 캡처 그룹과 이름 있는 역참조도 도입했어요. 캡처 그룹에 이름을 붙이려면 (?<name>...) 또는 (?'name'...)을 써요. 역참조는 \g{name}으로 쓸 수 있어요. 같은 이름을 둘 이상의 그룹에 붙이는 건 허용되지만, 그 경우 같은 이름의 집합 중 가장 왼쪽 것만 참조할 수 있어요. 패턴 밖에서 이름 있는 캡처 그룹은 %+ 해시를 통해 접근할 수 있어요.

yyyy-mm-dd, mm/dd/yyyy, dd.mm.yyyy의 세 형식 중 하나로 주어질 수 있는 달력 날짜를 매치해야 한다고 가정해볼게요. 날짜의 해당 성분을 캡처하는 그룹의 이름으로 각각 'd', 'm', 'y'를 쓰는 세 개의 적절한 패턴을 쓸 수 있어요. 매칭 연산은 세 패턴을 대체로 결합해요:

$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";
    }
}

대체 중 어떤 것이 매치되면 해시 %+가 세 개의 키-값 쌍을 담도록 바인딩돼요.

대체 캡처 그룹 번호 매기기 (Alternative capture group numbering)

또 다른 캡처 그룹 번호 매기기 기법(역시 Perl 5.10부터)은 대체 집합 안에서 그룹을 참조하는 문제를 다뤄요. 하루 중 시간을 민간 또는 군대 스타일로 매칭하는 패턴을 고려해볼게요:

if ( $time =~ /(\d\d|\d):(\d\d)|(\d\d)(\d\d)/ ){
    # process hour and minute
}

결과 처리는 $1$2인지 $3$4인지 결정하는 추가 if 문장을 요구해요. 둘째 대체에서도 그룹 번호 1과 2를 쓸 수 있다면 더 쉬울 텐데, 이건 대체를 감싸는 괄호로 만든 구조 (?|...)가 정확히 해내는 거예요. 이전 패턴의 확장 버전이에요:

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";
}

대체 번호 그룹 안에서, 그룹 번호는 각 대체에 같은 위치에서 시작해요. 그룹 뒤에서는 모든 대체를 걸쳐 도달한 최댓값보다 하나 높은 곳에서 번호 매기기가 계속돼요.

위치 정보 (Position information)

무엇이 매치됐는지 외에도 Perl은 무엇이 매치됐는지의 위치를 @-@+ 배열의 내용으로 제공해요. $-[0]은 전체 매치의 시작 위치이고 $+[0]은 끝 위치예요. 비슷하게 $-[n]$n 매치의 시작 위치, $+[n]은 끝 위치예요. $n이 정의되지 않으면 $-[n]$+[n]도 정의되지 않아요. 그러면 이 코드:

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

이걸 출력해요:

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

regexp에 그룹화가 없어도 문자열에서 정확히 무엇이 매치됐는지 알아내는 건 여전히 가능해요. 그것들을 쓰면 Perl은 $``_을 매치 앞의 문자열 부분으로, $&를 매치된 문자열 부분으로, $'`를 매치 뒤의 문자열 부분으로 설정해요. 예:

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

둘째 매치에서 $``가 ''`과 같은 이유는 regexp가 문자열의 첫 문자 위치에서 매치되고 멈췄기 때문이에요. 두 번째 "the"는 결코 보지 못했죠.

코드가 Perl 5.20 이전 버전에서 실행돼야 한다면, $``과 $'를 쓰면 regexp 매칭이 꽤 느려지고, $&는 덜하지만 느리게 만든다는 걸 알아둘 가치가 있어요. 프로그램의 한 regexp에서 쓰이면 프로그램의 _모든_ regexp에 대해 그것들이 생성되기 때문이에요. 그래서 원시 성능이 응용의 목표라면 피해야 해요. 대응하는 부분 문자열을 추출해야 한다면 대신 @-@+`를 써요:

$` is the same as substr( $x, 0, $-[0] )
$& is the same as substr( $x, $-[0], $+[0]-$-[0] )
$' is the same as substr( $x, $+[0] )

Perl 5.10부터는 ${^PREMATCH}, ${^MATCH}, ${^POSTMATCH} 변수를 쓸 수 있어요. 이들은 /p 수정자가 있을 때만 설정돼요. 결과적으로 프로그램 나머지를 벌하지 않아요. Perl 5.20에서 ${^PREMATCH}, ${^MATCH}, ${^POSTMATCH}/p가 쓰였는지 여부와 무관하게 사용 가능하고(수정자는 무시됨), $``, $', $&`는 어떤 속도 차이도 일으키지 않아요.

비캡처 그룹화 (Non-capturing groupings)

대체 집합을 묶는 데 필요한 그룹은 캡처 그룹으로 유용할 수도 있고 아닐 수도 있어요. 유용하지 않다면, 그것은 regexp 안팎 모두에서 사용 가능한 캡처 그룹 값 집합에 불필요한 추가만 만들 뿐이에요. (?:regexp)로 표시되는 비캡처 그룹화는 여전히 regexp를 단일 단위로 취급하게 하지만, 동시에 캡처 그룹을 세우지는 않아요. 캡처와 비캡처 그룹화 둘 다 같은 regexp에서 공존할 수 있어요. 추출이 없으므로 비캡처 그룹화는 캡처 그룹화보다 빨라요. 비캡처 그룹화는 regexp의 정확히 어떤 부분을 매칭 변수로 추출할지 선택하는 데도 편리해요:

# match a number, $1-$4 are set, but we only want $1
/([+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)/;

# match a number faster , only $1 is set
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/;

# match a number, get $1 = whole number, $2 = exponent
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE]([+-]?\d+))?)/;

비캡처 그룹화는 어떤 이유로 괄호가 필요한 split 연산에서 얻은 성가신 요소를 제거하는 데도 유용해요:

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

Perl 5.22 이후, regexp 안의 모든 그룹은 새 /n 플래그를 써서 비캡처로 설정할 수 있어요:

"hello" =~ /(hi|hello)/n; # $1 is not set!

자세한 내용은 perlre의 "n"을 참고하세요.

반복 매칭 (Matching repetitions)

이전 절의 예시들은 성가신 약점을 보여줘요. 우리는 3글자 단어나 4글자 이하 단어 덩어리만 매치하고 있었어요. \w\w\w\w|\w\w\w|\w\w|\w 같은 지루한 대체를 쓰지 않고도 어떤 길이의 단어, 더 일반적으로는 문자열을 매치할 수 있으면 좋겠어요.

이건 정확히 수량자(quantifier) 메타문자 '?', '*', '+', {}가 만들어진 문제예요. 그것들은 우리가 매치로 간주하는 regexp의 일부에 대한 반복 횟수를 구분하게 해줘요. 수량자는 지정하려는 문자, 문자 클래스, 그룹화 바로 뒤에 놓여요. 다음 의미를 가져요:

  • a?'a'를 1 또는 0번 매치를 뜻해요.
  • a*'a'를 0번 이상, 몇 번이든 매치를 뜻해요.
  • a+'a'를 1번 이상, 적어도 한 번 매치를 뜻해요.
  • a{n,m}은 적어도 n번, 하지만 m번을 넘지 않게 매치를 뜻해요.
  • a{n,}은 적어도 n번 이상 매치를 뜻해요.
  • a{,n}은 기껏해야 n번, 또는 그보다 적게 매치를 뜻해요.
  • a{n}은 정확히 n번 매치를 뜻해요.

원한다면 중괄호 안에, 그리고 그것에 인접해서, 그리고/또는 콤마(있으면) 옆에 공백(탭이나 공백 문자)을 넣을 수 있어요.

몇 가지 예:

/[a-z]+\s+\d*/;  # match a lowercase word, at least one space, and
                 # any number of digits
/(\w+)\s+\g1/;    # match doubled words of arbitrary length
/y(es)?/i;       # matches 'y', 'Y', or a case-insensitive 'yes'
$year =~ /^\d{2,4}$/;  # make sure year is at least 2 but not more
                       # than 4 digits
$year =~ /^\d{ 2, 4 }$/;    # Same; for those who like wide open
                            # spaces.
$year =~ /^\d{2, 4}$/;      # Same.
$year =~ /^\d{4}$|^\d{2}$/; # better match; throw out 3-digit dates
$year =~ /^\d{2}(\d{2})?$/; # same thing written differently.
                            # However, this captures the last two
                            # digits in $1 and the other does not.

% simple_grep '^(\w+)\g1$' /usr/dict/words   # isn't this easier?
beriberi
booboo
coco
mama
murmur
papa

이 모든 수량자에 대해 Perl은 regexp가 성공하도록 허용하면서 문자열을 가능한 한 많이 매치하려 해요. 그래서 /a?.../로 Perl은 먼저 'a'가 있는 상태로 regexp를 매치하려 하고, 실패하면 'a'가 없는 상태로 매치를 시도해요. 수량자 '*'에 대해 다음을 얻어요:

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

예상대로 매치가 문자열의 유일한 cat을 찾아 그것에 고정돼요. 하지만 이 regexp를 고려해볼게요:

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

처음에는 Perl이 catat을 찾아 거기서 멈출 거라 짐작할 수 있는데, 그러면 첫 수량자 .*에 가능한 가장 긴 문자열이 주어지지 않아요. 대신 첫 수량자 .*는 regexp가 매치되도록 하면서 문자열을 가능한 한 많이 움켜쥐어요. 이 예에서는 문자열의 마지막 at 시퀀스를 갖는 것을 의미해요. 여기서 설명된 또 다른 중요한 원칙은, regexp에 두 개 이상의 요소가 있을 때 가장 왼쪽 수량자(있으면)가 문자열을 가능한 한 많이 움켜쥐고, regexp 나머지가 조각을 두고 싸우게 한다는 거예요. 그래서 우리 예에서 첫 수량자 .*가 문자열의 대부분을 움켜쥐고, 둘째 수량자 .*는 빈 문자열을 가져요. 문자열을 가능한 한 많이 움켜쥐는 수량자를 최대 매치(maximal match) 또는 탐욕적(greedy) 수량자라고 불러요.

regexp가 여러 방법으로 문자열을 매치할 수 있을 때, 위 원칙을 써서 regexp가 어떤 방식으로 매치할지 예측할 수 있어요:

  • 원칙 0: 전체적으로 어떤 regexp든 문자열에서 가능한 가장 이른 위치에서 매치될 거예요.
  • 원칙 1: 대체 a|b|c...에서 전체 regexp를 위한 매치를 허용하는 가장 왼쪽 대체가 쓰일 거예요.
  • 원칙 2: 최대 매칭 수량자 '?', '*', '+', {n,m}은 일반적으로 전체 regexp가 매치되도록 허용하면서 문자열을 가능한 한 많이 매치할 거예요.
  • 원칙 3: regexp에 두 개 이상의 요소가 있으면, 가장 왼쪽 탐욕적 수량자(있다면)는 전체 regexp가 매치되도록 허용하면서 가능한 한 많은 문자열을 매치할 거예요. 다음 가장 왼쪽 탐욕적 수량자(있다면)는 전체 regexp가 매치되도록 허용하면서 자신에게 남은 가능한 한 많은 문자열을 매치하려 할 거예요. 이렇게 모든 regexp 요소가 충족될 때까지 계속돼요.

위에서 봤듯이 원칙 0이 나머지를 덮어써요. regexp는 가능한 한 일찍 매치되고, 다른 원칙들이 그 가장 이른 문자 위치에서 regexp가 어떻게 매치되는지 결정해요.

이 원칙들이 작동하는 예:

$x = "The programming republic of Perl";
$x =~ /^(.+)(e|r)(.*)$/;  # matches,
                          # $1 = 'The programming republic of Pe'
                          # $2 = 'r'
                          # $3 = 'l'

이 regexp는 가장 이른 문자열 위치 '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'

여기서 regexp는 문자열 시작에서 매치돼요. 첫 수량자 .*가 가능한 한 많이 움켜쥐고, 둘째 수량자 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*)/; # matches with $1 = ''

문자열 시작에서 'X'의 0개 복사본을 매치할 수 있기 때문이에요. 확실히 적어도 하나의 'X'를 매치하려면 X*가 아니라 X+를 써요.

때로는 탐욕이 좋지 않아요. 때로는 수량자가 최대 조각이 아니라 최소 조각의 문자열을 매치하길 원해요. 이 목적으로 Larry Wall이 최소 매치(minimal match) 또는 비탐욕적(non-greedy) 수량자 ??, *?, +?, {}?를 만들었어요. 이들은 평소 수량자에 '?'를 붙인 것이에요. 다음 의미를 가져요:

  • a??'a'를 0 또는 1번 매치를 뜻해요. 0을 먼저 시도하고, 그다음 1.
  • a*?'a'를 0번 이상, 몇 번이든, 하지만 가능한 한 적게 매치를 뜻해요.
  • a+?'a'를 1번 이상, 적어도 한 번, 하지만 가능한 한 적게 매치를 뜻해요.
  • a{n,m}?는 적어도 n번, m번 넘지 않게, 가능한 한 적게 매치를 뜻해요.
  • a{n,}?는 적어도 n번, 하지만 가능한 한 적게 매치를 뜻해요.
  • a{,n}?는 기껏해야 n번, 하지만 가능한 한 적게 매치를 뜻해요.
  • a{n}?는 정확히 n번 매치를 뜻해요. 정확히 n번 매치하므로 a{n}?a{n}과 동등하고 표기상 일관성을 위해 있을 뿐이에요.

위 예를 최소 수량자로 보겠어요:

$x = "The programming republic of Perl";
$x =~ /^(.+?)(e|r)(.*)$/; # matches,
                          # $1 = 'Th'
                          # $2 = 'e'
                          # $3 = ' programming republic of Perl'

문자열 시작 '^'과 대체 둘 다를 매치하게 할 최소 문자열은 Th이고, 대체 e|r'e'를 매치해요. 둘째 수량자 .*는 남은 문자열을 마음껏 삼킬 수 있어요.

$x =~ /(m{1,2}?)(.*?)$/;  # matches,
                          # $1 = 'm'
                          # $2 = 'ming republic of Perl'

이 regexp가 매치할 수 있는 첫 문자열 위치는 programming의 첫 'm'이에요. 이 위치에서 최소 m{1,2}?는 단지 하나의 'm'만 매치해요. 둘째 수량자 .*?가 문자를 매치하지 않기를 선호하지만, 문자열 끝 앵커 '$'에 제약되어 나머지 문자열을 매치해야 해요.

$x =~ /(.*?)(m{1,2}?)(.*)$/;  # matches,
                              # $1 = 'The progra'
                              # $2 = 'm'
                              # $3 = 'ming republic of Perl'

이 regexp에서 첫 최소 수량자 .*?가 빈 문자열을 매치할 거라 기대할 수 있어요. '^' 앵커에 제약되어 단어 시작을 매치해야 하지 않으니까요. 하지만 여기서 원칙 0이 적용돼요. 전체 regexp가 문자열 시작에서 매치될 수 있으므로, 문자열 시작에서 매치될 거예요. 그래서 첫 수량자는 첫 'm'까지의 모든 것을 매치해야 해요. 둘째 최소 수량자는 단지 하나의 'm'을 매치하고 셋째 수량자는 나머지 문자열을 매치해요.

$x =~ /(.??)(m{1,2})(.*)$/;  # matches,
                             # $1 = 'a'
                             # $2 = 'mm'
                             # $3 = 'ing republic of Perl'

이전 regexp와 마찬가지로 첫 수량자 .??는 위치 'a'에서 가장 이르게 매치할 수 있으므로 그렇게 해요. 둘째 수량자는 탐욕적이라 mm을 매치하고, 셋째는 나머지 문자열을 매치해요.

비탐욕적 수량자를 고려하도록 원칙 3을 수정할 수 있어요:

  • 원칙 3: regexp에 두 개 이상의 요소가 있으면, 가장 왼쪽 탐욕적(비탐욕적) 수량자(있다면)는 전체 regexp가 매치되도록 허용하면서 가능한 한 많은(적은) 문자열을 매치할 거예요. 다음 가장 왼쪽 탐욕적(비탐욕적) 수량자(있다면)는 전체 regexp가 매치되도록 허용하면서 자신에게 남은 가능한 한 많은(적은) 문자열을 매치하려 할 거예요. 이렇게 모든 regexp 요소가 충족될 때까지 계속돼요.

대체처럼 수량자도 백트래킹에 취약해요. 예의 단계별 분석이에요:

$x = "the cat in the hat";
$x =~ /^(.*)(at)(.*)$/; # matches,
                        # $1 = 'the cat in the h'
                        # $2 = 'at'
                        # $3 = ''   (0 matches)
  1. 문자열의 첫 글자 't'로 시작.
  2. 첫 수량자 '.*'가 전체 문자열 "the cat in the hat"을 매치하는 것에서 시작.
  3. regexp 요소 'at''a'가 문자열 끝과 매치되지 않음. 한 문자 백트래킹.
  4. regexp 요소 'at''a'가 여전히 문자열의 마지막 글자 't'와 매치되지 않으므로 한 문자 더 백트래킹.
  5. 이제 'a''t'를 매치할 수 있음.
  6. 셋째 요소 '.*'로 이동. 문자열 끝에 있고 '.*'가 0번 매치할 수 있으므로 빈 문자열을 할당.
  7. 끝났다!

대부분의 경우 이 전진·백트래킹이 빠르게 일어나고 검색이 빨라요. 하지만 실행 시간이 문자열 크기에 따라 기하급수적으로 커지는 병적인(병리적) regexp들이 있어요. 당신 얼굴에 터지는 전형적인 구조는 이 형태예요:

/(a|b+)*/;

문제는 중첩된 부정(불확정) 수량자예요. 길이 n의 문자열을 '+''*' 사이에 분할하는 여러 방법이 있어요. 길이 n의 b+ 하나로, 첫 b+가 길이 k이고 둘째가 길이 n-k인 두 반복, 비트가 n 길이에 더해지는 m 반복, 등등. 사실 문자열을 길이의 함수로 분할하는 방법은 기하급수적으로 많아요. regexp는 운이 좋아 과정에서 일찍 매치될 수 있지만, 매치가 없으면 Perl은 포기하기 전에 모든 가능성을 시도할 거예요. 그래서 중첩된 '*'들, {n,m}들, '+'들에 주의하세요. Jeffrey Friedl의 책 _Mastering Regular Expressions_가 이것과 다른 효율성 문제에 대한 훌륭한 논의를 제공해요.

소유 수량자 (Possessive quantifiers)

매치를 향한 끈질긴 탐색 중 백트래킹은 시간 낭비일 수 있어요. 특히 매치가 확실히 실패할 때요. 단순한 패턴을 고려해볼게요:

/^\w+\s+\w+$/; # a word, spaces, a word

이것이 "abc ""abc def "처럼 패턴 기대에 정확히 맞지 않는 문자열에 적용될 때마다, regexp 엔진은 문자열의 각 문자에 대해 대략 한 번 백트래킹할 거예요. 하지만 처음 단어 문자 _모두_를 가져가 첫 반복을 매치하는 것 말고는 방법이 없고, 모든 공백은 중간 부분이 먹어야 하며, 둘째 단어도 마찬가지라는 걸 알아요.

Perl 5.10에서 소유 수량자(possessive quantifiers) 의 도입으로, 평소 수량자에 '+'를 붙여 regexp 엔진에 백트래킹하지 말라고 지시하는 방법이 생겼어요. 이건 그것들을 탐욕적이면서 인색하게 만들어요. 한번 성공하면 다른 해법을 허용하려 아무것도 돌려주지 않아요. 다음 의미를 가져요:

  • a{n,m}+는 적어도 n번, m번 넘지 않게, 가능한 한 많이 매치하고, 아무것도 내주지 않음을 뜻해요. a?+a{0,1}+의 줄임말이에요.
  • a{n,}+는 적어도 n번, 하지만 가능한 한 많이 매치하고, 아무것도 내주지 않음을 뜻해요. a++a{1,}+의 줄임말이에요.
  • a{,n}+는 기껏해야 n번까지 가능한 한 많이 매치하고, 아무것도 내주지 않음을 뜻해요. a*+a{0,}+의 줄임말이에요.
  • a{n}+는 정확히 n번 매치를 뜻해요. 표기상 일관성을 위해 있을 뿐이에요.

이 소유 수량자들은 더 일반적인 개념인 독립 부분 표현식(independent subexpression) 의 특수한 경우를 나타내요. 아래 참조.

소유 수량자가 적합한 예로, 여러 프로그래밍 언어에 나타나는 인용 문자열 매칭을 고려해볼게요. 백슬래시는 이스케이프 문자로 쓰여, 다음 문자가 문자열의 또 다른 문자로 문자 그대로 취급되어야 함을 나타내요. 그래서 여는 인용부호 뒤에 (아마 빈) 대체 시퀀스를 기대해요. 이스케이프되지 않은 인용부호나 백슬래시를 제외한 어떤 문자, 또는 이스케이프된 문자요.

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

regexp 만들기 (Building a regexp)

이 시점에서 우리는 모든 기본 regexp 개념을 다뤘으니, 더 복잡한 정규표현식의 예를 들어볼게요. 숫자를 매치하는 regexp를 만들 거예요.

regexp를 만드는 첫 작업은 무엇을 매치하고 무엇을 제외할지 결정하는 거예요. 우리 경우 정수와 부동소수점 숫자 둘 다 매치하고, 숫자가 아닌 어떤 문자열도 거부하고 싶어요.

다음 작업은 문제를 regexp로 쉽게 변환되는 더 작은 문제들로 쪼개는 거예요.

가장 단순한 경우는 정수예요. 이들은 앞에 선택적 부호가 있는 숫자 시퀀스로 이뤄져요. 숫자는 \d+로 나타낼 수 있고 부호는 [+-]로 매치할 수 있어요. 그래서 정수 regexp는:

/[+-]?\d+/;  # matches integers

부동소수점 숫자는 잠재적으로 부호, 정수 부분, 소수점, 소수 부분, 지수를 가져요. 이 부분 중 하나 이상이 선택적이므로, 다른 가능성을 확인해야 해요. 올바른 형식의 부동소수점 숫자는 123., 0.345, .34, -1e6, 25.4E-72를 포함해요. 정수처럼 앞의 부호는 완전히 선택적이고 [+-]?로 매치할 수 있어요. 지수가 없으면 부동소수점 숫자는 소수점이 있어야 하고, 그렇지 않으면 정수임을 볼 수 있어요. 이들을 \d*\.\d*로 모델링하고 싶을 수 있는데, 그러면 숫자가 아닌 단일 소수점도 매치할 거예요. 그래서 지수가 없는 부동소수점 숫자의 세 경우는:

/[+-]?\d+\./;  # 1., 321., etc.
/[+-]?\.\d+/;  # .1, .234, etc.
/[+-]?\d+\.\d+/;  # 1.0, 30.56, etc.

이들은 삼중 대체의 단일 regexp로 결합할 수 있어요:

/[+-]?(\d+\.\d+|\d+\.|\.\d+)/;  # floating point, no exponent

이 대체에서 '\d+\.\d+''\d+\.'보다 앞에 두는 게 중요해요. '\d+\.'가 먼저였다면 regexp가 만족스럽게 그것을 매치하고 숫자의 소수 부분을 무시할 거예요.

이제 지수가 있는 부동소수점 숫자를 고려해볼게요. 여기서 핵심 관찰은 지수 앞에 정수 소수점 있는 숫자 모두가 허용된다는 거예요. 그러면 지수는 전체 부호처럼, 소수점 있거나 없는 숫자에 매치하는지와는 독립적이고, 가수(mantissa)에서 "결합 해제"될 수 있어요. regexp의 전체 형태가 이제 명확해져요:

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

지수는 'e' 또는 'E' 다음에 정수예요. 그래서 지수 regexp는:

/[eE][+-]?\d+/;  # exponent

모든 부분을 합치면 숫자를 매치하는 regexp를 얻어요:

/^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/;  # Ta da!

이런 긴 regexp는 친구들을 감탄시키지만, 해독하기 어려울 수 있어요. 이 같은 복잡한 상황에서 매치용 /x 수정자는 귀중해요. 거의 임의의 공백과 주석을 의미에 영향 없이 regexp에 넣게 해주죠. 그것을 써서 "확장된" regexp를 더 보기 좋은 형태로 다시 쓸 수 있어요:

/^
   [+-]?         # first, match an optional sign
   (             # then match integers or f.p. mantissas:
       \d+\.\d+  # mantissa of the form a.b
      |\d+\.     # mantissa of the form a.
      |\.\d+     # mantissa of the form .b
      |\d+       # integer of the form a
   )
   ( [eE] [+-]? \d+ )?  # finally, optionally match an exponent
$/x;

공백이 대부분 무관하다면 확장된 regexp에 공백 문자를 어떻게 포함할까요? 답은 백슬래시 처리 '\ '를 하거나 문자 클래스 [ ]에 넣는 거예요. 파운드 기호도 마찬가지예요. \# 또는 [#]를 써요. 예를 들어 Perl은 부호와 가수·정수 사이의 공백을 허용하는데, 우리는 이걸 regexp에 이렇게 추가할 수 있어요:

/^
   [+-]?\ *      # first, match an optional sign *and space*
   (             # then match integers or f.p. mantissas:
       \d+\.\d+  # mantissa of the form a.b
      |\d+\.     # mantissa of the form a.
      |\.\d+     # mantissa of the form .b
      |\d+       # integer of the form a
   )
   ( [eE] [+-]? \d+ )?  # finally, optionally match an exponent
$/x;

이 형태에서는 대체를 단순화하는 방법을 보기가 더 쉬워요. 대체 1, 2, 4가 모두 \d+로 시작하므로, 그것을 인수 분해할 수 있어요:

/^
   [+-]?\ *      # first, match an optional sign
   (             # then match integers or f.p. mantissas:
       \d+       # start out with a ...
       (
           \.\d* # mantissa of the form a.b or a.
       )?        # ? takes care of integers of the form a
      |\.\d+     # mantissa of the form .b
   )
   ( [eE] [+-]? \d+ )?  # finally, optionally match an exponent
$/x;

Perl v5.26부터 /xx를 지정하면 패턴의 대괄호 부분이 백슬래시로 이스케이프되지 않는 한 탭과 공백 문자를 무시하도록 바꿔요. 그래서 이렇게 쓸 수 있어요:

/^
   [ + - ]?\ *   # first, match an optional sign
   (             # then match integers or f.p. mantissas:
       \d+       # start out with a ...
       (
           \.\d* # mantissa of the form a.b or a.
       )?        # ? takes care of integers of the form a
      |\.\d+     # mantissa of the form .b
   )
   ( [ e E ] [ + - ]? \d+ )?  # finally, optionally match an exponent
$/xx;

이건 이 예의 가독성을 실제로 개선하진 않지만, 원한다면 쓸 수 있어요. 패턴을 컴팩트한 형태로 압축하면:

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

이게 우리의 최종 regexp예요. 정리하면, 우리는 regexp를 이렇게 만들어왔어요:

  • 작업을 상세히 명시하기,
  • 문제를 더 작은 부분으로 쪼개기,
  • 작은 부분들을 regexp로 번역하기,
  • regexp들 결합하기,
  • 최종 결합된 regexp 최적화하기.

이것들은 컴퓨터 프로그램을 쓰는 데 관여하는 전형적인 단계이기도 해요. 이건 완벽하게 이치에 맞아요. 정규표현식은 본질적으로 패턴을 지정하는 작은 컴퓨터 언어로 쓰인 프로그램이기 때문이에요.

Perl에서 정규표현식 사용하기 (Using regular expressions in Perl)

Part 1의 마지막 주제는 regexp가 Perl 프로그램에서 어떻게 쓰이는지 간단히 다뤄요. 그것들은 Perl 문법에서 어디에 들어맞을까요?

우리는 이미 매칭 연산자를 기본 /regexp/와 임의 구분자 m!regexp! 형태로 소개했어요. 문자열 매치를 테스트하는 데 바인딩 연산자 =~와 그 부정 !~을 썼어요. 매칭 연산자와 연관되어 단일 줄 /s, 다중 줄 /m, 대소문자 무시 /i, 확장 /x 수정자를 논의했어요. 매칭 연산자에 대해 알고 싶을 것이 몇 가지 더 있어요.

치환 금지 (Prohibiting substitution)

첫 번째 치환이 일어난 뒤 $pattern을 바꾸면 Perl은 그것을 무시해요. 치환을 전혀 원하지 않으면 특수 구분자 m''을 써요:

@pattern = ('Seuss');
while (<>) {
    print if m'@pattern';  # matches literal '@pattern', not 'Seuss'
}

문자열과 비슷하게 m''은 regexp에 대한 아포스트로피처럼 작용해요. 다른 모든 'm' 구분자는 인용부호처럼 작용해요. regexp가 빈 문자열로 평가되면, 마지막 성공한 매치 의 regexp가 대신 쓰여요. 그래서:

"dog" =~ /d/;  # 'd' matches
"dogbert" =~ //;  # this matches the 'd' regexp used before

전역 매칭 (Global matching)

마지막으로 논의할 두 수정자 /g/c는 여러 매치에 관련돼요. 수정자 /g는 전역 매칭을 뜻하고 매칭 연산자가 문자열 안에서 가능한 한 많이 매치하게 해요. 스칼라 컨텍스트에서 문자열에 대한 연속 호출은 /g가 매치에서 매치로 점프하고, 가면서 문자열의 위치를 추적하게 해요. pos() 함수로 위치를 얻거나 설정할 수 있어요.

/g의 사용은 다음 예시에 나와요. 공백으로 구분된 단어들로 이뤄진 문자열이 있다고 가정해볼게요. 단어가 몇 개인지 미리 알면 그룹화로 단어들을 추출할 수 있어요:

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

하지만 단어가 불확정 개수라면 어떨까요? 이건 /g를 위해 만들어진 종류의 작업이에요. 모든 단어를 추출하려면 단순한 regexp (\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

실패한 매치나 대상 문자열 변경이 위치를 리셋해요. 매치 실패 후 위치가 리셋되는 걸 원하지 않으면 /regexp/gc처럼 /c를 추가해요. 문자열의 현재 위치는 regexp가 아니라 문자열과 연관돼요. 이는 서로 다른 문자열이 서로 다른 위치를 갖고, 그 각각의 위치를 독립적으로 설정하거나 읽을 수 있음을 뜻해요.

리스트 컨텍스트에서 /g는 매치된 그룹화 리스트를, 그룹화가 없으면 전체 regexp의 매치 리스트를 돌려줘요. 그래서 단어만 원한다면:

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

/g 수정자와 밀접하게 연관된 것은 \G 앵커예요. \G 앵커는 이전 /g 매치가 중단된 지점에서 매치해요. \G는 문맥에 민감한 매칭을 쉽게 하게 해줘요:

$metric = 1;  # use metric units
...
$x = <FILE>;  # read in measurement
$x =~ /^([+-]?\d+)\s*/g;  # get magnitude
$weight = $1;
if ($metric) { # error checking
    print "Units error!" unless $x =~ /\Gkg\./g;
}
else {
    print "Units error!" unless $x =~ /\Glbs\./g;
}
$x =~ /\G\s+(widget|sprocket)/g;  # continue processing

/g\G의 조합은 문자열을 조금씩 처리하고, 임의의 Perl 로직을 써서 다음에 무엇을 할지 결정하게 해줘요.

\G는 고정 길이 레코드를 regexp로 처리하는 데도 귀중해요. 코딩 영역 DNA의 조각이 있고, 염기쌍 문자 ATCGTTGAAT...로 인코딩되어 있고, 모든 종결 코돈(stop codon) TGA를 찾고 싶다고 가정해볼게요. 코딩 영역에서 코돈은 3글자 시퀀스이므로 DNA 조각을 3글자 레코드 시퀀스로 생각할 수 있어요. 순진한 regexp:

# expanded, this is "ATC GTT GAA TGC AAA TGA CAT GAC"
$dna = "ATCGTTGAATGCAAATGACATGAC";
$dna =~ /TGA/;

는 작동하지 않아요. TGA를 매치할 수 있지만, 매치가 코돈 경계와 정렬되리란 보장이 없어요. 예를 들어 부분 문자열 GTT GAA가 매치를 줘요. 더 나은 해법은:

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은 가짜예요. 무슨 일이 일어났을까요?

답은 우리 regexp가 마지막 실제 매치를 지나갈 때까지 잘 작동한다는 거예요. 그러면 regexp가 동기화된 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

이게 올바른 답이에요. 이 예는 원하는 것을 매치하는 것뿐 아니라 원하지 않는 것을 거부하는 것도 중요함을 보여줘요.

(다른 regexp 수정자도 있습니다. /o처럼요. 하지만 그것들의 특화된 용도는 이 소개의 범위를 벗어나요.)

검색과 바꾸기 (Search and replace)

정규표현식은 Perl의 검색·바꾸기(search and replace) 연산에서도 큰 역할을 해요. 검색과 바꾸기는 s/// 연산자로 달성돼요. 일반적인 형태는 s/regexp/replacement/modifiers이고, regexp와 수정자에 대해 아는 모든 것이 이 경우에도 적용돼요. replacement 는 Perl 큰따옴표 문자열로, regexp와 매치된 것을 문자열에서 대체해요. 연산자 =~도 여기서 문자열을 s///와 연결하는 데 쓰여요. $_에 대해 매칭한다면 $_ =~을 뗄 수 있어요. 매치가 있으면 s///는 수행된 치환 횟수를 돌려주고, 아니면 거짓을 돌려줘요. 몇 가지 예:

$x = "Time to feed the cat!";
$x =~ s/cat/hacker/;   # $x contains "Time to feed the hacker!"
if ($x =~ s/^(Time.*hacker)!$/$1 now!/) {
    $more_insistent = 1;
}
$y = "'quoted words'";
$y =~ s/^'(.*)'$/$1/;  # strip single quotes,
                       # $y contains "quoted words"

마지막 예에서 전체 문자열이 매치됐지만, 단일 인용부호 안의 부분만 그룹화됐어요. s/// 연산자로 매칭 변수 $1, $2, 등등 이 대체 표현식에서 즉시 사용 가능하므로, $1을 써서 인용 문자열을 인용된 것만으로 바꿔요. 전역 수정자로 s///g는 문자열에서 regexp의 모든 발생을 검색·바꿔요:

$x = "I batted 4 for 4";
$x =~ s/4/four/;   # doesn't do it all:
                   # $x contains "I batted four for 4"
$x = "I batted 4 for 4";
$x =~ s/4/four/g;  # does it all:
                   # $x contains "I batted four for four"

이 튜토리얼에서 "regexp"보다 "regex"를 선호한다면, 다음 프로그램으로 바꿀 수 있어요:

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

% simple_replace regexp regex perlretut.pod

simple_replace에서 s///g 수정자를 써서 각 줄에서 regexp의 모든 발생을 바꿨어요. (정규표현식이 루프에 나타나지만 Perl은 그것을 한 번만 컴파일할 만큼 똑똑해요.) simple_grep처럼 prints/$regexp/$replacement/g 둘 다 $_를 암묵적으로 사용해요.

s///가 원래 변수를 바꾸길 원하지 않으면 비파괴 치환 수정자 s///r을 쓸 수 있어요. 이건 동작을 바꿔 s///r이 최종 치환된 문자열을 돌려주게 해요 (치환 횟수 대신):

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

이 예시는 "I like dogs. I like cats"를 출력할 거예요. 원래 $x 변수는 영향을 받지 않았다는 점을 주목하세요. 치환의 전체 결과가 대신 $y에 저장돼요. 치환이 아무것도 바꾸지 않으면 원래 문자열이 돌려져요:

$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."

검색·바꾸기에 특별히 제공되는 수정자는 s///e 평가 수정자예요. s///e는 대체 텍스트를 큰따옴표 문자열이 아니라 Perl 코드로 취급해요. 코드가 돌려주는 값이 매치된 부분 문자열을 대체해요. 텍스트를 바꾸는 과정에서 약간의 계산이 필요하다면 s///e가 유용해요. 이 예는 줄에서 문자 빈도를 세요:

$x = "Bill the cat";
$x =~ s/(.)/$chars{$1}++;$1/eg; # final $1 replaces char with itself
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

매치 m// 연산자처럼 s///s!!!, s{}{}, 심지어 s{}// 같은 다른 구분자를 쓸 수 있어요. 단일 인용부호 s'''를 쓰면 regexp와 replacement가 단일 인용 문자열로 취급되고 변수 치환이 없어요. 리스트 컨텍스트의 s///는 스칼라 컨텍스트와 같은 것, 매치 수를 돌려줘요.

split 함수 (The split function)

split() 함수는 regexp가 쓰이는 또 다른 곳이에요. split /regexp/, string, limitstring 피연산자를 부분 문자열 리스트로 분리하고 그 리스트를 돌려줘요. regexp는 원하는 부분 문자열을 구성하는 구분자에 해당하는 것을 매치하도록 설계되어야 해요. limit(있으면)는 분할을 limit 개수 이하의 문자열로 제약해요. 예를 들어 문자열을 단어로 쪼개려면:

$x = "Calvin and Hobbes";
@words = split /\s+/, $x;  # $word[0] = 'Calvin'
                           # $word[1] = 'and'
                           # $word[2] = 'Hobbes'

빈 regexp //를 쓰면 regexp가 항상 매치하고 문자열이 개별 문자로 분할돼요. regexp에 그룹화가 있으면 결과 리스트는 그룹화에서 매치된 부분 문자열도 포함해요. 예를 들어:

$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'

$x의 첫 문자가 regexp와 매치됐으므로 split은 빈 초기 요소를 리스트 앞에 붙였어요.

여기까지 읽었다면, 축하해요! 이제 광범위한 텍스트 처리 문제를 해결하는 데 정규표현식을 쓰는 데 필요한 모든 기본 도구를 가지게 됐어요. 처음으로 튜토리얼을 읽는 것이라면 여기서 멈추고 잠시 regexp를 가지고 놀아보는 게 어떨까요.... Part 2는 정규표현식의 더 심오한 측면에 관한 것이고, 그 개념들은 시작 단계에서 꼭 필요하진 않아요.

Part 2: 파워 도구 (Power tools)

좋아요, regexp의 기초를 알고 더 알고 싶어요. 정규표현식 매칭이 숲 속 산책과 유사하다면, Part 1에서 논의한 도구는 지형도와 나침반, 우리가 항상 쓰는 기본 도구와 유사해요. Part 2의 대부분 도구는 조명탄과 위성 전화와 유사해요. 등산 중 자주 쓰이진 않지만, 막혔을 때 귀중할 수 있어요.

다음은 Perl regexp의 더 고급스럽고, 덜 쓰이며, 때로는 난해한 능력들이에요. Part 2에서는 기초에 익숙하다고 가정하고 고급 기능에 집중할게요.

문자, 문자열, 문자 클래스에 대해 더 (More on characters, strings, and character classes)

아직 다루지 않은 이스케이프 시퀀스와 문자 클래스가 여러 개 있어요.

문자나 문자열을 대문자·소문자 사이에서 변환하는 이스케이프 시퀀스가 여러 개 있고, 패턴 안에서도 사용 가능해요. \l\u는 각각 다음 문자를 소문자·대문자로 변환해요:

$x = "perl";
$string =~ /\u$x/;  # matches 'Perl' in $string
$x = "M(rs?|s)\\."; # note the double backslash
$string =~ /\l$x/;  # matches 'mr.', 'mrs.', and 'ms.',

\L 또는 \U\E로 종료되거나 다른 \U\L이 덮을 때까지 지속되는 대소문자 변환을 나타내요:

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

\E가 없으면 문자열 끝까지 대소문자가 변환돼요. regexp \L\u$word 또는 \u\L$word$word의 첫 문자를 대문자로, 나머지 문자를 소문자로 변환해요. (ASCII 문자 너머에서는 조금 더 복잡해져요. \u는 실제로 titlecase 매핑을 수행하는데, 대부분의 문자에서 대문자와 같지만 전부는 아니에요. https://unicode.org/faq/casemap_charprop.html#4 참조.)

제어 문자는 \c로 이스케이프할 수 있어서, control-Z 문자는 \cZ로 매치돼요. 이스케이프 시퀀스 \Q...\E는 대부분의 비알파벳 문자를 인용하거나 보호해요. 예를 들어:

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

그것은 '$''@'를 보호하지 않아서 변수를 여전히 치환할 수 있어요.

\Q, \L, \l, \U, \u, \E는 사실 이중 인용(double-quotish) 문법의 일부이고, regexp 문법 자체의 일부는 아니에요. 프로그램에 직접 내장된 정규표현식에 나타나면 작동하지만, 패턴에 보간된 문자열에 포함되면 작동하지 않아요.

Perl regexp는 표준 ASCII 문자셋보다 더 많은 것을 다룰 수 있어요. Perl은 거의 모든 세계의 문자 언어의 알파벳과 많은 기호를 표현하는 표준인 유니코드 를 지원해요. Perl의 텍스트 문자열은 유니코드 문자열이므로 255보다 큰 값(코드포인트 또는 문자 번호)을 가진 문자를 포함할 수 있어요.

이게 regexp에 무슨 의미가 있을까요? 음, regexp 사용자는 Perl의 문자열 내부 표현에 대해 많이 알 필요가 없어요. 하지만 1) regexp에서 유니코드 문자를 어떻게 표현하는지, 2) 매칭 연산이 검색할 문자열을 바이트가 아니라 문자 시퀀스로 취급한다는 것을 알아야 해요. 1)에 대한 답은 chr(255)보다 큰 유니코드 문자가 \x{hex} 표기법으로 표현되는데, \xXY (중괄호 없는 두 16진수로 지정)는 255보다 가지 못하기 때문이에요. (Perl 5.14부터 8진수를 좋아한다면 \o{oct}도 쓸 수 있어요.)

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

참고: Perl 5.6.0에서 어떤 유니코드 기능을 쓰려면 use utf8이라고 말해야 했어요. 이건 더 이상 사실이 아니에요. 거의 모든 유니코드 처리에서 명시적 utf8 프래그마는 필요하지 않아요. (중요한 유일한 경우는 Perl 스크립트가 유니코드이고 UTF-8로 인코딩된 경우인데, 그때 명시적 use utf8이 필요해요.)

원하는 유니코드 문자의 16진수 시퀀스를 알아내거나 다른 사람의 16진수 유니코드 regexp를 해독하는 건 기계어로 프로그래밍하는 것만큼 재미없어요. 그래서 유니코드 문자를 지정하는 또 다른 방법은 이름 있는 문자 이스케이프 시퀀스 \N{_name_}을 쓰는 거예요. name 은 유니코드 표준에 지정된 대로의 유니코드 문자 이름이에요. 예를 들어 수성 행성의 점성술 기호를 나타내거나 매치하고 싶다면:

$x = "abc\N{MERCURY}def";
$x =~ /\N{MERCURY}/;   # matches
$x =~ /\N{ MERCURY }/; # Also matches

"짧은" 이름도 쓸 수 있어요:

print "\N{GREEK SMALL LETTER SIGMA} is called sigma.\n";
print "\N{greek:Sigma} is an upper-case sigma.\n";

charnames 프래그마를 지정해 이름을 특정 알파벳으로 제한할 수도 있어요:

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

문자 이름 색인은 유니코드 컨소시엄에서 온라인으로 제공돼요. https://www.unicode.org/charts/charindex.html이고, 다른 자원으로의 링크가 있는 설명 자료는 https://www.unicode.org/standard/where예요.

Perl v5.32부터 전체 이름에 대한 \N{...}의 대안이 있고, 그것은 이렇게 말하는 거예요:

/\p{Name=greek small letter sigma}/

\p{}에서 사용될 때 문자 이름의 대소문자는 무관하고, 대부분의 공백, 밑줄, 하이픈도 무관해요. (소수의 이상한 문자들이 항상 무시하는 데 문제를 일으켜요. 자세한 내용(더 능숙해지고 필요할 때 조회할 수 있는)은 https://www.unicode.org/reports/tr44/tr44-24.html#UAX44-LM2에 있어요.)

요구사항 2)에 대한 답은 regexp가 (대부분) 유니코드 문자를 사용한다는 거예요. 그 "대부분"은 지저분한 하위 호환성 이유 때문이고, Perl 5.14부터 use feature 'unicode_strings' 범위에서 컴파일된 어떤 regexp든(use v5.12 이상 범위에서 자동으로 켜지는) 그 "대부분"을 "항상"으로 바꿀 거예요. 유니코드를 제대로 다루려면 'unicode_strings'가 켜져 있는지 확인해야 해요. 내부적으로 이것은 문자열의 역사에 따라 UTF-8 또는 네이티브 8비트 인코딩으로 바이트에 인코딩되지만, 개념적으로는 바이트가 아니라 문자 시퀀스예요. 이에 대한 튜토리얼은 perlunitut를 참고하세요.

이제 유니코드 문자 클래스를 논의해볼게요. 대부분 "문자 속성(character properties)"이라고 불러요. 이것들은 \p{_name_} 이스케이프 시퀀스로 표현돼요. 부정은 \P{_name_}이에요. 예를 들어 소문자와 대문자 문자를 매치하려면:

$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

("Is"는 선택적이에요.)

유니코드 문자 속성은 아주 많아요. 전체 목록은 perluniprops를 참고하세요. 대부분이 더 짧은 이름의 동의어를 갖고, 그것도 그곳에 나열돼 있어요. 일부 동의어는 단일 문자예요. 그것들은 중괄호를 뗄 수 있어요. 예를 들어 \pM\p{Mark}와 같아서, 악센트 표시 같은 것을 뜻해요.

유니코드 \p{Script}\p{Script_Extensions} 속성은 모든 유니코드 문자가 쓰이는 언어 문자 체계로 분류하는 데 쓰여요. 예를 들어 영어, 프랑스어, 그리고 많은 다른 유럽 언어는 라틴 문자 체계로 쓰여요. 하지만 그리스 문자 체계, 태국 문자 체계, 가타카나 문자 체계 등등 도 있어요. (ScriptScript_Extensions의 더 옛날의, 덜 고급스러운 형태이고, 하위 호환성 이유로만 유지돼요.) 문자가 특정 문자 체계에 있는지 예를 들어 \p{Latin}, \p{Greek}, \p{Katakana}로 테스트할 수 있어요. 발리 문자 체계에 없다는 걸 테스트하려면 \P{Balinese}를 써요. (이들은 모두 내부적으로 Script_Extensions를 사용하는데, 더 나은 결과를 주기 때문이에요.)

지금까지 설명한 것은 \p{...} 문자 클래스의 단일 형태예요. 마주칠 수 있는 복합 형태도 있어요. 이들은 \p{_name_ =_value_} 또는 \p{_name_ :_value_}처럼 보여요 (등호와 콜론은 서로 바꿔 쓸 수 있어요). 이것들은 단일 형태보다 더 일반적이고, 사실 대부분의 단일 형태는 흔한 복합 형태의 Perl 정의 바로가기일 뿐이에요. 예를 들어 이전 문단의 문자 체계 예는 동등하게 \p{Script_Extensions=Latin}, \p{Script_Extensions:Greek}, \p{script_extensions=katakana}, \P{script_extensions=balinese}로 쓸 수 있어요 ({} 중괄호 사이에서 대소문자는 무관해요). 복합 형태를 써야 할 일은 없을지도 모르지만, 때로는 필요하고, 그것의 사용은 코드를 더 이해하기 쉽게 만들 수 있어요.

\X는 유니코드 확장 그래핌 클러스터(extended grapheme cluster) 를 구성하는 문자 클래스의 약자예요. 이것은 "논리 문자"를 나타내요. 단일 문자로 보이지만 내부적으로 둘 이상으로 표현될 수 있는 것이죠. 예로, 유니코드 전체 이름을 쓰면, 예를 들어 "A + COMBINING RING"은 기본 문자 "A"와 결합 문자 "COMBINING RING"을 가진 그래핌 클러스터예요. 덴마크어로는 위에 원이 얹힌 "A"로 번역되는데, Ångstrom 단어의 그것처럼요.

유니코드에 대한 완전하고 최신 정보는 최신 유니코드 표준 또는 유니코드 컨소시엄 웹사이트 https://www.unicode.org를 참고하세요.

그 모든 클래스로도 부족한 듯, Perl은 POSIX 스타일 문자 클래스도 정의해요. 이것들은 [:_name_ :] 형태로, name 은 POSIX 클래스 이름이에요. POSIX 클래스는 alpha, alnum, ascii, blank, cntrl, digit, graph, lower, print, punct, space, upper, xdigit, word(\w를 매치하는 Perl 확장)예요. /a 수정자는 이것들을 ASCII 범위에서만 매치하도록 제한해요. 그렇지 않으면 대응하는 Perl 유니코드 클래스와 같은 것을 매치할 수 있어요. [:upper:]\p{IsUpper}와 같고 등등. (이에 대한 예외와 함정이 몇 가지 있어요. 전체 논의는 perlrecharclass 참조.) [:digit:], [:word:], [:space:]는 익숙한 \d, \w, \s 문자 클래스에 대응해요. POSIX 클래스를 부정하려면 이름 앞에 '^'를 넣어, 예를 들어 [:^digit:]\D에, 유니코드 아래에서 \P{IsDigit}에 대응해요. 유니코드와 POSIX 문자 클래스는 \d처럼 쓸 수 있는데, POSIX 문자 클래스는 문자 클래스 안에서만 쓸 수 있다는 예외가 있어요:

/\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

휴! 그게 나머지 문자와 문자 클래스 전부예요.

정규표현식 컴파일과 저장 (Compiling and saving regular expressions)

Part 1에서 Perl이 regexp를 조밀한 opcode 시퀀스로 컴파일한다고 언급했어요. 그래서 컴파일된 regexp는 한 번 저장해 계속 다시 쓸 수 있는 데이터 구조예요. regexp 따옴표 qr//가 정확히 그걸 해요. qr/string/string을 regexp로 컴파일하고 결과를 변수에 할당할 수 있는 형태로 변환해요:

$reg = qr/foo+bar?/;  # reg contains a compiled regexp

그러면 $reg를 regexp로 쓸 수 있어요:

$x = "fooooba";
$x =~ $reg;     # matches, just like /foo+bar?/
$x =~ /$reg/;   # same thing, alternate form

$reg는 더 큰 regexp에 보간될 수도 있어요:

$x =~ /(abc)?$reg/;  # still matches

매칭 연산자처럼 regexp 따옴표도 다른 구분자를 쓸 수 있어요. 예를 들어 qr!!, qr{} 또는 qr~~. 구분자로 아포스트로피(qr'')는 어떤 보간도 억제해요.

사전 컴파일된 regexp는 매번 마주칠 때마다 재컴파일할 필요가 없는 동적 매치를 만드는 데 유용해요. 사전 컴파일된 regexp를 써서, 패턴 시퀀스를 grep하고 하나가 충족되는 즉시 다음 패턴으로 진행하는 grep_step 프로그램을 써요:

% cat > grep_step
#!/usr/bin/perl
# grep_step - match <number> regexps, one after the other
# usage: multi_grep <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;
    }
}
^D

% grep_step 3 shift print last grep_step
$number = shift;
        print $line;
        last unless @compiled;

사전 컴파일된 regexp를 배열 @compiled에 저장하면 재컴파일 없이 간단히 regexp를 루프할 수 있어, 속도를 희생하지 않고 유연성을 얻어요.

런타임에 정규표현식 구성하기 (Composing regular expressions at runtime)

백트래킹은 서로 다른 정규표현식으로 반복 시도하는 것보다 더 효율적이에요. 여러 정규표현식이 있고 그것들 중 어느 것과 매치해도 받아들일 수 있다면, 그것들을 대체 집합으로 결합하는 게 가능해요. 개별 표현식이 입력 데이터라면, join 연산을 프로그래밍해 이것을 할 수 있어요. simple_grep 프로그램의 개선된 버전, 즉 여러 패턴을 매치하는 프로그램에서 이 아이디어를 활용할 거예요:

% cat > multi_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/;
}
^D

% multi_grep 2 shift for multi_grep
$number = shift;
$regexp[$_] = shift foreach (0..$number-1);

때로는 분석할 입력 으로부터 패턴을 구성하고 매칭 연산의 왼쪽에 허용 값을 쓰는 것이 유리할 수 있어요. 이 다소 역설적인 상황의 예로, 입력이 명령 동사를 포함하고, 사용 가능한 명령 동사 집합 중 하나와 매치해야 하며, 주어진 문자열이 유일하기만 하면 명령이 약어로 줄어들 수 있는 추가 twist가 있다고 가정해볼게요. 아래 프로그램이 기본 알고리즘을 보여줘요.

% cat > keymatch
#!/usr/bin/perl
$kwds = 'copy compare list print';
while( $cmd = <> ){
    $cmd =~ s/^\s+|\s+$//g;  # trim leading and trailing spaces
    if( ( @matches = $kwds =~ /\b$cmd\w*/g ) == 1 ){
        print "command: '@matches'\n";
    } elsif( @matches == 0 ){
        print "no such command: '$cmd'\n";
    } else {
        print "not unique: '$cmd' (could be one of: @matches)\n";
    }
}
^D

% keymatch
li
command: 'list'
co
not unique: 'co' (could be one of: copy compare)
printer
no such command: 'printer'

입력을 키워드에 대해 매치하려고 하는 대신, 결합된 키워드 집합을 입력에 대해 매치해요. 패턴 매칭 연산 $kwds =~ /\b($cmd\w*)/g는 동시에 여러 일을 해요. 주어진 명령이 키워드가 시작하는 곳에서 시작하도록 보장해요 (\b). 추가된 \w* 덕에 약어를 허용해요. 매치 수(scalar @matches)와 실제 매치된 모든 키워드를 말해줘요. 그보다 더 요구하기는 어려워요.

정규표현식에 주석과 수정자 내장하기 (Embedding comments and modifiers in a regular expression)

이 절부터 Perl의 확장 패턴(extended patterns) 집합을 논의할 거예요. 이것들은 전통적인 정규표현식 문법의 확장으로, 패턴 매칭을 위한 강력한 새 도구를 제공해요. 우리는 이미 최소 매칭 구조 ??, *?, +?, {n,m}?, {n,}?, {,n}?의 형태의 확장을 봤어요. 아래 대부분의 확장은 (?char...) 형태를 가지는데, char는 확장 유형을 결정하는 문자예요.

첫 번째 확장은 내장 주석 (?#text)예요. 이것은 의미에 영향 없이 주석을 정규표현식에 내장해요. 주석은 텍스트에 닫는 괄호가 없어야 해요. 예:

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

이 주석 스타일은 대부분 /x 수정자가 허용하는 원시 자유 형식 주석으로 대체되었어요.

/i, /m, /s, /x(또는 이들의 어떤 조합) 같은 대부분의 수정자는 (?i), (?m), (?s), (?x)로 regexp에 내장될 수도 있어요. 예를 들어:

/(?i)yes/;  # match 'yes' case insensitively
/yes/i;     # same thing
/(?x)(          # freeform version of an integer regexp
         [+-]?  # match an optional sign
         \d+    # match a sequence of digits
     )
/x;

내장 수정자는 평소 수정자에 비해 두 가지 중요한 이점을 가질 수 있어요. 내장 수정자는 각각의 regexp 패턴에 대한 사용자 정의 수정자 집합을 허용해요. 이건 서로 다른 수정자를 가져야 하는 regexp 배열을 매치하는 데 훌륭해요:

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

둘째 이점은 내장 수정자(전체 regexp를 수정하는 /p 제외)가 포함된 그룹 안의 regexp에만 영향을 준다는 거예요. 그래서 그룹화를 써서 수정자 효과를 지역화할 수 있어요:

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

내장 수정자는 (?-i) 같은 것을 써서 이미 있는 수정자도 끌 수 있어요. 수정자는 단일 표현식으로 결합될 수도 있어요. 예를 들어 (?s-i)는 단일 줄 모드를 켜고 대소문자 무시를 꺼요.

내장 수정자는 비캡처 그룹화에 추가될 수도 있어요. (?i-m:regexp)regexp를 대소문자 무시로 매치하고 다중 줄 모드를 끄는 비캡처 그룹화예요.

앞보기와 뒤보기 (Looking ahead and looking behind)

이 절은 lookahead와 lookbehind 어서션에 관한 거예요. 먼저 배경을 조금 보겠어요.

Perl 정규표현식에서 대부분 regexp 요소는 매치할 때 일정량의 문자열을 "먹어요". 예를 들어 regexp 요소 [abc]는 매치할 때 문자열의 문자 하나를 먹어요. Perl이 매치 후 문자열의 다음 문자 위치로 이동한다는 의미에서요. 하지만 매치돼도 문자를 먹지 않는(문자 위치를 진행하지 않는) 요소들이 있어요. 지금까지 본 예는 앵커들이에요. 앵커 '^'는 줄 시작을 매치하지만 어떤 문자도 먹지 않아요. 마찬가지로 단어 경계 앵커 \b\w를 매치하는 문자가 그렇지 않은 문자 옆에 있을 때마다 매치하지만, 스스로는 어떤 문자도 먹지 않아요. 앵커는 폭 0 어서션(zero-width assertions) 의 예시예요. 폭 0이기 때문에 문자를 소비하지 않고, 어서션이기 때문에 문자열의 어떤 속성을 테스트하기 때문이에요. regexp 매칭에 대한 숲 산책 비유의 문맥에서, 대부분 regexp 요소는 우리를 산책로를 따라 이동시키지만, 앵커는 잠시 멈춰 주변을 확인하게 해요. 지역 환경이 확인되면 앞으로 진행할 수 있어요. 하지만 지역 환경이 우리를 만족시키지 못하면 백트래킹해야 해요.

환경 확인은 산책로에서 앞보기, 뒤보기, 또는 둘 다를 수반해요. '^'는 뒤를 봐서 앞에 문자가 없는지 확인해요. '$'는 앞을 봐서 뒤에 문자가 없는지 확인해요. \b는 앞과 뒤 둘 다 봐서 양쪽 문자가 "단어성(word-ness)"에서 다른지 확인해요.

Lookahead와 lookbehind 어서션은 앵커 개념의 일반화예요. Lookahead와 lookbehind는 테스트할 문자를 우리가 지정하게 해주는 폭 0 어서션이에요. Lookahead 어서션은 (?=regexp) 또는 (5.32부터, 5.28에서 실험적으로) (*pla:regexp) 또는 (*positive_lookahead:regexp)로 표시되고, lookbehind 어서션은 (?<=fixed-regexp) 또는 (5.32부터, 5.28에서 실험적으로) (*plb:fixed-regexp) 또는 (*positive_lookbehind:fixed-regexp)로 표시돼요. 몇 가지 예:

$x = "I catch the housecat 'Tom-cat' with catnip";
$x =~ /cat(*pla:\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

이것들의 괄호는 폭 0 어서션이므로 비캡처라는 점을 주목하세요. 그래서 둘째 regexp에서 캡처된 부분 문자열은 전체 regexp 자체의 것들이에요. Lookahead는 임의의 regexp를 매치할 수 있지만, 5.30 이전의 lookbehind (?<=fixed-regexp)는 고정 폭 regexp, 고정된 수의 문자 길이의 것에서만 작동해요. 그래서 (?<=(ab|bc))는 괜찮지만, 5.30 이전의 (?<=(ab)*)는 아니에요.

Lookahead와 lookbehind 어서션의 부정 버전은 각각 (?!regexp)(?<!fixed-regexp)로 표시돼요. 또는 5.32(5.28에서 실험적으로)부터 (*nla:regexp), (*negative_lookahead:regexp), (*nlb:regexp), (*negative_lookbehind:regexp)로 표시돼요. regexp가 매치되지 않으면 참으로 평가돼요:

$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+/만으론 작동하지 않아요. 대시들 사이, 또는 단어·대시와 사이에 공백이 필요하지 않기 때문이에요. 분할을 위한 추가 위치는 앞과 뒤를 봄으로써 세워져요:

$str = "one two - --6-8";
@toks = split / \s+              # a run of spaces
              | (?<=\S) (?=-)    # any non-space followed by '-'
              | (?<=-)  (?=\S)   # a '-' followed by any non-space
              /x, $str;          # @toks = qw(one two - - - 6 - 8)

독립 부분 표현식으로 백트래킹 막기 (Using independent subexpressions to prevent backtracking)

독립 부분 표현식(또는 원자 부분 표현식)은 더 큰 정규표현식의 문맥에서, 더 큰 정규표현식과 독립적으로 기능하는 정규표현식이에요. 즉 규칙적으로 더 큰 regexp가 매치할 수 있는지와 무관하게 원하는 만큼 문자열을 소비해요. 독립 부분 표현식은 (?>regexp) 또는 (5.32부터, 5.28에서 실험적으로) (*atomic:regexp)로 표시돼요. 먼저 평범한 regexp를 고려해 그것들의 동작을 설명할 수 있어요:

$x = "ab";
$x =~ /a*ab/;  # matches

이건 분명히 매치돼요. 하지만 매치 과정에서 부분 표현식 a*가 먼저 'a'를 움켜쥐었어요. 하지만 그렇게 하면 전체 regexp가 매치되지 않으므로, 백트래킹 후 a*는 결국 'a'를 돌려주고 빈 문자열을 매치해요. 여기서 a*가 매치한 것은 regexp 나머지가 매치한 것에 의존 했어요.

그걸 독립 부분 표현식과 대조해볼게요:

$x =~ /(?>a*)ab/;  # doesn't match!

독립 부분 표현식 (?>a*)는 regexp 나머지를 신경 쓰지 않으므로 'a'를 보고 움켜쥐어요. 그러면 regexp 나머지 ab가 매치할 수 없어요. (?>a*)가 독립적이므로 백트래킹이 없고, 독립 부분 표현식은 'a'를 내주지 않아요. 그래서 전체로서 regexp의 매치가 실패해요. 완전히 독립적인 regexp에서도 비슷한 동작이 발생해요:

$x = "ab";
$x =~ /a*/g;   # matches, eats an 'a'
$x =~ /\Gab/g; # doesn't match, no 'a' available

여기서 /g\G가 한 regexp에서 다른 regexp로 문자열의 "태그 팀" 인계를 만들어요. 독립 부분 표현식을 가진 regexp는 이것과 매우 유사해요. 문자열을 독립 부분 표현식에 인계하고, 문자열을 감싸는 regexp에 다시 인계하는 방식이죠.

독립 부분 표현식이 백트래킹을 막는 능력은 꽤 유용할 수 있어요. 최대 두 수준 깊이까지 괄호로 감싸인 비어 있지 않은 문자열을 매치하고 싶다고 가정해볼게요. 그러면 다음 regexp가 매치해요:

$x = "abc(de(fg)h";  # unbalanced parentheses
$x =~ /\( ( [ ^ () ]+ | \( [ ^ () ]* \) )+ \)/xx;

regexp는 여는 괄호, 하나 이상의 대체 사본, 닫는 괄호를 매치해요. 대체는 양방향이고, 첫 대체 [^()]+가 괄호 없는 부분 문자열을 매치하고 둘째 대체 \([^()]*\)가 괄호로 구분된 부분 문자열을 매치해요. 이 regexp의 문제는 병적이라는 거예요. (a+|b)+ 형태의 중첩 부정 수량자를 가져요. Part 1에서 중첩 수량자가 이 같은 것, 매치가 불가능하면 실행에 기하급수적으로 오랜 시간이 걸릴 수 있다고 논의했어요. 기하급수적 폭발을 막으려면 어떤 지점에서 쓸모없는 백트래킹을 막을 필요가 있어요. 이건 내부 수량자를 독립 부분 표현식으로 감싸서 할 수 있어요:

$x =~ /\( ( (?> [ ^ () ]+ ) | \([ ^ () ]* \) )+ \)/xx;

여기서 (?>[^()]+)는 문자열을 가능한 한 많이 삼키고 유지함으로써 문자열 분할의 퇴화를 깨뜨려요. 그러면 매치 실패가 훨씬 더 빨리 실패해요.

조건부 표현식 (Conditional expressions)

조건부 표현식 은 어떤 조건에 기반해 어떤 패턴이 매치될지 선택하게 해주는 if-then-else 문장의 한 형태예요. 두 유형의 조건부 표현식이 있어요: (?(_condition_)_yes-regexp_)(?(condition)_yes-regexp_ |_no-regexp_). (?(_condition_)_yes-regexp_)는 Perl의 'if () {}' 문장과 같아요. condition 이 참이면 yes-regexp 가 매치돼요. condition 이 거짓이면 yes-regexp 가 건너뛰어지고 Perl이 다음 regexp 요소로 이동해요. 둘째 형태는 Perl의 'if () {} else {}' 문장과 같아요. condition 이 참이면 yes-regexp 가, 그렇지 않으면 no-regexp 가 매치돼요.

condition 은 여러 형태를 가질 수 있어요. 첫 형태는 단순히 괄호 안의 정수 (_integer_)예요. 대응하는 역참조 \_integer_가 regexp에서 더 일찍 매치됐으면 참이에요. 같은 일을 캡처 그룹과 연관된 이름으로 할 수 있고, (<_name_ >) 또는 ('_name_ ')로 써요. 둘째 형태는 맨 폭 0 어서션 (?...)이고, lookahead, lookbehind, 또는 코드 어서션(다음 절에서 논의)이에요. 셋째 형태 집합은 표현식이 재귀 내에서 실행되면((R)) 또는 번호((R1), (R2),...)나 이름((R&_name_))으로 참조된 어떤 캡처 그룹에서 호출되면 참을 돌려주는 테스트를 제공해요.

condition 의 정수 또는 이름 형태는 regexp에서 더 일찍 매치된 것에 기반해 무엇을 매치할지 더 유연하게 선택하게 해줘요. 이건 "$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 condition 은 역참조와 함께, 매치의 더 이른 부분이 더 늦은 부분에 영향을 주게 해줘요. 예를 들어:

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

이것은 AAG로 끝나거나, 다른 염기쌍 조합과 'C'로 끝나는 DNA 시퀀스를 매치해요. 형태가 (?(?<=AA)G|C)이고 (?((?<=AA))G|C)가 아님을 주목하세요. lookahead, lookbehind, 코드 어서션에서 조건부 주변의 괄호는 필요하지 않아요.

이름 있는 패턴 정의하기 (Defining named patterns)

어떤 정규표현식은 여러 곳에서 동일한 하위 패턴을 사용해요. Perl 5.10부터 패턴의 한 섹션에서 이름 있는 하위 패턴을 정의해, 패턴 어디에서든 이름으로 호출할 수 있게 됐어요. 이 정의 그룹의 문법 패턴은 (?(DEFINE)(?<_name_ >_pattern_)...)이에요. 이름 있는 패턴의 삽입은 (?&_name_)로 써요.

아래 예시는 앞서 소개한 부동소수점 숫자용 패턴을 써서 이 기능을 보여줘요. 한 번 이상 쓰이는 세 하위 패턴은 선택적 부호, 정수의 자릿수 시퀀스, 소수 분수예요. 패턴 끝의 DEFINE 그룹이 그것들의 정의를 담고 있어요. 소수 분수 패턴이 정수 패턴을 재사용할 수 있는 첫 장소임을 주목하세요.

/^ (?&osg)\ * ( (?&int)(?&dec)? | (?&dec) )
   (?: [eE](?&osg)(?&int) )?
 $
 (?(DEFINE)
   (?<osg>[-+]?)         # optional sign
   (?<int>\d++)          # integer
   (?<dec>\.(?&int))     # decimal fraction
 )/x

재귀적 패턴 (Recursive patterns)

이 기능(Perl 5.10에서 도입)은 Perl의 패턴 매칭 능력을 크게 확장해요. 패턴 어디에서든 (?_group-ref_) 구조로 다른 캡처 그룹을 참조함으로써, 참조된 그룹 안의 패턴 이 그룹 참조 자체를 대신해 독립 하위 패턴으로 쓰여요. 그룹 참조가 그것이 참조하는 그룹 안에 포함될 수 있으므로, 지금까지 재귀 파서를 요구했던 작업에 패턴 매칭을 적용하는 게 이제 가능해요.

이 기능을 설명하기 위해, 문자열이 회문(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) 또는 (?0)으로 재삽입될 수 있어요. 그룹에 이름을 붙이는 걸 선호한다면 (?&_name_)으로 그 그룹으로 재귀할 수 있어요.

약간의 마법: 정규표현식에서 Perl 코드 실행하기 (A bit of magic: executing Perl code in a regular expression)

보통 regexp는 Perl 표현식의 일부예요. 코드 평가(code evaluation) 표현식은 임의의 Perl 코드가 regexp의 일부가 되게 함으로써 그걸 뒤집어요. 코드 평가 표현식은 (?{_code_})로 표시되고, code 는 Perl 문장 문자열이에요.

코드 표현식은 폭 0 어서션이고, 돌려주는 값은 환경에 의존해요. 두 가지 가능성이 있어요. 코드 표현식이 조건부 표현식 (?(_condition_)...)에서 조건부로 쓰이거나, 그렇지 않거나. 코드 표현식이 조건부라면 코드가 평가되고 결과( 마지막 문장의 결과)가 참 또는 거짓을 결정하는 데 쓰여요. 코드 표현식이 조건부로 쓰이지 않으면 어서션은 항상 참으로 평가되고 결과가 특수 변수 $^R에 들어가요. 변수 $^R은 regexp의 뒤에서 코드 표현식에 쓸 수 있어요. 몇 가지 바보 같은 예:

$x = "abcdef";
$x =~ /abc(?{print "Hi Mom!";})def/; # matches,
                                     # prints 'Hi Mom!'
$x =~ /aaa(?{print "Hi Mom!";})def/; # doesn't match,
                                     # no 'Hi Mom!'

다음 예에 주의 깊게 주의하세요:

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

언뜻 보면 출력이 안 돼야 한다고 생각할 거예요. 분명히 ddd가 대상 문자열과 매치되지 않을 테니까요. 하지만 이 예를 보세요:

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

흠. 여기서 무슨 일이 일어났을까요? 따라오고 있다면 위 패턴이 실질적으로 (거의) 마지막 것과 같아야 한다는 걸 알 거예요. 'd'를 문자 클래스로 감싼다고 매치되는 것이 바뀌진 않으니까요. 그럼 왜 첫 번째는 출력하지 않고 둘째는 할까요?

답은 regexp 엔진이 하는 최적화에 있어요. 첫 번째 경우 엔진이 보는 전부는(그 ?{} 구조를 제외하고) 평범한 옛 문자들이에요. 그것은 패턴을 실제로 실행하기 전에 문자열 'ddd'가 대상 문자열에 발생하지 않는다는 걸 알아낼 만큼 똑똑해요. 하지만 둘째 경우 우리는 패턴이 더 복잡하다고 생각하도록 속였어요. 엔진은 한 번 보고 문자 클래스를 보면서, 매치 여부를 결정하려면 실제로 패턴을 실행해야 한다고 결정하고, 실행 과정에서 우리가 매치가 없음을 발견하기 전에 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'

절 제목에서 언급한 약간의 마법은 regexp가 매치를 찾는 과정에서 백트래킹할 때 일어나요. regexp가 코드 표현식을 백트래킹하고, 그 안에서 쓰인 변수들이 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'

(?{local $c = $c + 1;}) (?{$c = $c + 1;})로 바꾸면 변수 변화가 백트래킹 동안 실행 취소되지 않고, 다음을 얻어요:

'a' count is 4, $c variable is 'bob'

오직 지역화된 변수 변화만 실행 취소된다는 점을 주목하세요. 코드 표현식 실행의 다른 부작용은 영구적이에요. 그래서:

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

생성:

Yow
Yow
Yow
Yow

결과 $^R은 자동으로 지역화되어, 백트래킹이 있을 때 적절히 동작할 거예요.

이 예시는 조건부에서 코드 표현식을 써서 정관사, 영어의 'the' 또는 독일어의 'der|die|das'를 매치해요:

$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;

여기서 문법은 (?(?{...})_yes-regexp_ |_no-regexp_)이고 (?((?{...}))_yes-regexp_ |_no-regexp_)가 아님을 주목하세요. 즉 코드 표현식의 경우 조건부 주변에 추가 괄호가 필요 없어요.

코드 텍스트가 패턴에 문자 그대로 나타나는 게 아니라 보간된 변수에 포함된 코드 표현식을 쓰려 하면, Perl이 당신을 놀라게 할 수 있어요:

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

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

regexp가 코드 표현식을 보간하는 변수를 가지면 Perl은 regexp를 오류로 취급해요. 하지만 코드 표현식이 변수로 사전 컴파일되면 보간은 괜찮아요. 문제는 왜 이게 오류냐는 거예요.

이유는 변수 보간과 코드 표현식이 함께 보안 위험을 제기하기 때문이에요. 이 조합은 위험한데, 검색 엔진을 쓰는 많은 프로그래머가 종종 사용자 입력을 받아 직접 regexp에 꽂기 때문이에요:

$regexp = <>;       # read user-supplied regexp
$chomp $regexp;     # get rid of possible newline
$text =~ /$regexp/; # search $text for the $regexp

$regexp 변수가 코드 표현식을 포함하면, 사용자가 임의의 Perl 코드를 실행할 수 있어요. 예를 들어 어떤 조커가 system('rm -rf *');를 검색해 파일들을 지울 수 있어요. 이 의미에서 보간과 코드 표현식의 조합은 당신의 regexp를 오염(taint) 시켜요. 그래서 기본적으로 같은 regexp에서 보간과 코드 표현식 모두를 쓰는 건 허용되지 않아요. 악의적인 사용자가 걱정되지 않는다면 use re 'eval'을 호출해 이 보안 검사를 우회할 수 있어요:

use re 'eval';       # throw caution out the door
$bar = 5;
$pat = '(?{ 1 })';
/foo${pat}bar/;      # compiles ok

코드 표현식의 또 다른 형태는 패턴 코드 표현식(pattern code expression) 이에요. 패턴 코드 표현식은 일반 코드 표현식과 같지만, 코드 평가의 결과가 정규표현식으로 취급되어 즉시 매치된다는 점이 달라요. 간단한 예:

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

이 마지막 예시는 일반과 패턴 코드 표현식 둘 다 포함해요. 이진 문자열 1101010010001...'1'들의 피보나치 간격 0,1,1,2,3,5,...을 갖는지 감지해요:

    $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);

$^N은 마지막 완료된 캡처 그룹이 매치한 것과 같게 설정됨을 기억하세요. 출력:

It is a Fibonacci sequence
Largest sequence matched was 5

하! 평범한 regexp 패키지로 그걸 해보세요...

변수 $z0$z1은 regexp가 컴파일될 때 치환되지 않는다는 점을 주목하세요. 코드 표현식 밖의 평범한 변수에서 일어나는 것처럼요. 대신 perl이 문자 그대로의 regexp 패턴을 포함하는 코드를 컴파일할 때와 동시에 전체 코드 블록이 perl 코드로 파싱돼요.

/x 수정자 없는 이 regexp는:

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

코드 부분에서도 공백이 여전히 가능함을 보여줘요. 그럼에도 코드와 조건부 표현식으로 작업할 때, 확장 형태의 regexp는 regexp를 만들고 디버깅하는 데 거의 필수적이에요.

백트래킹 제어 동사 (Backtracking control verbs)

Perl 5.10은 regexp 엔진을 직접 영향하고 모니터링 기법을 제공함으로써 백트래킹 과정에 대한 상세한 제어를 제공하려는 여러 제어 동사를 도입했어요. 자세한 설명은 perlre의 "Special Backtracking Control Verbs"를 참고하세요.

아래는 (*FAIL) 제어 동사를 보여주는 예시 하나뿐이에요. (*F)로 약어될 수 있어요. regexp에 삽입되면 패턴과 문자열 사이의 어떤 불일치에서 그렇듯 실패를 일으켜요. "정상적인" 실패 후 그렇듯 regexp 처리가 계속되므로, 예를 들어 문자열의 다음 위치나 다른 대체가 시도돼요. 매치 실패가 캡처 그룹을 보존하거나 결과를 생산하지 않으므로, 내장 코드와 결합해 쓰는 게 필요할 수 있어요.

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

패턴은 글자 부분 집합을 매치하는 클래스로 시작해요. 이게 매치될 때마다 $count{'a'}++; 같은 문장이 실행되어 글자의 카운터를 증가시켜요. 그런 다음 (*FAIL)이 말 그대로 하고, regexp 엔진은 교과서대로 진행해요. 문자열 끝에 도달하지 않는 한, 다른 모음을 찾기 전에 위치가 전진돼요. 그래서 매치든 아니든 차이가 없고, regexp 엔진은 전체 문자열이 검사될 때까지 진행해요.

(실제 프로그램에서는 대신 다음을 써서 해결할 수 있어요:

my $string = "supercalifragilisticexpialidocious";
my %count;
$count{lc($1)}++ while $string =~ /([aeiou])/ig;
printf "%3d '%s'\n", $count{$_}, $_ for (sort keys %count);

이것은 regexp의 내장 코드 블록보다 빨라요.)

프래그마와 디버깅 (Pragmas and debugging)

디버깅에 관해 말하자면, Perl에서 regexp를 통제하고 디버깅하는 여러 프래그마가 있어요. 우리는 이미 이전 절에서 하나의 프래그마, use re 'eval';을 마주쳤어요. 이건 변수 보간과 코드 표현식이 regexp에 공존하게 해줘요. 다른 프래그마들은:

use re 'taint';
$tainted = <>;
@parts = ($tainted =~ /(\w+)\s+(\w+)/; # @parts is now tainted

taint 프래그마는 오염된 변수와의 매치의 어떤 부분 문자열도 오염되게 해요. 당신 perl이 tainting을 지원한다면요 (perlsec 참조). 보통은 그렇지 않은데, regexp가 종종 오염된 변수에서 안전한 조각을 추출하는 데 쓰이기 때문이에요. 안전한 조각을 추출할 때가 아니라 다른 어떤 처리를 할 때 taint을 써요. tainteval 프래그마 둘 다 어휘적으로 범위가 정해져, 프래그마를 감싸는 블록 끝까지만 효과가 있다는 뜻이에요.

use re '/m';  # or any other flags
$multiline_string =~ /^foo/; # /m is implied

re '/flags' 프래그마(Perl 5.14에서 도입)는 주어진 정규표현식 플래그를 어휘 범위 끝까지 켜요. 자세한 내용은 re의 "'/flags' mode"를 참고하세요.

use re 'debug';
/^(.*)$/s;       # output debugging info

use re 'debugcolor';
/^(.*)$/s;       # output debugging info in living color

전역 debugdebugcolor 프래그마는 regexp 컴파일과 실행에 대한 상세한 디버깅 정보를 얻게 해줘요. debugcolor는 termcap 색 시퀀스를 표시할 수 있는 터미널에서 디버깅 정보가 색으로 표시된다는 점 빼고 debug와 같아요. 예제 출력:

% perl -e 'use re "debug"; "abc" =~ /a*b+c/;'
Compiling REx 'a*b+c'
size 9 first at 1
   1: STAR(4)
   2:   EXACT <a>(0)
   4: PLUS(7)
   5:   EXACT <b>(0)
   7: EXACT <c>(9)
   9: END(0)
floating 'bc' at 0..2147483647 (checking floating) minlen 2
Guessing start of match, REx 'a*b+c' against 'abc'...
Found floating substr 'bc' at offset 1...
Guessed: match at offset 0
Matching REx 'a*b+c' against 'abc'
  Setting an EVAL scope, savestack=3
   0 <> <abc>           |  1:  STAR
                         EXACT <a> can match 1 times out of 32767...
  Setting an EVAL scope, savestack=3
   1 <a> <bc>           |  4:    PLUS
                         EXACT <b> can match 1 times out of 32767...
  Setting an EVAL scope, savestack=3
   2 <ab> <c>           |  7:      EXACT <c>
   3 <abc> <>           |  9:      END
Match successful!
Freeing REx: 'a*b+c'

튜토리얼을 여기까지 왔다면, 디버깅 출력의 다른 부분이 무엇을 말하는지 짐작할 수 있을 거예요. 첫 부분:

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

컴파일 단계를 설명해요. STAR(4)는 별표가 붙은 객체, 이 경우 'a'가 있고, 매치되면 4번 줄, PLUS(7)로 가라는 뜻이에요. 중간 줄들은 매치 전에 수행된 몇 가지 휴리스틱과 최적화를 설명해요:

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

그런 다음 매치가 실행되고 나머지 줄들이 과정을 설명해요:

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

각 단계는 n <x> <y> 형태이고, <x>는 매치된 문자열 부분, <y>는 아직 매치되지 않은 부분이에요. | 1: STAR은 Perl이 위 컴파일 목록의 1번 줄에 있다는 뜻이에요. 훨씬 더 자세한 내용은 perldebguts의 "Debugging Regular Expressions"를 참고하세요.

regexp 디버깅의 대안 방법은 regexp 안에 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

함께 보기 (SEE ALSO)

이건 그저 튜토리얼이에요. Perl 정규표현식에 대한 전체 이야기는 perlre 정규표현식 레퍼런스 페이지를 참고하세요.

매칭 m//와 치환 s/// 연산자에 대한 더 많은 정보는 perlop의 "Regexp Quote-Like Operators"를 참고하세요. split 연산에 대한 정보는 perlfunc의 "split"을 참고하세요.

정규표현식의 보살핌과 먹이기에 대한 훌륭한 종합 자원으로는 Jeffrey Friedl의 책 Mastering Regular Expressions(O'Reilly 출판, ISBN 1556592-257-3)을 참고하세요.

Copyright (c) 2000 Mark Kvale. All rights reserved. Now maintained by Perl porters.

이 문서는 Perl 자체와 같은 조건으로 배포될 수 있어요.

감사의 글 (Acknowledgments)

종결 코돈 DNA 예의 영감은 _Mastering Regular Expressions_의 7장 ZIP code 예에서 왔어요.

저자는 Jeff Pinyan, Andrew Johnson, Peter Haworth, Ronald J Kimball, Joe Smith의 모든 도움되는 코멘트에 감사하고 싶어요.

Perldoc Browser는 Dan Book(DBOOK)이 유지보수해요. 사이트 자체, 검색, 문서 렌더링 관련 문제는 GitHub issue tracker로 연락하세요.

Perl 문서는 Perl 5 Porters가 Perl 개발 과정에서 유지보수해요. 문서 내용이나 형식 관련 문제는 Perl issue tracker, 메일링 리스트, 또는 IRC로 연락하세요.