1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사...

14
1. 정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild card)의 기능을 최 대한도로 확장시킨 것이라고 할 수 있다. 정규표현의 역사는 1940년대로 거슬러올라간다. 신 경생리학자인 Warren McCulloch와 Walter Pitts가 신경세포들이 상호작용하는 방식을 모델 링했는데, 몇 년 뒤 수학자인 Stephen Kleene이 모델을 수학적으로 형식화하여 regular set 이라 불렀고, 이 regular set을 표현하는 표기법을 고안해 냈는데 이것이 바로 정규표현 (regular expression)의 시초이다. 50년대와 60년대에 수학자들 사이에서 정규표현에 대한 이론적 논의가 있었지만, 이것이 컴퓨터 시스템에서 실용화된 것은 60년대 말의 일이다. Ken Thompson은 1968년 ‘Regular Expression Search Algorithm’이라는 논문을 발표하면서 정규표현 컴파일러를 제시하였고, 이 연구로부터 만들어진 정규표현 엔진이 유닉스 에디터인 qed, ed에 탑재되어 많은 사람들이 이용하게 되었다. ed에 탑재된 명령어 중 ‘Global Regular Expression Print’라는 것이 특히 애용되었고, 이로부터 grep이라는 이름이 생겨났 으며, 이 기능은 아예 별도의 유틸리티로 독립되게 되었다. 한편 Alfred Aho는 grep의 기능을 보완하여 egrep을 만들었고, 비슷한 시기에 정규표현을 지원하는 awk, lex, sed, Tcl, Emacs, vi 등의 도구도 만들어졌다. 그런데 이렇게 다양한 도 구들이 정규표현을 지원하게 되면서, 프로그램마다 정규표현의 표기법이라든지 특정 기호의 의미, 검색 방식 등에 차이가 생기게 되었다. 그리고 1987년 Larry Wall이 개발한 스크립트 언어 Perl은 정규표현을 가장 풍부하게 지원하여, 텍스트 처리를 위한 프로그래밍 언어로서 각광을 받게 되었다. 90년대에 들어와 만들어진 스크립트 언어 Python도 정규표현을 지원한 다. 1986년에는 Henry Spencer가 C 언어로 정규표현 엔진을 구현하여 공개함으로써, C 언어 개발자들이 자신의 응용 프로그램을 만들 때 정규표현 엔진을 자유로이 탑재할 수 있게 되었 다. 최근에는 유니코드의 대두에 따라 유니코드 문자열까지도 처리할 수 있는 정규표현 라이 브러리가 나오게 되었다. 대표적인 것으로 John Maddock이 만든 Boost.Regex 라이브러리와 Eric Niebler가 만든 Greta 및 Boost.Xpressive라는 라이브러리 등이 있다. 2. 정규표현의 기초 프로그램에 따라 정규표현 기호를 사용하는 방식에 약간의 차이가 있다. 모든 프로그램들을 다 살펴볼 수는 없으므로, 여기서는 cygwin 패키지에 포함되어 있는 grep과 pcregrep, 그리 고 EmEditor를 중심으로 살펴보기로 한다. 1) 2.1. dot 정규표현에서는 몇 개의 메타문자(metacharacter)를 정해 놓고 일정한 의미를 나타내게 하 고 있다. 예를 들어 마침표(dot) ‘.’는 임의의 하나의 문자를 의미한다. 이해를 돕기 위해 EmEditor를 이용해서 정규표현 검색을 직접 해 보자. EmEditor로 ‘이상한 나라의 앨리스’ 1) 예전의 cygwin 패키지에는 grep과 egrep, fgrep이 있었다. frep은 속도는 빠르지만 사용할 수 있는 정규표현에 심한 제약이 있고, egrep은 속도는 약간 느리지만 풍부한 정규표현을 사용할 수 있고, grep은 이 둘의 중간쯤 된다. 그런데 cygwin의 최근 버전에서는 fgrep과 egrep이 빠 지고 대신 pcregrep이란 것이 들어갔다. ‘pcre’는 ‘Perl Compatible Regular Expressions’의 약자로서, Philip Hazel이 Perl의 정규표현 syntax를 본따서 C 언어로 구현한 정규표현 라이브 러리이다. pcregrep은 이 pcre 엔진을 이용해서 만든 grep인 셈이다. 예전의 egrep보다 더 풍 부한 정규표현을 제공하므로 pcregrep을 이용하는 것이 좋다.

