정규 표현식은 텍스트에서 검색할 문자 패턴을 기술하는 문자 그룹입니다. 대부분의 운영 체제에서 파일이름에 사용되는 만능 문자의 개념과 아주 비슷합니다. 별표(*)는 파일 이름에서 아무거나 일련의 문자를 대표하는데 사용될 수 있습니다. 그래서 *.py는 파일 끝이 .py로 끝난다는 뜻입니다. 사실 파일이름 만능-문자는 정규 표현식의 아주 작은 부분집합입니다.
정규 표현식은 대단히 강력한 도구입니다. 현대의 프로그래밍 언어는 대부분 정규 표현식을 내장 지원하거나 라이브러리를 가지고 있어서 정규 표현식에 기반하여 텍스트를 검색하고 교체할 수 있습니다. 정규 표현식을 모두 설명하는 것은 이 자습서의 범위를 벗어납니다. 실제로 적어도 정규 표현식 하나에 온전히 바친 책이 있습니다. 관심이 있다면 오라일리 사의 책을 한 번 보시기를 권장합니다.
정규 표현식의 흥미로운 특징은 프로그램의 구조와 상당히 유사하다는 것입니다. 정규 표현식은 작은 단위로 구성된 패턴입니다. 이 패턴은 다음과 같습니다:
· 한-개짜리 문자
· 와일드카드 문자
· 문자 범위 또는 집합
· 괄호로 둘러싼 그룹.
그룹은 한 단위임을 주목하세요. 그래서 그룹의 그룹 등등을 얼마든지 복잡한 수준까지 가질 수 있습니다. 연속열이나 반복 또는 조건 연산자를 사용하는 프로그래밍 언어가 연상될 정도로 이런 단위들을 조합할 수 있습니다. 이런 특징을 하나씩 차례대로 살펴보겠습니다. 예제를 시험해 보려면 re 모듈을 반입해 그의 메쏘드를 사용할 필요가 있습니다. 편의상 예제에서는 이미 re를 반입해 두었다고 간주하겠습니다.
연속열
언제나 그렇듯이 가장 단순한 구조는 연속열이며 가장 단순한 정규 표현식 역시 일련의 문자들입니다:
red
다음은 한 문자열에서 'r'과 'e' 그리고 'd' 세 개의 기호에 순서대로 부합합니다. 그리하여 red와 lettered 그리고 credible 같은 단어가 모두 검색됩니다. 모두 안에 'red'가 들어 있기 때문입니다. 부합의 결과를 더 강력하게 제어하기 위해 (메타문자(metacharacters)라고 부르는) 특수 문자를 공급해서 검색의 범위를 제한할 수 있습니다:
연속열에 사용되는 메타문자 | ||
표현식 |
의미 |
예제 |
^red |
오직 줄의 처음에만 부합한다 |
red ribbons are good |
red$ |
오직 줄의 끝에만 부합한다 |
I love red |
\Wred |
오직 단어의 처음에만 부합한다 |
it's redirected by post |
red\W |
오직 단어의 끝에만 부합한다 |
you covered it already |
위의 메타문자는 앵커(anchors)라고 부릅니다. 왜냐하면 문장이나 단어 안에서 정규 표현식의 위치를 고정하기 때문입니다. 다른 앵커도 re 모듈 문서에 정의되어 있지만 이 장에서는 다루지 않습니다.
연속열은 어떤 문자도 대체할 수 있는 와일드카드 문자도 담을 수 있습니다. 와일드카드 문자는 점입니다. 다음과 같이 해보세요:
>>> import re
>>> re.match('be.t', 'best')
<re.MatchObject instance at 864460>
>>> re.match('be.t', 'bess')
메시지를 보면 첫 인자로 건넨 정규 표현식 be.t가 두 번째 인자로 건넨 best에 부합한다는 것을 볼 수 있습니다. be.t는 'beat'와 'bent' 그리고 'belt' 등등에도 부합합니다. 두 번째 예제는 'bess'가 t로 끝나지 않기 때문에 부합하지 않습니다. 그래서 MatchObject가 생성되지 않았습니다. 몇 가지 부합 예를 더 시도해서 어떻게 작동하는지 알아보세요. (match()는 오직 문자열의 처음에만 부합한다는 것을 주의하세요. 중간에는 부합하지 않습니다. 나중에 보시겠지만 그럴 경우 search()를 사용하면 됩니다!)
다음 단위는 범위(range) 또는 집합(set)입니다. 이 단위는 각괄호에 둘러싼 문자 집단으로 구성됩니다. 정규 표현식은 무엇이든 그 안에 든 문자 하나를 검색합니다.
>>> re.match('s[pwl]am', 'spam')
<re.MatchObject instance at 7cab40>
이는 'swam'이나 'slam'에는 부합하지만 'sham'에는 부합하지 않습니다. 왜냐하면 'h'는 정규 표현식 집합에 포함되어 있지 않기 때문입니다.
그룹의 첫 원소로 ^ 사인을 배치하면 나열된 문자들을 제외하고 어떤 문자도 찾으라고 명령할 수 있습니다. 그리하여 다음 예제에서는:
>>> re.match('[^f]ool', 'cool')
<re.MatchObject instance at 864890>
>>> re.match('[^f]ool','fool')
'cool'과 'pool'에는 부합시킬 수 있지만 'fool'은 부합시키지 못합니다. 왜냐하면 패턴의 처음에서 'f'는 제외하고 찾기 때문입니다.
마지막으로 문자열이나 다른 단위를 괄호 안에 싸 넣어서 무리지을 수 있습니다. 그룹화는 홀로 사용되면 별로 유용하지 않지만 다음으로 살펴볼 반복 그리고 조건적 특징과 조합되면 유용합니다.
반복
좀 더 특수한 문자를 사용하면, 반복되는 문자열에 부합하는 정규 표현식을 만들 수 있습니다. 다음의 메타 문자를 사용하면 문자의 반복 또는 문자 그룹의 반복을 찾을 수 있습니다:
반복에 사용되는 메타문자 | ||
표현식 |
의미 |
예제 |
'?' |
앞 문자가 0개 또는 1개. 0 부분에 주의. 조심하지 않으면 그 때문에 낭패를 볼 수 있다. |
pythonl?y는 다음에 부합한다: |
'*' |
앞 문자가 0개 이상. |
pythonl*y는 위의 단어에 부합할 뿐만 아니라, 다음에도 부합한다: |
'+' |
앞 문자가 1개 이상. |
pythonl+y는 다음에 부합한다: |
{n,m} |
앞 문자가 n개에서 m개까지 반복. |
fo{1,2}는 다음에 부합한다: |
이 반복 문자는 모두 문자 그룹에도 적용할 수 있습니다. 그리하여:
>>> re.match('(.an){1,2}s', 'cans')
<re.MatchObject instance at 862760>
같은 패턴이 'cancans'이나 'pans' 또는 'canpans'에도 부합하지만 'bananas'에는 부합하지 않습니다. 왜냐하면 두 번째 'an' 그룹 앞에 문자가 하나도 없기 때문입니다.
{m,n} 형태의 반복에는 한 가지 약점이 있습니다. 그 약점은 부합을 오직 n 단위만큼으로 제한하지 못한다는 것입니다. 그리하여 위의 표에 있는 예제인 fo{1,2}는 성공적으로 fooo에 일치합니다. 왜냐하면 fooo의 처음에서 foo가 부합하기 때문입니다. 그리하여 얼마나 많은 문자를 부합시킬지 제한하고 싶으면 그 곱표현식 뒤에 앵커나 부인된 범위를 덧붙일 필요가 있습니다. 이 경우 fo{1,2}[^o]는 fooo에 부합하지 않는데 'o'가 1개나 2개 나온 다음에 'o'말고 아무거나 부합하기 때문입니다.
탐욕적 표현식
정규 표현식은 탐욕적입니다. 다시 말해 부합과 검색은 처음의 완벽한 부합만으로 멈추지 않고 문자열에 되도록이면 많이 부합합니다. 보통 이 때문에 별로 문제가 되지는 않지만 와일드카드를 반복 연산자와 결합하면 예상보다 많은 것을 잡게되어 버릴 수 있습니다.
다음 예제를 생각해 보세요. a를 찾고 다음에 얼마든지 문자가 따르다가 b를 찾고 싶다고 명령하는 a.*b와 같은 정규 표현식이 있다면, 일치 함수는 첫 a에서부터 마지막 b까지 찾습니다. 다시 말해 검색 문자열에 'b'가 여러 개 있다면, 맨 마지막 'b'만 제외하고 모든 'b'가 정규 표현식의 .* 부분에 포함될 것입니다. 그리하여 다음 예제에서:
re.match('a.*b','abracadabra')
MatchObject 객체는 abracadab 모두에 부합합니다. 그저 맨 앞 ab에만 부합하는 것이 아닙니다. 이런 탐욕적 부합 행위는 정규 표현식을 처음 사용하는 사람이 제일 흔히 저지르는 실수입니다.
이런 탐욕적 행위를 막으려면 그냥 반복 문자 뒤에 '?'를 추가하면 됩니다. 다음과 같이 말입니다:
re.match('a.*?b','abracadabra')
이제 오직 'ab'에만 일치할 것입니다.
조건
마지막 퍼즐 조각은 정규 표현식에게 선택적인 원소만 찾도록 만든 것입니다. 즉 여러 패턴중 하나를 선택하도록 만들 수 있습니다. 이런 옵션을 따로따로 살펴보겠습니다:
선택적 원소
0개 이상을 나타내는 메타 문자를 사용하여 문자하나가 선택적인지 지정할 수 있습니다:
>>> re.match('computer?d?', 'computer')
<re.MatchObject instance at 864890>
computer나 computed에 부합하겠지만, computerd에도 부합할 것입니다. 이는 원하는 것이 아닙니다.
정규 표현식 안에서 범위를 사용하면 좀 더 구체적으로 지정할 수 있습니다. 그리하여:
>>> re.match('compute[rd]','computer')
<re.MatchObject instance at 874390>
오직 computer와 computed만 선택하고 원하지 않는 computerd는 버립니다.
선택적 표현식
문자 리스트에서 선택해 부합시키는 외에도 부분-표현식을 선택하여 부합시킬 수도 있습니다. 문자 그룹을 괄호에 무리지을 수 있다고 말씀드린 바 있습니다. 그러나 사실은 얼마든지 정규 표현식을 괄호에 무리지어서 그것을 한 단위로 취급할 수 있습니다. 그 구문을 설명하면서 (RE)라는 표기법을 사용하여 정규 표현식 그룹임을 나타내겠습니다.
여기에서 (RE)xxxx 또는 (RE)yyyy가 담긴 정규 표현식을 부합시키고 싶은 경우를 조사해 보고 싶습니다. xxxx와 yyyy는 서로 다른 패턴입니다. 그리하여, premature와 preventative에 모두 부합시키고 싶다면, 선택 메타문자를 사용하면 됩니다:
>>> regexp = 'pre(mature|ventative)'
>>> re.match(regexp,'premature')
<re.MatchObject instance at 864890>
>>> re.match(regexp,'preventative')
<re.MatchObject instance at 864890>
>>> re.match(regexp,'prelude')
정규 표현식을 정의할 때 두 옵션을 괄호 안에 포함시켜야 함을 주의하세요. 그렇지 않으면 그 옵션은 prematureentative나 prematurventative가 되어 버립니다. 다시 말해 e 문자와 v 문자만 옵션이 됩니다. 그룹이 형성되지 않습니다.
파이썬에서 정규 표현식 사용하기
정규 표현식의 모습이 어떤지 조금 살펴 보았습니다. 그러나 그것으로 무엇을 할수 있는가? 그리고 파이썬에서는 어떻게 사용하는가? 먼저 첫 질문에 답한다면, 정규 표현식을 아주 강력한 텍스트 검색 도구로 사용할 수 있습니다. 단 한 번의 연산으로 다양한 텍스트 문자열을 찾을 수 있으며, 심지어 메타문자를 사용하면 빈 줄 같은 인쇄-불가 문자도 검색할 수 있습니다. 또한 re 모듈의 함수와 메쏘드를 사용하면 이런 패턴을 교체할 수 있습니다. 이미 match() 함수가 작동하는 모습을 보았습니다. 그 중에 몇 가지를 아래에 기술합니다:
re 모듈의 함수와 메쏘드 | |
함수/메쏘드 |
효과 |
match(RE,string) |
RE가 문자열에 처음으로 부합한 일치 객체를 돌려준다. |
search(RE,string) |
문자열 안의 어디에서든 RE가 발견되면 일치 객체가 반환된다. |
split(RE, string) |
string.split()과 비슷하지만 RE를 가름자로 사용한다. |
sub(RE, replace, string) |
RE가 제일 처음 일치한 곳에서 RE를 교체문자열(replace)로 교체하여 만든 문자열을 돌려준다. 이 함수는 특징이 더 있으니 주의하자. 자세한 것은 문서를 참조하자. |
findall(RE, string) |
정규표현식(RE)이 문자열에 나타나면 리스트에 일치 객체를 모두 담아 돌려준다. |
compile(RE) |
정규 표현식 객체를 생산한다. 같은 RE를 여러 번 재사용할 수 있다. 이 객체에는 위의 메쏘드가 모두 있으나 묵시적인 정규 표현식을 가진다. 그리고 함수 버전을 사용하는 것보다 더 효율적이다. |
이 목록에 're' 메쏘드와 함수가 모두 나열된 것은 아니라는 것을 주의하세요. 나열된 함수들은 선택적인 매개변수들이 있어서 사용법을 확장시킬 수 있습니다. 나열된 함수들은 가장 흔히 사용되는 연산들이며 대부분의 필요에 충분합니다.
정규 표현식을 사용하는 실용적 예제
파이썬으로 정규 표현식을 어떻게 사용하는지 보여주는 한 예로 프로그램을 만들어 보겠습니다. HTML 파일에서 ALT 속성이 없는 IMG 태그를 찾아 보겠습니다. 그런 태그를 발견하면 메시지를 소유자에게 추가하여 앞으로 보다 우호적인 HTML을 만들도록 권고합니다!
import re
# 대/소문자로 'IMG'를 탐지한다.
# <와 'I' 사이에 0개 이상의 공간문자를 허용한다.
img = '< *[iI][mM][gG] '
# > 앞에 'ALT'나 'alt'까지 모든 문자를 허용한다.
alt = img + '.*[aA][lL][tT].*>'
# 파일을 열어 리스트로 읽어 들인다.
filename = raw_input('검색할 파일이름을 입력하시오')
inf = open(filename,'r')
lines = inf.readlines()
# 줄에 IMG 태그가 있으나 안에 ALT가 없으면,
# HTML 주석에 메시지를 추가한다.
for i in range(len(lines)):
if re.search(img,lines[i]) and not \
re.search(alt,lines[i]):
lines[i] += '<!-- PROVIDE ALT TAGS ON IMAGES! -->\n'
# 이제 변경된 파일을 쓰고 말끔하게 정리한다.
inf.close()
outf = open(filename,'w')
outf.writelines(lines)
outf.close()
위의 코드에서 두 가지 점에 주목하세요. 첫째 re.match 대신에 re.search를 사용합니다. search는 문자열의 어느 곳에서든 패턴을 찾는 반면에 match는 문자열의 처음만을 찾기 때문입니다. 두 번째 if 서술문 안에 서술문 계속 문자인 '\'를 사용합니다. 이렇게 하면 두 줄에 걸쳐 코드를 배치할 수 있습니다. 좀 더 말끔하게 보입니다. 특히 많은 표현식이 조합되어 있다면 말입니다.
이 코드는 완성도가 많이 떨어집니다. 왜냐하면 IMG 태그가 여러 줄에 걸쳐서 갈라져 있는 경우를 고려하지 않기 때문입니다. 그러나 우리에 목적에 맞게 테크닉은 충분히 잘 보여줍니다. 물론 HTML 파일의 변덕은 결코 장려할 것이 못되지만, 아마도 ALT 태그를 제공하지 않는 사람이라면 그런 대접을 받을만하지 않을까요!
다시 문법 계수기 사례 연구에서 정규 표현식이 작동하는 모습을 살펴 보겠습니다. 그동안 가지고 놀면서 re 모듈에 있는 다른 메쏘드도 점검해 보세요. 이런 강력한 텍스트 처리 도구를 사용하여 무엇을 할 수 있는지 겨우 그 표면만을 훑어 보았습니다.
기억해야 할 요점 |
· 정규 표현식은 텍스트 검색의 효율성과 힘을 개선시킬 수 있는 텍스트 패턴이다. · 정규 표현식은 제대로 사용하기에 어렵기로 악명이 높다. 그리고 알 수 없는 버그 때문에 난감할 수 있다 - 조심해서 다루자. · 정규 표현식은 만병 통치약이 아니다. 종종 좀 더 세심하게 접근할 필요가 있다. 세 번을 시도해도 잘 작동하지 않는다면 다른 접근법을 생각해 보자! |
출처 : http://coreapython.hosting.paran.com/tutor/index.htm
'Python > 파이썬 프로그래밍 연습' 카테고리의 다른 글
사건 주도적 프로그래밍 (0) | 2012.04.24 |
---|---|
객체 지향 프로그래밍 (0) | 2012.04.24 |
이름공간 (0) | 2012.04.24 |
에러 처리하기 (0) | 2012.04.24 |
텍스트 처리하기 (0) | 2012.04.24 |
댓글