Upload: others

Post on 06-Oct-2020

0 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

1. 정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild card)의 기능을 최대한도로 확장시킨 것이라고 할 수 있다. 정규표현의 역사는 1940년대로 거슬러올라간다. 신경생리학자인 Warren McCulloch와 Walter Pitts가 신경세포들이 상호작용하는 방식을 모델링했는데, 몇 년 뒤 수학자인 Stephen Kleene이 모델을 수학적으로 형식화하여 regular set이라 불렀고, 이 regular set을 표현하는 표기법을 고안해 냈는데 이것이 바로 정규표현(regular expression)의 시초이다. 50년대와 60년대에 수학자들 사이에서 정규표현에 대한 이론적 논의가 있었지만, 이것이 컴퓨터 시스템에서 실용화된 것은 60년대 말의 일이다. Ken Thompson은 1968년 ‘Regular Expression Search Algorithm’이라는 논문을 발표하면서 정규표현 컴파일러를 제시하였고, 이 연구로부터 만들어진 정규표현 엔진이 유닉스 에디터인 qed, ed에 탑재되어 많은 사람들이 이용하게 되었다. ed에 탑재된 명령어 중 ‘Global Regular Expression Print’라는 것이 특히 애용되었고, 이로부터 grep이라는 이름이 생겨났으며, 이 기능은 아예 별도의 유틸리티로 독립되게 되었다. 한편 Alfred Aho는 grep의 기능을 보완하여 egrep을 만들었고, 비슷한 시기에 정규표현을 지원하는 awk, lex, sed, Tcl, Emacs, vi 등의 도구도 만들어졌다. 그런데 이렇게 다양한 도구들이 정규표현을 지원하게 되면서, 프로그램마다 정규표현의 표기법이라든지 특정 기호의 의미, 검색 방식 등에 차이가 생기게 되었다. 그리고 1987년 Larry Wall이 개발한 스크립트 언어 Perl은 정규표현을 가장 풍부하게 지원하여, 텍스트 처리를 위한 프로그래밍 언어로서 각광을 받게 되었다. 90년대에 들어와 만들어진 스크립트 언어 Python도 정규표현을 지원한다. 1986년에는 Henry Spencer가 C 언어로 정규표현 엔진을 구현하여 공개함으로써, C 언어 개발자들이 자신의 응용 프로그램을 만들 때 정규표현 엔진을 자유로이 탑재할 수 있게 되었다. 최근에는 유니코드의 대두에 따라 유니코드 문자열까지도 처리할 수 있는 정규표현 라이브러리가 나오게 되었다. 대표적인 것으로 John Maddock이 만든 Boost.Regex 라이브러리와 Eric Niebler가 만든 Greta 및 Boost.Xpressive라는 라이브러리 등이 있다.

2. 정규표현의 기초 프로그램에 따라 정규표현 기호를 사용하는 방식에 약간의 차이가 있다. 모든 프로그램들을 다 살펴볼 수는 없으므로, 여기서는 cygwin 패키지에 포함되어 있는 grep과 pcregrep, 그리고 EmEditor를 중심으로 살펴보기로 한다.1)

2.1. dot 정규표현에서는 몇 개의 메타문자(metacharacter)를 정해 놓고 일정한 의미를 나타내게 하고 있다. 예를 들어 마침표(dot) ‘.’는 임의의 하나의 문자를 의미한다. 이해를 돕기 위해 EmEditor를 이용해서 정규표현 검색을 직접 해 보자. EmEditor로 ‘이상한 나라의 앨리스’

1) 예전의 cygwin 패키지에는 grep과 egrep, fgrep이 있었다. frep은 속도는 빠르지만 사용할 수 있는 정규표현에 심한 제약이 있고, egrep은 속도는 약간 느리지만 풍부한 정규표현을 사용할 수 있고, grep은 이 둘의 중간쯤 된다. 그런데 cygwin의 최근 버전에서는 fgrep과 egrep이 빠지고 대신 pcregrep이란 것이 들어갔다. ‘pcre’는 ‘Perl Compatible Regular Expressions’의 약자로서, Philip Hazel이 Perl의 정규표현 syntax를 본따서 C 언어로 구현한 정규표현 라이브러리이다. pcregrep은 이 pcre 엔진을 이용해서 만든 grep인 셈이다. 예전의 egrep보다 더 풍부한 정규표현을 제공하므로 pcregrep을 이용하는 것이 좋다.

Page 2: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

파일을 연 다음 Ctrl-F를 눌러서 찾기 대화상자를 띄운다. ‘대소문자 구분’ 및 ‘정규표현 사용’에 체크를 한다. 그리고 ‘찾을 문자열’ 입력란에 “e.e”라고 입력하고 ‘찾기’ 버튼을 눌러 보자.

<그림 1>

그림을 보면, 검색 결과 매치된 문자열들이 다른 색깔로 표시된다. 여기서 볼 수 있듯이 “e.e”라는 정규표현은 ‘e’라는 문자 뒤에 임의의 문자가 하나 오고 그 뒤에 ‘e’라는 문자가 온 것을 의미한다. 두 ‘e’ 사이에 어떤 문자가 오든 상관없다.

2.2. character class 각괄호([ ]) 안에 복수의 문자들을 적어 주면 이 전체는 “각 괄호 안에 적은 문자들 중 어느 하나”를 의미하게 된다. 예를 들어 “[aeiou]s”라고 하면 ‘s’ 앞에 ‘a’, ‘e’, ‘i’, ‘o’, ‘u’ 중 어

Page 3: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

느 것이 와도 된다.

<그림 2>

반면에 각괄호 안에서 맨 앞에 ‘^’(caret)을 써 주고 그 뒤에 문자들을 나열하면 그것은 나열된 문자를 제외한 임의의 하나의 문자를 의미하게 된다.<그림 3>에서 보듯이 ‘s’ 앞에 ‘a’, ‘e’, ‘i’, ‘o’, ‘u’ 5개의 문자를 제외한 임의의 문자가 온 경우 매치되었다. ‘s’ 앞에 공백이 온 경우도 매치되었음에 유의하기 바란다. 공백 문자도 어엿한 하나의 문자인 것이다. 탭 문자, 줄바꿈 문자도 마찬가지이다. 이 셋을 합쳐서 white space라고 한다. character class의 효용성을 보여주기 위해 예를 하나 더 들어 보자. 영어의 동사 fall의 쓰임에 대해 알아보고 싶다고 하자. 문제는 동사 fall의 활용형이 falls, falling, fallen, fell로 다양하게 나타난다는 것이다. 문자열 ‘fall’을 찾으면 falls, falling, fallen도 함께 검색되므로 이들은 별 문제가 없지만 fell은 검색되지 않을 것이다. 문자열 ‘fell’을 따로 검색하면 물론 되기야 하지만, fell까지 한 번에 찾을 수 있으면 더 좋을 것이다. 이럴 때 ‘f[ae]ll’이라고 하면 fall과 fell이 함께 검색된다. 그런데 이렇게 할 경우 fellow까지 함께 검색된다. fellow를 배제하고 싶으면 ‘f[ae]ll[^o]’와 같이 하면 된다.

Page 4: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

<그림 3>

<그림 4>

Page 5: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

2.3. anchor 찾고자 하는 문자열이 라인의 맨 처음에 있는 경우만 골라내고자 할 때에는 ‘^’를 사용하고 라인의 맨 끝에 있는 경우만 골라내고자 할 때에는 ‘$’를 사용한다. 예를 들어 “^th”라고 하면 “th”라는 문자열이 라인의 맨 처음에 온 경우만 매치된다. (그림 4) 반면에 “to$”라고 하면 “to”라는 문자열이 라인의 맨 끝에 온 경우에만 매치된다(그림 5).2)

<그림 5>

2.4. alternation하나의 문자에 대해 x라는 문자도 좋고 y라는 문자도 좋다고 할 경우에는 character class를 사용하면 되지만, 문자열 a도 좋고 문자열 b도 좋다고 할 경우에는 alternation을 사용해야 한다. alternation은 수직 바(vertical bar) ‘|’로 나타낸다. 예를 들어 “(to|in) ”이라고 하면 공백 앞에 “to”가 오거나 “in”이 온 경우를 찾아 준다.

2) ‘^’와 ‘$’의 의미가 각각 라인의 시작과 끝인가, 아니면 검색 대상 문자열의 시작과 끝인가 하는 문제가 있다. grep이나 egrep 등 정규표현을 지원한 초기의 도구들은 입력 파일을 한 라인 한 라인 불러와서 처리를 하기 때문에 이 문제가 발생하지 않았다. 하나의 라인이 항상 검색 대상 문자열이 되기 때문이다. 그러나 복수의 라인이 검색 대상 문자열이 될 때에는 이 문제가 제기된다. EmEditor에서는 두 anchor 기호의 의미를 라인의 시작과 끝으로 규정하고 있다. 만약 검색 대상 문자열의 시작과 끝이라는 의미였다면, 파일의 맨 처음에 “th”가 온 경우, 파일의 맨 끝에 “to”가 온 경우만 매치되었을 것이다.

Page 6: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

여기서 괄호는 alternation 연산자의 힘의 미치는 범위를 한정하는 역할만 한다. 실제로 괄호가 포함된 문자열을 찾아 주는 것은 아니다. 괄호 역시 정규표현에서 특별한 의미를 갖는 메타문자인 것이다. 그럼 실제로 괄호나 마침표가 들어 있는 문자열을 찾고 싶을 때에는 어떻게 하나? 그럴 때에는 escape 문자인 역슬래시 ‘\’를 그 앞에 첨가해 준다.

<그림 6>

만약 “to|in”을 둘러싸고 있는 괄호를 뺀다면 어떻게 될까? 그러면 “to” 또는 “in ”이 검색 대상이 된다. “(to)|(in )”과 같은 의미로 해석되는 것이다.3)

2.5. repetition 영어 단어 “colour”는 ‘u’를 생략하고 쓰기도 한다. ‘u’를 포함한 “colour”든지 ‘u’를 생략한 “color”든지 상관없이 다 뽑아내고 싶을 때는 어떻게 할까? 물론 “colour|color”와 같이 할 수도 있다. 그러나 이보다 더 간단한 방법이 있다. “colou?r”과 같이 하면 된다. 기호 ‘?’는 그 앞의 표현이 없거나 1개 있음을 나타낸다. 정규표현 “colou?r”의 의미를 곧이곧대로 해석하자면 문자열 “colo”이 오고 그 뒤에 문자 ‘u’가 없어도 되고 1개 있어도 되고 그 뒤에 문자 ‘r’이 오는 것을 의미한다. 즉 기호 ‘?’는 “0 또는 1개”의 의미로 이해하면 된다. 이에 반해, 기호 ‘+’는 “1개 이상”을 의미하고 기호 ‘*’는 0개 이상을 의미한다.4)

3) grep의 경우 ‘|’ 앞에 역슬래시를 해 줘야 한다. ‘?’나 ‘+’ 같은 반복 기호도 마찬가지이다.

4) ‘*’는 흔히 Kleene star 또는 Kleene closure라 불린다. 정규표현을 수학적으로 형식화한 수학자 Kleene의 이름이 기념되고 있는 것이다. 한편 ‘+’는 positive closure라고 불린다.

Page 7: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

<그림 7>

위 그림에서 보듯이 “a+”라고 하면 ‘a’가 1개든 2개든 몇개든 연속해 있는 것은 다 매치된다. 그럼 위 그림에서 주어진 정규표현과 매치된 문자열은 모두 몇개일까? 눈에 보이는 대로 헤아려서 14개라고 답하는 사람이 많을 것이다. 그러나 EmEditor에서 커서를 문서의 맨 처음에 두고(Ctrl-Home) ‘Find Next’ 버튼을 누르거나 F3을 눌러서 다음 매치된 문자열이 무엇인지 추적해 보자. 그럼 매치된 문자열이 14개가 아니라 22개임을 알 수 있을 것이다. “aaaaa”에 걸쳐 있는 색깔 강조가 하위문자열을 덮어 버려서 드러나지 않은 것이지, 사실 “aaaaa”의 끝 4개의 ‘a’로 이루어진 “aaaa”, 끝의 3개의 ‘a’로 이루어진 “aaa”, 끝의 2개의 ‘a’로 이루어진 “aa”, 끝의 1개의 ‘a’로 이루어진 “a” 등의 하위문자열도 매치된 것이다. 그렇다면 제기될 수 있는 의문은 “aaaaa”의 처음 4개의 ‘a’로 이루어진 “aaaa”, 처음 3개의 ‘a’로 이루어진 “aaa” 등은 매치되는 것으로 볼 수 없는가 하는 것이다. 답은 “매치되지 않는다”이다. 그 이유는 정규표현의 반복 기호 ‘?’, ‘+’, ‘*’가 기본적으로 greedy search를 하기 때문이다. 즉 주어진 정규표현에 매치될 수 있는 어떤 문자열 A가 있고, A와 시작을 공유하지만 그보다 먼저 끝나는 하위문자열 B가 있을 때, 정규표현은 항상 긴 문자열 A를 선택한다는 것이다. 예를 들어 “bbAaaaZaaaaZcc”라는 검색 대상 문자열이 있고 “A.*Z”라는 정규표현이 있을 때, “AaaaZaaaaZ”도 이 정규표현에 매치될 수 있고 이 문자열의 하위문자열인 “AaaaZ”도 매치될 수 있는데, 이 둘 중 보다 긴 쪽인 전자를 선택한다. 반복 기호가 greedy search를 하지 않고 가능한 한 최소의 문자열을 선택하도록 하기 위해서는 반복 기호 뒤에 ‘?’를 붙인다.5) 다음 두 그림을 비교해 보기 바란다.

<그림 8>

5) 여기서 기호 ‘?’은 반복 기호로서의 의미와는 다른 의미를 나타내는 셈이다.

Page 8: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

<그림 9>

반복 기호가 하나의 문자에만 적용되는 것은 아니다. 복잡한 정규표현 전체가 반복의 대상이 될 수 있다. 예를 들어 “(ab)+”는 문자열 “ab”가 1개 이상 반복된 것을 의미한다. 이렇게 반복 기호를 복합 표현 전체에 적용할 때에는 반복 기호의 힘이 적용되는 범위를 괄호로 묶어 준다. alternation의 경우와 마찬가지로 이 때의 괄호는 메타문자로서 범위를 지정해 주는 기능만 할 뿐, 검색 대상 문자열의 괄호와 매치되지는 않는다. character class에도 반복 기호가 적용될 수 있다. 예를 들어 “z[abc]+”는 ‘z’ 뒤에 ‘a’, ‘b’, ‘c’ 셋 중의 어느 것이든 1개 이상 반복된 것을 의미한다. 똑같은 문자가 반복될 필요는 없다. ‘a’, ‘b’, ‘c’ 셋이 뒤섞여서 나와도 상관없다.

<그림 10> 반복되는 횟수를 정확히 지정해 줄 수도 있다. 어떤 표현이 정확히 몇 번 이상, 몇 번 이하 반복된 경우를 찾고 싶을 때에는 “{n,m}”과 같은 표기법을 사용한다. 예를 들어 “a{3,4}”라고 하면 ‘a’가 3번 이상 4번 이하 반복된 것을 의미한다.

<그림 11> “{n,}”와 같이 쓰면 반복 횟수의 하한선만 n번으로 정해지고 상한선은 없다. 즉 “n번 이상”을 의미한다. 반면에 “{n}”은 정확히 n번 반복된 것을 의미한다.

Page 9: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

2.6. backreference backreference는 정규표현 내에서 앞에 나온 부분을 뒤에서 지칭할 때 사용하는 것이다. 예를 들어 “(ed|es)[^\n]*\1”라고 하면 “ed” 또는 “es”가 하나의 라인 내에서 반복된 경우를 찾아 준다. 앞에 나온 것이 “ed”이면 뒤에도 “ed”가 나와야 하고, 앞에 나온 것이 “es”이면 뒤에도 “es”가 나와야 한다. 이 점에서 “(ed|es)[^\n]*(ed|es)”와 차이가 있다. 이것은 앞에는 “ed”가 나오고 뒤에는 “es”가 나와도 매치된다. backreference할 부분은 반드시 괄호로 묶어 주어야 한다. 하나의 정규 표현 내에서 괄호로 묶은 부분이 둘 이상일 경우, 왼쪽부터 세어서 여는 괄호가 먼저 나오는 것부터 “\1”, “\2”, “\3” 하는 식으로 지칭한다. “이상한 나라의 앨리스” 파일에서 “(ed|es)[^\n]*\1”을 실제로 찾아 보자. grep이나 pcregrep은 정규표현에 의해 매치되는 부분이 정확히 어디서 어디까지인지가 금방 드러나지 않는 단점이 있다. 그래서 Eric Niebler의 Greta라는 정규표현 라이브러리를 이용하여 필자가 만든 유틸리티를 이용해서 찾아 보자. 라인 번호가 앞에 붙어 있고, 정규표현에 의해 매치되는 부분의 시작과 끝이 까만 삼각형으로 표시되어 있다.

<그림 12>

Page 10: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

<그림 13> backreference는 찾기뿐 아니라 바꾸기에서도 유용하다. “e.e” 뒤에 나오는 공백을 ‘#’으로 바꾸고 싶다고 하자. 이 때 ‘e’와 ‘e’ 사이에는 임의의 문자가 올 수 있다. 그렇기 때문에 대치해 넣을 문자열을 하나로 지정해 줄 수가 없다. 이럴 때 backreference를 이용하면 편리한 것이다. 찾을 문자열을 “(e.e) ”로 하고 대치해 넣을 문자열을 “\1#”이라고 하면 된다. “\1”이 찾을 문자열에서 괄호로 묶은 부분을 가리키기 때문이다.

3. 정규표현의 응용3.1. 영어 사전 이제 정규표현을 이용해서 실제로 유용한 작업을 해 보자. 染谷泰正(Someya Yasumasa)라는 일본의 영어학자가 만든 영어 단어 목록을 가지고 예시하겠다. 파일을 열어 보면 표제어, 등급(난이도), 품사의 세 필드로 이루어져 있다. 우선 “tion”으로 끝나는 표제어들을 추출해 보자. 각 필드는 공백으로 구분되어 있으므로 pcregrep으로 “tion ”을 찾아 보면 될 것이다.

Page 11: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

위 그림처럼 하면 “tion”으로 끝나는 단어들이 “tion.txt‘라는 이름의 파일에 저장될 것이다. 단어의 수가 1009개가 맞는지 확인해 보라. 이번에는 형용사들을 모두 추출해 보자. 이 사전에서 형용사는 “JJ”로 표시되어 있다. 그냥 “JJ”를 찾으면 혹시 단어 내부에 “JJ”라는 문자열이 들어 있으면 그 단어가 형용사가 아니더라도 함께 추출되어 나올 것이므로 “JJ”가 라인 맨 끝에 나온다는 것을 명시해 주는 것이 좋겠다. pcregrep “JJ$” wrdlvl-2.txt >output_file_name과 같이 하면 될 것이다.이번에는 부사 중에서 등급이 15에서 24 사이인 것들을 뽑아 보자. 이 사전에서 부사는 “RB”로 표시되어 있다. 그럼 정규표현의 끝 부분이 “ RB$”인 것은 쉽게 알겠는데, 그 앞 부분의 등급은 어떻게 나타내야 할까? 이 사전에서 등급은 항상 두 자리의 숫자로 표시되어 있다. 10보다 작은 수는 앞에 0을 적어 주고 있는 것이다. 그렇다면 가장 무식한 방법은 “(15|16|17|18|19|20|21|22|23|24) RB$”처럼 하면 될 것이다. 그러나 이 방법은 타이핑하기도 귀찮을 뿐 아니라 검색 시간도 상대적으로 오래 걸린다. 보다 좋은 방법은 “(1[5-9]|2[0-4]) RB$”와 같이 하는 것이다. 앞에 오는 숫자가 1일 때에는 뒤에 오는 숫자는 5부터 9 사이이고 앞 숫자가 2일 때에는 뒤 숫자가 0과 4 사이인데, 이 두 경우를 ‘|’로 연결한 것이다. 결과가 578개인지 확인해 보라.

<그림 15>

3.2. 국어사전 인터넷에 올라 있는 동아국어사전을 가지고 작업을 해 보자. 파일을 열어 보면 다음과 같은

<그림 14>

Page 12: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

포맷으로 되어 있다.

<그림 16>

여기서 ‘가’라는 음절이 두 번 이상 (인접해 있지는 않더라도) 반복되는 표제어들을 뽑아 보자. 우선 찾고자 하는 문자열을 뜻풀이나 예문이 아니라 표제어로 제한하기 위해 맨 앞에 “^\* ”를 붙여 주어야 할 것이다. 이 사전에서 표제어는 라인의 맨 처음에 ‘*’라는 기호 뒤에 공백을 하나 두고서 나타나기 때문이다. ‘*’ 앞에 역슬래시를 두는 이유는 짐작할 수 있을 것이다. 정규표현에서 메타문자 ‘*’가 갖는 특수한 의미를 없애고 진짜 ‘*’라는 문자를 찾기 위해서이다. 그리고 두 개의 ‘가’ 사이에 콜론(‘:’)이 있으면 안 될 것이다. 콜론이 표제어와 뜻풀이를 구분해 주고 있는데, 두 개의 ‘가’가 콜론의 경계를 넘어서 반복되는 것을 하용하면 표제어에 ‘가’가 하나 나오고 뜻풀이에 ‘가’가 나오는 것까지 포함하게 되기 때문이다. 표제어와 한자 정보를 구분짓고 있는 괄호도 제외해야 하지 않느냐는 질문이 나올 법하다. 원칙적으로는 그렇다. 그러나 한자 정보 구획에 한글 ‘가’가 나올 리는 없기 때문에 괄호까지 신경 쓸 필요는 없을 듯하다. 그러면 답은 “^\* 가[^:]*가”와 같이 된다. 실제로 EmEditor로 찾아 보면 다음과 같이 ‘가’가 표제어 내에서 두 번 이상 나온 예들을 찾아 준다.

Page 13: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

<그림 17>

그럼 이번에는 표제어 내에 똑같은 음절이 두 개 이상 연속해서 나오는 경우를 찾아 보자. “^\* [^:(]*([^:(])\1”와 같이 하면 될 것이다. 라인 맨 앞에 “*”와 공백이 오고 그 뒤에 콜론과 괄호를 제외한 임의의 문자가 임의의 수만큼 오고(없어도 무방하고), 그 뒤에 콜론과 괄호를 제외한 임의의 한 문자가 오고 그것이 바로 뒤에 다시 온다는 의미이다. EmEditor로 실제로 찾아 보면, ‘가가’, ‘가가례’, ‘가가 문전’, ‘가가 호호’, ‘가가’, ‘가가 대소’, ‘까까머리’, ‘깐깐오월’, ‘갈갈’, ‘깔깔’, ‘깔깔웃다’, ‘갈갈이’, ‘깔깔하다’ 등이 제대로 찾아진다. 그런데 여기서 한 가지 주의할 것이 있다. 똑같은 정규표현을 가지고 똑같은 한글완성형 파일에 대해 grep이나 pcregrep을 가지고 찾아 보자. 이상하게 같은 음절이 연이어 나온 경우가 아닌데도 뽑혀져 나오는 것이 많을 것이다. 이런 현상의 원인은 “하나의 문자란 무엇을 의미하는가?” 하는 문제와 관련되어 있다. grep이나 pcregrep은 multi-byte 코드 체계를 고려하지 않고 만들어졌다. 따라서 모든 문자는 1 바이트라는 전제 위에서 정규표현의 문법도 만들어졌다. 그러나 한글 음절은 KS 완성형 코드 체계에서 2 바이트에 해당한다. grep이나 pcregrep은 정규표현에서 dot도 character class도 모두 1 바이트로 간주한다. 따라서, 위의 정규표현은 같은 음절이 연속해서 나오는 경우를 찾는 것이 아니라 같은 바이트가 연속해서 나오는 경우를 찾는 것이다. ‘납’은 코드값이 ‘0xB3B3’이고 ‘만’은 ‘0xB8B8’인데 이렇게 동일한 바이트가 연속된 음절을 다 찾아 주는 것이다. 그리고 ‘-스럽다’의 ‘럽’은 ‘0xB7B4’, ‘다’는 ‘0xB4D9’이므로 ‘럽’의 뒤 바이트와 ‘다’의 앞 바이트 역시 동일 바이트의 연속인 것이다. 이런 예도 함께 추출될 것이다. 반면에 정작 같은 음절이 반복되더라도 동일한 바이트의 반복이 아닌 이상 정규표현에 매치되지 않을 것이다. 따라서 grep이나 pcregrep 같은 도구를 가지고 한글을 처리할 때에는 이러한 점을 유의해야 한다.

Page 14: 1.정규표현의 역사hosting03.snu.ac.kr/~korean/old/data/han/regular...1.정규표현의 역사 정규표현(regular expression)은 문자열 검색에 있어서 아무개문자(wild

반면에 EmEditor는 철저한 유니코드 기반 프로그램으로서, KS 완성형이라든가 기타 다른 나라의 로컬 코드 체계로 되어 있는 파일까지도 프로그램 내부에서는 유니코드로 변환해서 처리한다. 유니코드에서는(정확히 말하면 UCS2에서는) 모든 문자가 2 바이트를 차지한다. 따라서 EmEditor 같은 유니코드 기반 프로그램에서는 정규표현 엔진도 유니코드 기반으로, 모든 문자가 2 바이트라는 전제 위에서 만든 것이다. 한글 자료를 처리하기 위해서는 유니코드 기반 프로그램이 유리한 점이 많다.