서버관리자라면 알아야 하는 정규식

게시자 : 한동훈 (ddoch )
등록일 : 1997-05-24 01:03
제 목 : [강좌] Regex (정규표현식) 라이브러리 (1)

GNU REGEX (정규표현식) 프로그래밍 강좌 (1)
——————————————-

글쓴이 : 한동훈 ddoch at hitel.kol.co.kr
날 짜 : 1997.5.23.
저작권 : 상업적인 용도가 아닌한 어디로든 이동 및 게재 가능
부탁사항 : 질문과 관련된 내용이나 답변을 요하는 내용은 메일로 적어주지
마시고 관련 프로그래밍 게시판을 이용해 주시면 성의껏 답변해
드리겠습니다. 제가 상당히 게으른 관계로 질문메일에 제대로 답
변을 못해드리고 있는 점 죄송합니다.

▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤
▤ 목차 ▤
▤ ▤
▤ 1. 들어가는 말 ▤
▤ 2. 정규표현식 문법 ▤
▤ 3. 공통적인 오퍼레이터 ▤
▤ 4. GNU 오퍼레이터 ▤
▤ 5. GNU 이맥스 오퍼레이터 ▤
▤ 6. Regex 프로그래밍 ▤
▤ 1) BSD Regex 함수 ▤
▤ 2) POSIX Regex 함수 ▤
▤ 3) GNU Regex 함수 ▤
▤ 7. 나오는 말 ▤
▤ ▤
▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤▤

1. 들어가는 말
—————

안녕하세요. ddoch 한동훈 입니다.

flex 와 bison 을 공부하던 중 regex에 대한 정리가 필요하다고 생각하여 regex
메뉴얼 (자료실/문서/2027번 문서/regex.zip)로 공부를 하던 중 그냥번역하는
것 보다는 실제 예를 들어가면서 설명하는 것이 좋을 것 같아 이렇게 강좌로 올
립니다.

이 강좌 내용은 위 메뉴얼을 중심으로 설명을 드릴 예정이며 순서 또한 6. Regex
프로그래밍 중 난이도에 따라 비교적 쉬운 “BSD Regex 함수” 부터 설명을 하였다
는 점을 제외하고는 같습니다. 그리고 중간중간 이해를 돕기위해 예제로 설명을
하겠으며 설명내용 중 잘못된 것이나 참고사항은 위의 주소로 보내주시면 감사하
겠습니다. 그리고 설명 중 모자라는 부분이나 빠진 부분이 있을 수도 있으므로,
위에서 말씀드린 메뉴얼을 참고하시기 바라겠습니다.

그리고 6장을 제외한 나머지 내용은 일반 리눅스 및 유닉스 사용자들이 참조할 수
있는 공통적인 내용이며 6장은 프로그래머를 위한 내용입니다.

regex는 정규표현식을 이용하여 패턴 검색 및 매칭 작업을 수행하는 함수를 제공
하는 일종의 라이브러리입니다. Linux 나 UNIX에서 이 라이브러리는 아주 광범위
하게 사용되어 사용자 수준에서의 정규표현식을 사용하여 강력한 작업을 할 수 있
도록 도와주는 역할을 합니다. 아마도 리눅스나 유닉스를 사용하시면서 쉘상에서,

queen:~$ xanim [a-g]*.gif

이런 명령을 사용해보신 분들이 많이 계실 겁니다.
표준 쉘만이 아니라, ed, ex, sed, awk, vi, emacs, grep, egrep등의 유닉스 표준
유틸리티들과 postgres, bison, flex등의 툴 들에서도 내부적으로 사용을 하며,
프로그램을 직접 설치해보신 분은 소스디렉토리안에 “regex.h, regex.c”라는 파일
이 들어 있는 경우를 종종 보셨을 겁니다.

이런 정규표현식은 bison, flex에서도 사용된다고 하였는 데, 이것은 각종 문자열
파싱이나 문장, 구문해석에 사용되어 컴파일러 제작, 어문해석기 등의 프로그램을
만드는 데 사용됩니다.아직 정규표현식에서 “[가-나]”와 같은 한글을 처리하지 못
하고 있는 데, 이런 문제는 한글어휘분석기 및 한글토큰분석에 난제로 등장하고 있
는 관계로 이의 해결은 우리들의 몫이 아닐까 생각합니다.

설치는, 리동 자료실에 있는 regex 0.12 버젼 (자료실/2370번/regex012.tgz)을
받으셔서 root로 압축을 푸시고 “./configure; make; make install”로 설치를
하시면 됩니다. 네트웍에서 구하실려면 GNU 공식 사이트나 한국에서 미러를 하고
있는 카이스트에서 “regex” 로 검색하시면 찾을 수 있습니다.

말이 설치지, 설치되는 것은 info 파일과 texi 파일을 컴파일하여 해당디렉토리로
이동시키는 것일 뿐입니다. 압축을 푼 디렉토리에 보시면 regex.c 와 regex.h가
있는 데, 이 두개가 전부이므로 휴대를 하시면서 사용하시던지, 아니면 regex.o
파일로 링크만 시키시던지는 마음대로 하시면 될 것입니다. 참고로 슬랙 3.1에
“/usr/include”에도 “regex.h”가 있으나 본 헤더파일과는 다르므로 인클루드 하실
때 주의하시기 바랍니다. 테스트 파일은 “test/” 디렉토리에 있으므로 살펴보시면
도움이 될 것이며, 테스트 소스 컴파일은 “test/” 디렉토리에서 “make all” 로 하
시면 됩니다.

“regex.h” 파일에 아주 자세한 설명이 들어 있으므로 자주 참고를 하시기 바라며,
한부 뽑아서 보셔도 좋습니다.

정규표현식을 이용하여 프로그램을 짜시려는 분들이나 정규표현식을 익히시려는 분
들에게 조금이나마 도움이 되었으면 좋겠습니다.

정규표현식을 이용한 프로그램인 egrep을 이용하여 소스내에서 특정 토큰(예: int)
을 찾는 경우를 예를 들어보겠습니다.

queen:~$ egrep int something.c

이런식으로 찾으면 “printf” 도 같이 검색이 되므로 요구를 채워주지 못합니다.

queen:~$ egrep “[^[:alnum:]_]int[^[:alnum:]_]” something.c

이제, 하나의 독립된 토큰으로서의 “int”만 찾아서 우리에게 보여줍니다.

만일, egrep 같은 프로그램을 짤 때, 첫번째 인자(정규표현식)를 일일이 C로 파싱
하여 처리하는 것은 거의 사람의 인내성의 한계를 실험하는 것이 될 것입니다.
이럴 때 미리 짜놓은 regex 함수를 이용하여 해당 펑션에서 첫번째 인자와 해당파
일을 읽은 문자열을 넘겨주면 알아서 검색 및 패턴 매칭을 해주므로 아주 간편하
게 프로그래밍 할 수 있는 것입니다.

정규표현식에도 상당히 많은 형태의 문법이 있다는 것은 천천히 보여드리도록 하
겠습니다. 그리고 강좌 마지막에 가능하다면, 정규표현식을 이용하는 간단한 기
능의 egrep 버젼을 만들어 보도록 하겠습니다.

자, 그럼 이제 설명에 들어가볼까요..

2. 정규표현식 문법
——————-

정규표현식은 어떤 문자열의 집합을 묘사하는 텍스트 스트링입니다.
오퍼레이터는 ‘[‘나 ‘*’같은 한개 이상의 문자와 매칭되는 정규표현식안에 있는
문자입니다.
일반적으로 대부분의 문자는 ‘a’나 ‘z’와 같이 그 자체로서의 문자그래로의 뜻
을 가집니다. 이것을 여기서는 “그냥문자(ordinary) 또는 일반문자”라고 하겠습니
다. 이와는 반대로 ‘.’와 같이 특수한 뜻을 나타내는 문자를 “특수문자(special)”
라고 부르겠습니다.
어떤 문자가 특수문자인지 또는 그냥문자인지는 다양한 정규표현식의 문법과 해당
정규표현식에서의 문맥에 따라 달라집니다. 이제, 아래에서 자세하게 이야기 하
겠습니다.

2.1 문법 비트
————–

정규표현식에서 어떤 특정한 문법은 몇몇의 문자들을 항상 특수문자로 취급하고,
다른 문법은 가끔 특수문자로 취급하며, 또다른 문법은 그러한 문자들을 일반문자
로 취급할 경우가 있습니다.

주어진 정규표현식에서 Regex가 인식하는 특정한 문법은 해당 정규표현식의 패턴
버퍼의 syntax 필드에 따라 다릅니다. 이 말은 위의 예에서 정규표현식 중에서
“[:alpha:]”같은 것들이 이 패턴을 다루는 버퍼중에서 syntax 필드에 따라 틀린
문법으로 치부될 수도 있고, 그냥 무시하고 넘어갈 수도 있으며, 올바르게 작동
할 수도 있다는 이야기입니다. 따라서 syntax 필드를 조정해줌으로써 정규표현식
의 기능을 다양하게 제한하고 확장할 수 있다는 이야기가 되겠네요.

패턴 버퍼는 “[a-g]*”와 같은 정규표현식을 뒤에서 설명하는 정규표현식 “컴파일”
함수에 인자로 넘겨줌으로 만들수 있습니다.

(참고로, 여기서 “컴파일”이라함은, 텍스트 스트링 형태의 정규표현식을 검색,매칭
할수 있는 형태로 만들기 위해 어떤 버펴(패턴 버퍼)에 번역을 하거나 이에 필요
한 각종 값을 담아두는 역할을 하는 것을 이야기합니다. )

syntax 필드는 다양한 비트들의 조합으로 구성되며, 이러한 비트들을 보통,
“문법 비트”라고 부릅니다. 이러한 문법 비트는 “어떤 문자가 어떤 오퍼레이터가
될것인가”하는 문제를 결정하게 됩니다.

이제, 문법 비트의 모든 것을 알파벳 순서로 설명을 드리겠습니다. 참고적으로, 이
것은 “regex.h”에 자세히 설명되어 있는 것으로 “RE_”로 정의되어 있습니다.

언뜻 정의된 이름만으로도 그 기능을 충분히 예견할 수 있을 것입니다.

* RE_BACKSLASH_ESCAPE_IN_LISTS (리스트에서 백슬래쉬는 이스케이프)

일반적인 리스트 오퍼레이터인 ‘[‘, ‘]’안에서 ‘\'(이스케이프)문자는 뒷글자
를 이스케이프하는 탈출문자가 된다는 이야지이지요. 만일 이 비트가 세팅되
지 않으면 리스트 오퍼레이터안에서의 ‘\’는 그냥문자(=일반문자)가 됩니다.

보통, 리스트 오퍼레이터 안의 문자는 특수문자 성격을 상실하고 그냥문자가 되
는 게 일반적입니다.

* RE_BK_PLUS_QM (‘\+’, ‘\?’)

이 비트가 설정되면 ‘\+’는 “하나이상을 매칭시키는 오퍼레이터(이후 하나이상
오퍼레이터)(match-one-or-more operator)”가 되며, ‘\?’는 “0개 이상을 매칭
시키는 오퍼레이터 (이후 뻥개이상 (^^ 오퍼레이터)”(match-zero-or-more
operator)이 됩니다. 이 비트가 설정되지 않으면, 각각 ‘+’와 ‘?’가 그 역할을
대신합니다.

일반적으로는 보통, ‘+’, ‘?’가 각각 하나이상, 0개 이상을 매칭시키는 오퍼레이
터로 작동을 합니다.
만일 RE_LIMITED_OPS 가 세팅되었다면 이 비트는 세팅하지 마셔야 합니다.

* RE_CHAR_CLASSES (문자 클래스)

이 비트가 세팅되어 있으면 리스트안에서 문자클래스 오퍼레이터를 사용할 수
있으며 그렇지 않으면 사용할 수 없습니다.

위에서 예를 든, egrep 의 경우에는 리스트안([..])에서 문자 클래스 ([:alnum:])
을 사용할 수 있었으므로 이 비트가 세팅되어 있다는 것을 미루어 짐작할 수 있
습니다.

* RE_CONTEXT_INDEP_ANCHORS

이 비트가 세팅되어 있다면, ‘^’와 ‘$’는 리스트 밖에서의 어디에서나 특수문자
로 취급하며, 그렇지 않다면 확실한 문맥에서만 특수문자로 취급합니다.

* RE_CONTEXT_INDEP_OPS

이 비트가 세팅되어 있으면, 리스트 밖에서 어디서던지 “확실한 문자”들은 특수
문자로 취급됩니다. 그렇지 않으면 그러한 문자들은 단지 어떤 문맥에서만 특수
문자이고 다른 곳에서는 그냥문자로 취급됩니다. 특히, 이 비트가 세팅되지 않
은 상태의 ‘*’ 와 RE_LIMITED_OPS가 설정되지 않았을 때의 ‘+’와 ‘?'(또는
RE_BK_PLUS_QM이 설정되었을 때의 ‘\+’, ‘\?’)는, 정규표현식의 처음(예:*foo)
이나 오픈그룹연산자(‘(‘)나 대체 연산자(‘|’)의 바로뒤(예: (*.., |*)에 오지
않을 때에만 이것을 반복 오퍼레이터로 취급합니다.

* RE_CONTEXT_INVALID_OPS

이 비트가 세팅되어 있다면, 반복오퍼레이터(‘*’)와 대체오퍼레이터(‘|’)는
정규표현식 내부에서 “확실한 위치”에는 올수 없게 됩니다. 특히, 다음과 같은
경우에는 정규표현식이 잘 못된 경우입니다.

O 반복 오퍼레이터가 다음의 위치에 올경우
– 정규표현식의 처음에 올경우 (예: ‘*[a-z]’)
– 라인의 시작 오퍼레이터 (‘^’)나 오픈 그룹 (‘(‘)이나 대체 오퍼레이터(‘|’)
의 바로뒤에 오는 경우 (예: ‘^*’, ‘(*..)’, ‘|*’)

o 대체 오퍼레이터가 다음의 위치에 올경우
– 정규표현식의 처음이나 마지막에 올경우 (예: ‘|foo’, ‘foo|’)
– 라인의 끝 오퍼레이터 (‘$’)의 바로 전이나, 대체오퍼레이터, 오픈 그룹
오퍼레이터의 바로뒤에 올경우 (예: ‘|$’, ‘||’, ‘(|’)

만일, 이 비트가 세팅되어 있지 않다면, 정규표현식의 어디에서던지 반복 오퍼
레이터와 데체 오퍼레이터가 올 수 있게 됩니다.

* RE_DOT_NEWLINE (점 ‘.’은 뉴라인을 포함)

이 비트가 세팅되어 있다면, “아무거나한문자 오퍼레이터(match-any-character
operator)” (‘.’)는 뉴라인문자와 매칭될 수 있습니다. 세트되어 있지 않다면
‘.’ 는 뉴라인문자(‘\n’) 와 매칭될 수 없습니다.

* RE_DOT_NOT_NULL (점 ‘.’은 널이 될 수 없다)

이 비트가 세팅되어 있다면, 아무거나한문자 오퍼레이터는 널문자와 매칭될 수
없으며, 세트되어 있지 않다면 가능합니다.

* RE_INTERVALS (간격)

이 비트가 세트되어 있다면 Regex는 “간격오퍼레이터(interval operators)”
(‘{‘, ‘}’)를 인식할 수 있고, 그렇지 않다면 불가능합니다.

* RE_LIMITED_OPS (오퍼레이터 제한)

이 비트가 세팅되어 있다면, Regex는 하나이상 오퍼레이터(‘+’또는 ‘\+’)와
뻥개이상 오퍼레이터(‘*’)는 인식을 하지 못하며, 세팅되어 있지 않다면, 가능
합니다.

* RE_NEWLINE_ALT (뉴라인 대체)

이 비트가 세팅되어 있다면, 뉴라인은 대체 오퍼레이터로 취급되며, 그렇지 않다
면 뉴라인문자는 그냥문자가 됩니다.

* RE_NO_BK_BRACES (백슬래쉬 없는 중괄호)

이 비트가 세트되어 있다면, ‘{‘는 오픈 인터벌(open-interval)오퍼레이터가 되
고, ‘}’는 클로즈 인터벌(close-interval) 오퍼레이터가 됩니다. 그렇지 않다면
, ‘\{‘와 ‘\}’가 각각 그역할을 대신합니다. 이 비트는 RE_INTERVALS가 세트되
어 있을 때에만 상관있습니다.

* RE_NO_BK_PARENS (백슬래쉬 없는 소괄호)

이 비트가 세트되어 있다면 ‘(‘는 오픈 그룹 오퍼레이터가 되고, ‘)’는 클로즈
그룹 오퍼레이터가 됩니다. 만일 이 비트가 세트되어 있지 않다면, ‘\(‘와
‘\)’가 각각 그역할을 대신합니다.

* RE_NO_BK_REFS (거꾸로참조 (^^ 오퍼레이터 인식안함)

이 비트가 세트되어 있다면, Regex는 ‘\’digit 와 같은 거꾸로참조 오퍼레이터
를 인식하지 않습니다. 그렇지 않다면 인식합니다.

* RE_NO_BK_VBAR (백슬래쉬 막대기 ^^;를 인식안함)

이 비트가 세트되어 있다면 ‘|’가 대체오퍼레이터로 되고, 세트되어 있지 않다면
,’\|’가 대체오퍼레이터로 됩니다. 이 비트는 RE_LIMITED_OPS 가 세트되었다면
상관없습니다.

* RE_NO_EMPTY_RANGES (비어있지 않는 범위)

이 비트가 세트되어 있다면, 정규표현식에서 잘못된 범위지정(예:'[z-a]’)
시에는 틀린게 됩니다. 비트가 설정되어 있지 않다면, Regex는 그 범위를 단
지 텅비게 만듭니다.

* RE_UNMATCHED_RIGHT_PAREN_ORD (빠진 오른쪽 괄호)

이 비트가 세트되었고, 정규표현식에서 오픈그룹 오퍼레이터(‘(‘)가 클로즈
그룹 오퍼레이터와 짝이 맞지 않는다면 그냥 넘어가나, 다른 경우네는 ‘)’를
찾게 됩니다.

휴..이제 설명을 다했군요.. 무슨 뜻인지는 짐작이 가실겁니다.

이제 이러한 문법 비트들이 모여 어떻게 표준 응용프로그램마다 조금씩 다르게
적용되는 지 살펴보지요.

2.2 미리 정의된 문법
———————

이번에 살펴볼 것은 “regex.h” 에서 정의된 중요 응용 프로그램의 문법 스타일
을 정의해둔 부분입니다. 여기서 기준이 되는 프로그램은, GNU Emacs, POSIX Awk,
traditional Awk, Grep, Egrep 등이며, POSIX 기본과 확장 정규표현식이 정의됩
니다.

#define RE_SYNTAX_EMACS 0

#define RE_SYNTAX_AWK \
(RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \
| RE_NO_BK_PARENS | RE_NO_BK_REFS \
| RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \
| RE_UNMATCHED_RIGHT_PAREN_ORD)

#define RE_SYNTAX_POSIX_AWK \
(RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS)

#define RE_SYNTAX_GREP \
(RE_BK_PLUS_QM | RE_CHAR_CLASSES \
| RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \
| RE_NEWLINE_ALT)

#define RE_SYNTAX_EGREP \
(RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \
| RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \
| RE_NEWLINE_ALT | RE_NO_BK_PARENS \
| RE_NO_BK_VBAR)

#define RE_SYNTAX_POSIX_EGREP \
(RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES)

/* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */
#define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC

#define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC

/* POSIX 기본문법과 확장문법에서 공통되는 문법 */
#define _RE_SYNTAX_POSIX_COMMON \
(RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \
| RE_INTERVALS | RE_NO_EMPTY_RANGES)

#defineRE_SYNTAX_POSIX_BASIC \
(_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM)

/* Differs from …_POSIX_BASIC only in that RE_BK_PLUS_QM becomes
RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this
isn’t minimal, since other operators, such as \`, aren’t disabled. */
#define RE_SYNTAX_POSIX_MINIMAL_BASIC \
(_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS)

#define RE_SYNTAX_POSIX_EXTENDED \
(_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \
| RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \
| RE_NO_BK_PARENS | RE_NO_BK_VBAR \
| RE_UNMATCHED_RIGHT_PAREN_ORD)

/* Differs from …_POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS
replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */
#define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \
(_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \
| RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \
| RE_NO_BK_PARENS | RE_NO_BK_REFS \
| RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD)

2.3 백슬래쉬 문자
——————

‘\’문자는 4가지의 서로 다른 뜻을 가지고 있습니다. 그 의미는 현재의 문맥과
어떤 문법 비트가 세트되어 있는가에 따라 다릅니다. 그 뜻은 1) 그냥문자, 2)
다음문자를 인용하는 역할, 3) 오퍼레이터를 도입하는 의미, 4) 아무뜻 없음
의 의미중의 하나가 됩니다.

1) 문법 비트가 RE_BACKSLASH_ESCAPE_IN_LISTS 가 세트되지 않은 상태에서 리스
트안에 있을 때는 일반문자가 됩니다. 예를 들어, ‘[\]’는 ‘\’과 매칭이 됩
니다.

2) 아래에 설명하는 두가지 중의 하나로 사용될 때에는 다음 글자를 이스케이프
하게 됩니다. 물론 다음글자가 특수문자이면 일반문자의 의미를 가지게 합니
다.

* 리스트의 밖에 있을 때
* 리스트의 안에 있고 문법비트가 RE_BACKSLASH_ESCAPE_IN_LISTS가 세트되어
있을 때

3) 어떤 특정한 문법비트가 세트되고 확실한 일반문자가 뒤따라 올때 그것은
오퍼레이터를 전개하는 역할을 합니다. 위에서 설명한 RE_BK_PLUS_QM,
RE_NO_BK_BRACES, RE_NO_BK_VAR, RE_NO_BK_PARENS, RE_NO_BK_REF를 참조하세
요.

*’\b’ 는 단어에서의 경계를 짓는 것과 매칭되는 오퍼레이터입니다.
* ‘\B’ 는 단어내부와 매칭되는 오퍼레이터입니다.
* ‘\<‘ 는 단어의 시작과 매칭되는 오퍼레이터입니다.
* ‘\>’ 는 단어의 끝과 매칭되는 오퍼레이터입니다.
* ‘\w’ 는 단어의 구성과 관련되는 오퍼레이터입니다.
* ‘\W’ 는 비단어 구성과 관련되는 오퍼레이터입니다.
* ‘\” 는 버퍼의 시작과 매칭되는 오퍼레이터입니다.
* ‘\” 는 버퍼의 끝과 매칭되는 오퍼레이터입니다.
* Regex가 emacs 심볼로 정의된 상태로 전처리되어 컴파일된다면, ‘\sclass’
는 문법상의 클래스와 매칭되는 오퍼레이터를 나타내고, ‘\Sclass’는
문법상 비 클래스 오퍼레이터를 나타냅니다.

4) 다른 모든 경우에, Regex 는 ‘\’를 무시합니다. 예를 들자면, ‘\n’은 ‘n’
과 매칭됩니다.

( 다음시간에는 우리가 일반적으로 사용하는 지금까지 설명한 오퍼레이터에
대해서 자세하게 알아보겠습니다. )

번 호 : 616
게시자 : 한동훈 (ddoch )
등록일 : 1997-05-26 01:36
제 목 : [강좌] Regex (정규표현식) 라이브러리 (2)

GNU REGEX (정규표현식) 라이브러리 강좌 (2)
——————————————-

3. 공통적인 오퍼레이터
———————–

오퍼레이터라 함은 앞서도 말씀드렸지만 정규표현식에서 사용하는 ‘*’ 나 ‘[‘ 같은
것을 말합니다. 정규표현식을 지원하는 awk, sed, vi, emacs에서 이런 기능을 사
용해보신 분은 얼마나 편리하고 강력한 기능을 제공하는 지 충분히 경험해보셨을
겁니다. 사실 유닉스는 텍스트 처리에서 탁월한 능력을 보여주고 있고, 유닉스의
이런 장점을 따온 리눅스도 마찬가지로 지원을 하는 기능입니다. 따라서, 정규
표현식에 대한 기본적인 지식은 반드시 익혀두시는 것이 좋습니다. 한두군데의
응용프로그램이 아니라 거의 모든 텍스트 처리 프로그램들은 정규표현식을 이용
하는 텍스트 패턴 매칭을 수행하기 때문입니다.

일반적으로 vi에서 다음과 같은 명령을 많이 사용하실 것입니다.
아래와 같은 데이터베이스가 있다고 가정하겠습니다. 여기에서 앞부분의 우편번
호부분만을 문서내에서 삭제하고 싶다고 하면 다음과 같이 간단하게 할 수 있
습니다.

100-011 서울시 중구 충무로1가 02 충무로1가
100-012 서울시 중구 충무로2가 02 충무로2가

:%s/^[0-9]*-[0-9]* //
……………

밑에 ‘..’ 된 부분이 정규표현식이고, 정규표현식은 오퍼레이터의 집합으로 구성
됩니다. 대체로, 오퍼레이터들은 하나만으로 된 것들(예: ‘*’)과 ‘\’다음에 한글
자가 따라오는 형태로 되어 있습니다. 예를 들면, ‘(‘나 ‘\(‘는 오픈그룹 오퍼레
이터입니다. (물론 이것은 문법 비트가 RE_BK_PARENS가 세팅되어 있는 가에 따라
달라집니다.)

대부분의 오퍼레이터는 리스트 (‘[‘, ‘]’)안에서는 그 특수한 의미를 상실합니다.

그럼, 이제 각각의 오퍼레이터들을 하나씩 살펴보도록 하겠습니다.

3.1 자신을 매칭시키는 오퍼레이터 (그냥문자 또는 일반문자)
———————————————————-

이것은 그냥 일반문자를 말합니다. ‘f’는 ‘f’와 매칭되지 ‘ff’와 매칭되지는 않습
니다.

3.2 아무거나한문자 오퍼레이터 (.)
———————————-

‘.’은 아무런 문자 한개와 매칭됩니다. 단, 특수한 경우로 다음과 같은 경우에
해당문자는 매칭될 수 없습니다.

뉴라인문자 : 문법비트가 RE_DOT_NEWLINE이 세팅되어 있지 않을때
널 : 문법비트가 RE_DOT_NOT_NULL 이 세트되어 있을 때

예) ‘a.b’는 ‘acb’, ‘a.b’, ‘azb’등과 매칭됩니다.

3.3 연결 오퍼레이터
——————–

이 오퍼레이터는 두개의 정규표현식, a와 b를 연결합니다. 즉, ‘ab’는 ‘a’다음에
바로 ‘b’가 따라오는 것을 나타내는 것으로, 정규표현식 ‘ab’는 정규표현식 ‘a’
와 ‘b’를 연결한 것입니다. 따라서, 사실 연결 오퍼레이터는 개념적으로만 있을
뿐이지 어떤 형태는 띄고 있지 않습니다. 굳이, 형태를 나타낸다고 하면, ‘ab’중
‘a’와 ‘b’사이의 빈문자(empty character)가 연결 오퍼레이터라고 할 수 있습니다.

3.4 반복 오퍼레이터
——————–

반복 오퍼레이터는 정규표현식 중 어떤 표현식의 형태를 반복적으로 나타내는 데
사용되는 것으로, 일반적으로 ‘*'(뻥개이상매칭), ‘+'(한개이상매칭), ‘?'(뻥개나
한개매칭), ‘{‘, ‘}'(특정한 반복 횟수 지정-간격오퍼레이터)가 있습니다.

3.4.1 뻥개이상 매칭 오퍼레이터 (*) (match-zero-or-more operator)
———————————–

이 연산자는 해당 스트링을 정규표현식으로 매칭시키기 위해 가능한한 가장적은
반복횟수(0를 포함하여)를 선택합니다. 가령, 예를 들면, ‘o*’ 는 “0개 이상으로
구성된 o”를 매칭합니다. ‘fo*’는 ‘fo’의 반복이 아니라 ‘o’의 반복을 나타냅니
다. 따라서, ‘fo*’는 ‘f’, ‘fo’, foo’등과 매칭됩니다. 다음과 같은 경우에는 반
복 오퍼레이터의 역할을 수행하지 않습니다.

* 정규표현식의 처음에 올 경우 (‘*foo’)
* 라인의 시작과 매칭되는 ‘^’나, 오픈그룹 ‘(‘나, 대체 오퍼레이터인 ‘|’ 바로
다음에 위치할 경우 (‘^*’, ‘(*foo)’, ‘foo|*bar’)

위의 경우에 아래의 3가지 다른 일이 일어날 수 있습니다.

* 문법비트가 RE_CONTEXT_INVALID_OPS 가 세트되었다면, 그 정규표현식은 틀린것
으로 취급됩니다.
* RE_CONTEXT_INVALID_OPS 가 세트되지 않았고, RE_CONTEXT_INDEP_OPS가 세트되었
다면, ‘*’는 반복 오퍼레이터 역할을 수행합니다.
* 다른경우는, ‘*’는 그냥문자(일반문자)입니다.

‘*’ 의 작동원리를 예로 들어보겠습니다.

‘ca*ar’ 이라는 정규표현식으로 ‘caaar’ 이라는 문자를 매칭 시킨다고 한다면,
‘ca*ar’ 의 ‘a*’ 는 ‘caaar’의 ‘aaa’를 매칭시킵니다. 그러나 마지막 전자의 ‘ar’
이 후자의 남은 ‘r’을 매칭 시키지 못하기 때문에 이전 ‘a*’ 로 매칭된 ‘aaa’중
마지막 하나를 거꾸로 밟아 ‘a’를 취소함으로써 ‘ar’을 매칭시킵니다.

1) ca*ar => caaar (match)
^^^ ^^^^
2) ca*ar => caaar (not match)
^^ ^
3) ca*ar => caaar (one back cancle)
^^^ ^^^
4) ca*ar => caaar (match)
—^^ —^^

3.4.2 하나이상 오퍼레이터 (+ or \+) (match-one-or-more operator)
———————————–

RE_LIMITED_OPS 로 오퍼레이터 제한을 가하면, Regex 는 이 오퍼레이터를 인식
하지 못합니다. 만일 RE_BK_PLUS_QM 이 세팅되어 있다면, ‘\+’ 가 그 역할을 하고,
아니면 ‘+’ 가 됩니다.

이것은 앞서의 뻥개이상 오퍼레이터 (‘*’)와 적어도 하나는 매칭시킨다는 점을 제
외하고는 같습니다.

가령, ‘+’가 이 오퍼레이터면, ‘ca+r’ 은 ‘car’, ‘caaaar’과 매칭되고, ‘cr’과는
매칭되지 않습니다.

3.4.3 뻥개나 한개 오퍼레이터 (? or \?)
—————————————

이것도 역시 RE_LIMITED_OPS 가 설정되어 있으면, 인식하지 못합니다. 아울러,
RE_BK_PLUS_QM 의 세팅여부에 따라, ‘\?’ 나 ‘?’가 그 역할을 합니다.

이 오퍼레이터는 뻥개이상의 오퍼레이터와 한개나 하나도 매칭시키지 않는다는
점만 제외하면 비슷합니다. 예를 들면, ‘ca?r’은 ‘car’나 ‘cr’을 매칭시키고,
다른 것들은 매칭되지 않습니다.

3.4.4 간격 오퍼레이터 ({…} 또는 \{…\}) (interval operator)
——————————————-

이 오퍼레이터를 사용하면, 특정 패턴의 출현빈도를 지정할 수 있습니다.

RE_INTERVALS 가 세트되어 있다면, Regex는 이것을 인식합니다. 아울러 다른 것과
마찬가지로 가능한한 가장 적은 횟수의 반복과 매칭됩니다.

RE_NO_BK_BRACES 가 세트되었다면, ‘{‘, ‘}’가 오퍼레이터가 되며, 그렇지 않다면,
‘\{‘와 ‘\}’가 오러페이터가 됩니다.

‘{‘ 와 ‘}’ 가 현재의 간격 오퍼레이터라고 했을 경우에, 다음의 뜻은 다음과 같
습니다.

* r{2,5} : 2개에서 5개 사이의 ‘r’
* r{2,} : 2개 이상의 ‘r’
* r{4} : 정확히 4개의 ‘r’

다음의 경우에는 틀린 것이 됩니다.

* 최소한계 갯수가 최대한계 갯수보다 클 경우
* 간격 오퍼레이터 안의 숫자가 RE_DUP_MAX 의 범위를 벗어날 경우

만약, 간격 표현식이 잘못 작성되어 있고, 문법비트가 RE_NO_BK_BRACES 가 세트
되어 있을 경우에는, Regex 는 간격 오퍼레이터 안에 있는 모든 문자는 그냥문자
(일반문자)로 재구성합니다. 이 비트가 세트되어 있지 않다면, 그 정규표현식은
진짜로 틀린 것이 됩니다.

또한, 정규표현식이 유효하긴 한데, 간격 오퍼레이터가 작동할 대상이 없을 경우,
RE_CONTEXT_INVALID_OPS 가 세트되어 있다면, 그 정규표현식은 틀린 것이 됩니다.
비트가 세트되어 있지 않다면, Regex 는 간격 오퍼레이터 안의 모든 문자를 그냥
문자(일반문자)로 재구성하며, 백슬래쉬는 그냥 무시해버립니다.

flex 로 간단히 예를 들어보겠습니다.

……………………………………………………………
queen:~/regex$ echo -e “%%\nx{5} printf(\”only five\\n\”); ” | flex
queen:~/regex$ gcc lex.yy.c -lfl
queen:~/regex$ a.out
xxxxx
only five

^D
queen:~/regex$
……………………………………………………………

3.5 대체 오퍼레이터 (| or \|) (alternation operator)
——————————

RE_LIMITED_OPS 로 오러레이터에 제한을 가한다면, Regex 는 이것을 인식하지 않
습니다. RE_NO_BK_VBAR 가 세트되어 있다면, ‘|’가 이것을 의미하고, 그렇지 않다
면 ‘\|’가 이 오퍼레이터를 나타냅니다.

대체 오퍼레이터는 정규표현식 중의 하나를 매칭시킵니다. ‘foo|bar|quux’는
‘foo’나 ‘bar’ 또는 ‘quux’와 매칭됩니다.

데체 오퍼레이터는 가장 낮은 우선순위를 가지기 때문에, 그룹 오퍼레이터를 사
용하여 괄호를 묶을 수도 있습니다. 예를 들자면, ‘(u|li)n(i|u)x’ 는 ‘linux’,
‘unix’ 등과 매칭됩니다.

3.6 리스트 오퍼레이터 ([…] and [^…])
—————————————–

리스트 오퍼레이터는 하나 이상의 아이템의 집합으로 되어 있습니다. 하나의 아
이템은 문자(예: ‘a’), 문자 클래스 표현식(예: ‘[:digit:]’), 범위 표현식(‘-‘)
이 들어갈 수 있습니다. 리스트안에 어떤 아이템을 취할 수 있는 지는 문법비트
에 영향을 받습니다. 비어있는 리스트 (‘[]’)는 틀린 것이 됩니다.

에를 들면, ‘[ab]’는 ‘a’나 ‘b’를 매칭시키고, ‘[ad]*’는 빈문자열이나, ‘a’나
‘b’가 앞서는 한개이상의 문자열과 매칭됩니다.

이것과는 반대의 의미를 지니는 것이 있습니다. 위의 ‘[..]’가리스트 안의 하나
를 매칭시키는 것이라면 ‘[^…]’는 리스트안의 문자가 아닌 하나의 문자와 매칭
됩니다. ‘^’는 “라인의 처음”이라는 용도로 사용되지만, 리스트의 처음에 오면,
이후의 문자가 아닌 하나의 문자와 매칭시키는 역할을 합니다. 앞서의 예제에서도
살펴보았지만, ‘[^a-zA-Z]’는 알파벳 문자가 아닌 문자와 매칭됩니다. 아울러,
일반적인 경우에, 리스트안에서는 특수문자들이 그 의미를 상실한다고 앞에서 말
씀드렸습니다. 따라서, ‘[.*]’는 보통 ‘.’나 ‘*’ 문자를 매칭시킵니다.

조금의 특수한 경우가 있긴 합니다.

‘]’ : 리스트를 닫는 역할을 합니다. 다만 ‘[‘ 다음에 ‘]’ 가 바로오면 그냥
문자입니다.
‘\’ : RE_BACKSLASH_ESCAPE_IN_LISTS 문법 비트가 세트되었다면 다음문자를
이스케이프 시키는 역할을 합니다.
‘[:’ : RE_CHAR_CLASSES 가 세트되고 그뒤에 문법에 맞는 클래스 이름이 따라
온다면 문자 클래스 오퍼레이터가 됩니다.
‘:]’ : 문자 클래스를 닫는 역할을 합니다.
‘-‘ : 리스트의 처음에 오지 않고 (예: ‘[-.]’), 범위지정에서 끝 포인터에 오지
않는 다면(예: ‘[a–]’) 범위 오퍼레이터의 역할을 합니다.

3.6.1 문자 클래스 오퍼레이터 ([:…:]) (character class operators)
—————————————

이것은, 유사한 성격의 문자들을 사용자가 알아보기 쉽게 단어로 그룹을 지어서
사용하는 것입니다. C 에서의 isdigit, isalpha 등과 같이 구성이 되어 있습니다.

가령, ‘[[:alnum:]]’은 ‘[a-zA-Z0-9]’ 와 같은 의미를 가지지요.
사용할 수 있는 클래스는 다음과 같습니다.

alnum : 알파벳과 숫자
alpha : 알파벳
blank : 스페이스나 탭 (시스템에 의존적임)
cntrl : 아스키코드에서의 127 이상의 문자와 32 이하의 제어문자
(한글의 첫째바이트가 127 이상이므로 제어문자로 취급됨 )
digit : 숫자
graph : 스페이스는 제외되고 나머지는 ‘print’ 항목과 같음.
lower : 소문자
print : 아스키코드에서 32에서 126까지의 찍을 수 있는 문자
punct : 제어문자도 아니고 알파벳.숫자도 아닌 문자
space : 스페이스, 케리지 리턴, 뉴라인, 수직 탭, 폼피드
upper : 대문자
xdigit : 16진수, 0-9, a-f, A-F

클래스 오퍼레이터는 리스트 안에서만 (예: ‘[[:digit:]]’) 효력을 발휘하고,
그냥 ‘[:digit:]’ 와 같이 사용하면 다른 의미를 가지게 됩니다.

3.6.2 범위 오퍼레이터 (-) (range operator)
————————–

범위 오퍼레이터는 리스트 안에서만 작동하며, ‘-‘를 앞뒤로 한 두문자사이의
모든 문자를 의미합니다. 가령, ‘a-f’는 ‘a’에서 ‘f’사이의 모든 문자를 포함
합니다.

주의) 문자 클래스는 범위에서 시작과 끝포인터에 사용될 수 없습니다. 그것은
하나의 문자가 아니라 문자그룹이기 때문에 그렇죠.

잘못된 경우 : ‘[[:digit:]-[:alpha:]]’

이외에, 약간의 특수한 경우가 있습니다.

RE_NO_EMPTY_RANGES가 세트되었고, 범위의 끝 포인터가 시작포인터보다 작다면,
(예: ‘[z-a]’) 그것은 틀린 것이 됩니다. 해당 문법비트가 세트되어 있지 않다
면, 그 범위는 텅 비게 만듭니다. 만일 ‘-‘문자를 원래의 문자의미로 리스트안에
넣을려면, 다음 중 한가지를 따라야 합니다.

* 리스트의 첫부분이나 마지막에 삽입한다.
* 범위의 시작포인터가 ‘-‘보다 작게 하고, 끝포인터를 ‘-‘와 같거나 크게 한다.

에를 들어, ‘[-a-z]’는 소문자나 ‘-‘를 의미합니다.

3.7 그룹화 오퍼레이터 ((…) or \(…\)) (grouping operators)
—————————————–

Regex 에서는 그룹을 하나의 보조 표현식으로 처리합니다. 마치 수학연산에서
‘(a*(b-c)+d)/e’ 와 같이 말입니다. 여기서 바깥쪽 괄호부터 그룹1번, 안쪽 괄
호(‘(b-c)’)가 그룹2번이 됩니다. 즉, 왼쪽에서 오른쪽으로, 바깥쪽에서 안쪽으
로 그룹의 순서가 매겨집니다. 이것은 잠시뒤에 설명할 “거꾸로 참조(후진참조)”
오퍼레이터에 의해 사용됩니다. 사실, 연산식 등에서 괄호가 연속으로 나올경우,
C의 파싱에서도 왼쪽에서부터 괄호를 처리합니다.

따라서, 그룹을 사용하면 다음의 일을 처리할 수 있습니다.

* 대체오퍼레이터 (‘|’)나 반복오퍼레이터 (‘+’나 ‘*’)에서 인자의 범위를 지정
합니다.
* 주어진 그룹과 매칭되는 보조문자열의 인덱스의 자취를 유지합니다.
이 그룹오퍼레이터를 사용하면,
* “거꾸로참조” (back-reference)오퍼레이터를 사용할 수 있습니다.
* 레지스터를 사용할 수 있습니다.

이 부분들은 나중에 자세히 설명하겠습니다.

문법비트가 RE_NO_BK_PARENS 가 세트되어 있다면, ‘(‘와 ‘)’가 그 역할을 하며,
아니면, ‘\(‘와 ‘\)’가 그 역할을 합니다. RE_UNMATCHED_RIGHT_PAREN_ORD 가 세
트되어 있고, ‘(‘는 있는 데 ‘)’가 없다면, ‘)’가 매칭된 것으로 생각하고 넘어
갑니다.

3.8 거꾸로참조 오퍼레이터 (\숫자) (back-reference operator)
———————————-

이 오퍼레이터는 사실, 조금 헷갈리기는 하지만 비슷한 패턴이 여러번 나올경우에
상당한 편의를 제공합니다.

RE_NO_BK_REF 문법 비트가 세팅되어 있지 않다면, 이 오퍼레이터를 인식합니다.
거꾸로참조 오퍼레이터는 이미 기술한 앞의 그룹을 매칭합니다.
정규표현식 중 ‘숫자’ 그룹을 나타내기 위해서는 ‘\숫자’형태로 사용합니다.
숫자는 ‘1’에서 ‘9’까지 가능하며, 이것은 처음의 1에서 9까지의 그룹과 매
칭됩니다.

조금더 세부적인 이야기를 해보겠습니다.

* ‘(a)\1’ 은 ‘aa’와 매칭합니다. ‘\1’은 첫번째 그룹을 나타내며, ‘(a)’로 괄호
로 둘러쌈으로써 그룹을 표시하는 것입니다. 마찬가지로, ‘(bana)na\1bo\1’은
‘bananabanabobana’와 매칭됩니다.

* 조금 복잡한 이야기를 해보겠습니다. 반복 오퍼레이터 등의 작동으로 그룹이
한번 이상 매칭이 될 경우 거꾸로참조 오퍼레이터는 마지막으로 매칭된 보조
문자열을 매칭합니다. 말로만 하면 이해가 안되므로, ‘((a*)b)*\1\2’ 와
‘aabababa’와의 매칭여부를 따져볼까요? 이게 산술연산식이면 얼마나 좋겠
습니까마는 안타깝게도 정규표현식이니만큼 조금 햇갈리더라도 잘 살펴보면 그
리 어렵지만은 않습니다. 괄호의 순서에 따라 그룹은 다음과 같이 대응합니다.

1번 그룹
+——+
……. |
‘((a*)b)*\1\2’
…. |
+——–+
2번 그룹

매칭되는 순서를 살펴봅시다. ‘–‘ 는 매단계에서 서로 매칭되는 부분입니다.

1) ((a*)b)*\1\2 aabababa
——- —
2) ((a*)b)*\1\2 aabababa
– —
3) ((a*)b)*\1\2 aabababa
— —
4) ((a*)b)*\1\2 aabababa
— –

여기서 생각해야 할점은 3)단계의 ‘\1’은 1단계의 ‘((a*)b)’와 매칭되나, 이것은
또한 2)단계의 ‘*’ 반복 오퍼레이터에 의해 ‘*'(ab)와 매칭됩니다. 따라서, 최종
적으로 ‘\1’은 ‘ab’와 매칭됩니다.
물론 위의 표현식은 ‘aababa’와도 매칭이 됩니다.

…………………………………………………………..
queen:~/regex$ echo “aabababa” | egrep “((a*)b)*\1\2”
aabababa
queen:~/regex$ echo “aababa” | egrep “((a*)b)*\1\2”
aababa
…………………………………………………………..

* ‘(one()|two())-and-(three\2|four\3)’ 은 ‘one-and-three’ 와 ‘two-and-four’
와 매칭이 되지, ‘one-and-four’와 ‘two-and-three’와는 매칭이 되지 않습니다.
여기에서, 먼저 ‘one-and-‘ 부분까지 매칭이 되었다고 하면, 두번째 그룹(one
옆의 괄호)은 빈문자열과 매칭이 되었고, 세번째 그룹(two옆의 괄호)는 매칭에
관여하지 않게 됩니다. 그런상황에서 ‘four’가 매칭이 될 경우, Regex 는 그룹
3을 참조하기 위해 거꾸로 돌아갑니다. 그러나 이미 그룹3은 매칭에 관여하지
않기 때문에 전체 매칭은 실패로 돌아갑니다.

거꾸로참조 오퍼레이터를 반복 오퍼레이터의 인자로 쓸수도 있습니다. 예를 들면,
‘(a(b))\2*’는 ‘a’다음에 ‘b’가 하나이상 오는 것과 매칭이 됩니다. 아울러,
‘(a(b))\2{3}’ 은 ‘abbbb’와 매칭이 됩니다.

당연히, n번째의 보조표현이 없다면 매칭은 실패하게 됩니다.
재미있지 않습니까? ^^

3.9 닻 오퍼레이터 (^, $) (anchoring operators)
————————-

닻 오퍼레이터는 전체 문자열이나 하나의 라인에서 시작과 끝을 나타내는 것들입
니다.

3.9.1 라인의 시작 오퍼레이터 (^)
——————————–

이 오퍼레이터는 문자열의 시작이나 뉴라인 문자 다음의 빈문자열와 매칭할 수 있
습니다.

다음의 경우에 ‘^’는 이 오퍼레이터의 역할을 하고, 다른 경우에는 그냥문자가 됩
니다.

* ‘^’ 이 패턴에서 처음에 위치한다. 가령, ‘^foo’ 같은 경우
* 문법비트가 RE_CONTEXT_INDEP_ANCHORS 가 세트되었고, 골호나 그룹..등의 밖에
있을 경우
* 오픈그룹이나 대체 오퍼레이터 다음에 따라올 경우, 예를 들면, ‘a\(^b\)’,
‘a\|^b’

이러한 규칙은 ‘^’ 를 포함하는 유효한 패턴이라고 하더라도 매칭될 수 없다는 것
을 암시합니다. 만약, 패턴 버퍼에서 newline_anchor 필드가 세트되었다면, ‘^’
는 뉴라인 다음과의 매칭에 실패합니다. 이것은 가끔 전체 문자열을 라인으로 나
누어서 처리하지 않을 때에 유용하다고 하는군요.

3.9.2 라인의 끝 오퍼레이터 ($)
——————————-

이 오퍼레이터는 문자열의 끝이나 뉴라인 문자의 이전의 빈 문자열과 매칭됩니다.
이것은 항상 ‘$’로 나타납니다. 예를 들면, ‘foo$’는 ‘foo\nbar’의 처음 세글자
와 매칭이 됩니다.

(다음 시간에는 GNU 오퍼레이터와 GNU emacs 오퍼레이터를 잠깐 살펴보고 재미
있는 Regex 프로그래밍에 들어가겠습니다.)

번 호 : 617
게시자 : 한동훈 (ddoch )
등록일 : 1997-05-26 19:42
제 목 : [강좌] Regex (정규표현식) 라이브러리 (3)

GNU REGEX (정규표현식) 라이브러리 강좌 (3)
——————————————-

4. GNU 오퍼레이터
——————

이 장에서 설명하는 것은 POSIX에는 정의되지 않았으나 GNU 에 의해 정의된 오퍼
레이터입니다.

4.1 워드 오퍼레이터 (word operators)
——————–

여기에 나오는 오퍼레이터는 Regex 가 단어들의 일부분을 인식해야 가능합니다.
Regex 는 어느 문자가 단어의 일부분인지 아닌지를 결정하기 위해 문법 테이블
을 사용합니다.

사실, 텍스트를 처리하거나 관련작업을 하다보면 단어단위로 하여야 할 작업이
많이 있습니다. 하지만 표준 POSIX에서는 단어(워드)단위의 작업에 대해 특별히
지원가능하게 규정된 것이 없습니다. 하지만 GNU 에서는 쓸만한 워드 단위의
작업을 유용하게 처리할 수 있는 다양한 오퍼레이터를 지원함으로써 정규표현식
을 좀더 강력하게 제어할 수 있게 되었습니다. 이런 워드 오퍼레이터는 많이 사
용되고 있지 않지만 활용을 잘 하면 아주 똑똑한 일을 많이 처리할 수 있습니다.

4.1.1 이맥스가 아닌 문법 테이블 (non-emacs syntax tables)
——————————–

문법 테이블은 일반적인 문자세트의 문자들에 의해 인덱스화된 하나의 배열입
니다. Regex 는 항상 이 인덱스 테이블을 사용하기 위해 항상 char * 변수값을
사용합니다. 몇몇 경우에는 이 변수값을 초기화하고 순서대로 여러분들이 초기화
시킬수도 있습니다.

* Regex 가 전처리 심볼 emacs 로 컴파일되었고, SYNTAX_TABLE 이 둘다 정의되
지 않았다면, Regex 는 re_syntax_table 을 할당하고 i가 글자이거나 숫자,
‘_’ 이라면, 원소 i나 SWord를 초기화한다. i가 그렇지 않다면 그 값은 0으로
초기화됩니다.
* Regex 가 정의되지 않은emacs로 컴파일되었으나 SYNTAX_TABLE 이 정의되었다
면 여러분들은 char * 변수 re_syntax_table 을 유효한 문법 테이블(syntax
table)로 정의하여야 합니다.
* Regex가 전처리 심볼 emacs가 정의된 상태에서 컴파일되었다면 어떤 일이 일어
나는 지는 뒤에서 설명합니다.

4.1.2 Match-word-boundary Operator (\b)
—————————————-

‘\b’ 는 단어를 구분짓습니다. 즉, 이것은 단어의 시작과 끝의 빈 문자열과 매칭
이 됩니다. 예를 들면, ‘\brat\b’는 분리된 낱말, ‘rat’을 매칭시킵니다.
그러나,단어의 범위를 어떻게 규정하는가 하는 것은 몇가지 예제로 충분히 유추
할 수 있을 것입니다.

이 강좌의 처음에 든 예를, 이 오퍼레이터를 사용하면 더 간단합니다.

…………………………………………………………
grep “\bint\b” regex.c

mcnt = (int) Sword;
int mcnt;
………
queen:~/regex$
…………………………………………………………

위의 예를 살펴볼 때, 단어는 “공백문자(화이트문자)나 부호문자(‘(‘, ‘]’, ‘-‘,
..) 가 끼어들지 않는 문자의 연속된 집합” 정도로 생각할 수 있습니다.

4.1.3 Match-within-word Operator (\B)
—————————————-

‘B’ 는 낱말안에서의 빈문자열과 매칭합니다. 예를 들면, ‘c\Brat\Be’ 는
‘create’ 와 매칭하고, ‘dirty \Brat’은 ‘dirty rat’과 매칭하지 않습니다.

4.1.4 Match-beginning-of-word Operator (\<)
——————————————-

‘\<‘ 는 단어의 시작에서 빈문자열을 매칭합니다.

4.1.5 Match-end-of-word Operator (\>)
—————————————-

‘\>’ 는 단어의 끝에서 빈문자열과 매칭합니다.

………………………………………………………..
queen:~/regex$ grep “\<char\>” regex.c
return (char *) re_error_msg[(int) ret];
const char *s;
….
queen:~/regex$
………………………………………………………..

4.1.6 Match-word-constituent Operator (\w)
——————————————

‘\w’ 는 낱말을 이루는 어떤 문자와 매칭합니다.

4.1.7 Match-non-word-constituent Operator(\W)
———————————————

‘\W’ 는 낱말의 성분요소가 아닌 어떤 문자와 매칭합니다.

…………………………………………………………
queen:~/regex$ echo ” int ” | grep “\Wi\wt”
int
queen:~/regex$
…………………………………………………………

‘\w’ 과 ‘.’의 차이점은 전자는 낱말속의 어느 한문자(그러므로 낱말의 구성요소)
와 매칭이 되나, ‘.’는 이것저것 따지지 않고 어느 한문자와 매칭이 되므로 조금
의미적으로 틀립니다. 아울러, ‘\W’도 낱말속의 어떤 문자 (예를 들면, ‘int’속의
‘n’)과는 매칭이 되지 않으며 낱말에 포함되지 않는 어떤 한문자 (예를 들면, ‘ ‘)
와 매칭이 됩니다.

4.2 버퍼 오퍼레이터
——————–

이제 설명할 것은 버퍼에서 작동하는 오퍼레이터입니다. 이맥스에서의 buffer는
“이맥스 buffer” 입니다. 다른 프로그램에서는 전체 문자열을 버퍼로 여깁니다.

4.2.1 Match-beginning-of-buffer Operator (\`)
———————————————-

‘\`’는 버퍼의 시작되는 부분의 빈문자열과 매칭됩니다.

4.2.2 Match-end-of-buffer Operator (\’)
—————————————-

‘\”는 버퍼의 끝 부분의 빈문자열과 매칭됩니다.

5. GNU 이맥스 오퍼레이터
————————-

이제 설명할 것은 POSIX에서는 정의되지 않았고, GNU에서 정의되었으며, 이것을 사
용할 때는 Regex 가 컴파일 될 때 전처리 심볼을 정의된 emacs로 하여야 합니다.

5.1 문법 클래스 오퍼레이터 (syntactic class operators)
—————————

이 오퍼레이터들은 Regex 가 이 문법 문자들의 클래스를 인식하여야 합니다.
Regex 는 이것을 검사하기 위해 문법 테이블을 사용합니다.

5.1.1 이맥스 문법 테이블
————————

하나의 문법 테이블은 여러분들의 문자셋(아스키문자셋 같은 것들..)에 의해 인덱
스화된 하나의 배열입니다. 아스키 하에서는 따라서 문법 테이블은 256개의 원소
를 가집니다.

Regex 가 전처리 심볼, 정의된 emacs 로 컴파일되었다면, 여러분들은
re_syntax_table 을 정의하고 그 값을 이맥스 문법 테이블로 초기화하여야 합니다.
이맥스 문법 테이블은 Regex 의 문법 테이블보다는 좀 더 복잡합니다.

5.1.2 Match-syntactic-class Operator (\sclass)
———————————————–

이 오퍼레이터는 문법 클래스가, 서술된 문자가 명시하는, 어떤 문자를 매칭
합니다. ‘\sclass’가 이 오퍼레이터를 나타내며, class는 여러분들이 원하는 문법
클래스를 나타내는 문자입니다. 예를 들여, ‘w’ 는 단어를 구성하는 문자의 문법
글래스를 나타내므로, ‘\sw’은 단어를 구성하는 아무 문자와 매칭합니다.

5.1.3 Match-not-syntactic-class Operator (\Sclass)
—————————————————

위의 오퍼레이터와는 반대되는 뜻입니다. 예를 들어, ‘w’ 는 단어를 구성하는 문자
의 문법 클래스를 나타내므로, ‘\Sw’ 은 단어의 구성성분이 아닌 아무 문자와 매
칭됩니다.

지겹게 지금까지 많을 것을 설명드렸지만, 사실 이 모든 것을 다 한꺼번에 기억하
실 필요성은 없습니다. 자주 사용하시면서 그때그때 마다 조금씩 익숙하게 익히
시는 것이 좋으리라 봅니다.

이제, 조금 더 재미있는 Regex 프로그래밍에 들어가겠습니다.

6. Regex 프로그래밍
——————–

Regex 는 세가지 다른 인터페이스가 있습니다. 하나는 GNU를 위해 디자인 된 것과,
하나는 POSIX 에 호환되는 것, 나머지 하나는 Berkeley UNIX 에 호환되는 것입
니다.
다른 유닉스 버젼에도 충분히 호환되는 것으로 프로그래밍을 하시려면, POSIX
Regex 함수로 프로그래밍하시는 것이 좋을 겁니다. 그렇지 않고 일반적으로, GNU
의 강력한 기능을 사용하시려면 GNU Regex 함수를 사용하시는 것이 좋을 것 입니
다.

그럼, 먼저 비교적 간단한 BSD Regex 함수부터 살펴보겠습니다.

6.1 BSD Regex 함수
——————-

Berkeley UNIX 에 호환되는 코드를 작성하려면, 이 함수를 사용하십시요.
그러나, 그다지 많은 기능은 지원되지 않고, 간단한 두개의 함수만이 지원됩니다.
따라서, BSD Regex 함수로는 간단한 검색은 할 수 있으나, 매칭작업은 할 수 없습
니다.

BSD Regex 함수로 검색을 하기위해서는 다음의 순서를 따라야 합니다.

1) re_syntax_options 의 값을 원하는 정규표현식 문법비트의 값으로 설정합니다.
앞에서 설명이 된, 각종의 문법 비트를 조합하여 설정할 수 있습니다.

예) re_syntax_options = RE_SYNTAX_POSIX_BASIC;

2) 정규표현식을 컴파일 합니다.

char *re_comp (char *regex)

regex 는 널로 끝나는 정규표현식의 주소입니다. re_comp 는 내부적으로 패턴
버퍼를 사용하기 때문에 사용자에게는 노출이 되지 않기 때문에, 새로운 정규
표현식으로 검색하려면, 해당 정규표현식을 재 컴파일하여야 합니다. 즉, 내부
의 패턴버퍼를 현재의 정규표현식과 맞추어 주어야 한다는 것입니다. 만일
regex 를 NULL스트링 으로 컴파일 할경우에는 내부의 패턴버퍼가 변하지 않으니
주의를 하여야 합니다.

re_comp 는 성공적으로 컴파일되었다면, NULL을 돌려주며, 정규표현식이 잘못
되거나 문제가 생겨서 컴파일 할 수 없다면 에러 문자열을 돌려줍니다.
이 에러 문자열은 뒤에 나올 re_compile_pattern 의 그것과 같습니다.

3) 검색작업을 합니다.

int re_exec (char *string)

한번 re_comp 로 정규표현식을 컴파일 하였다면, 이제 re_exec 를 사용하여
string 문자열내에서 해당 표현이 나오는 지를 검색할 수 있습니다.

re_exec 는 검색에 성공했을 경우에 1을 리턴하고, 실패했을 경우에는 0을 리턴
합니다. 이 함수는 내부적으로 빠른 검색을 위해 GNU fastmap 을 사용합니다.

자, 그럼 이제 간단한 예제를 하나 만들어 보도록 합시다. 위의 함수를 사용하여
간단한 패턴 검색을 테스트 하는 것입니다.

—————————————————————————

/* BSD Regex functions example

Usage : bsd search_string pattern
*/

#include <stdio.h>
#include <stdlib.h>
#include “regex.h”

void main(int argc, char *argv[]) {
char *error;
re_syntax_options = RE_SYNTAX_POSIX_BASIC;

if (argc != 3) exit(1);
if ((error = re_comp(argv[2])) != NULL) {
fprintf(stderr, “re_comp: %s: %s\n”, argv[2], error);
exit(1);
}
switch(re_exec(argv[1])) {
case 0 :
fprintf(stderr, “re_exec: \”%s\” failure..\n”, argv[1]);
break;
case 1 :
fprintf(stderr, “re_exec: \”%s\” success..\n”, argv[1]);
break;
}
}

————————————————————————–

먼저, 현재 여러분들이 테스트 하시는 디렉토리에 “regex.c” 와 “regex.h” 를 한
부 복사해 두시고, regex.c 를 컴파일만 하여 오브젝트 파일을 만들거나 이미 컴
파일된 regex.o 를 한부 가지고 옵니다. 컴파일 할 경우,

queen:~/regex$ gcc -c regex.c -g

위의 소스를 bsd.c 로 저장을 한다면, 이제 다음과 같이 컴파일 하면 됩니다.

queen:~/regex$ gcc -o bsd bsd.c regex.o

다음은 테스트 결과입니다.

………………………………………………………………
queen:~/regex$ bsd “lnx5, 2445 #linux” “[[:digit:]]\{4\}\W.li\w\wx”
re_exec: “lnx5, 2445 #linux” success..
queen:~/regex$ bsd “printf (\”int i = 10\”)” “\<int\b”
re_exec: “printf (“int i = 10″)” success..
queen:~/regex$ bsd “regex is powerful” “\b\w*\W[is”
re_comp: \b\w*\W[is: Unmatched[ or [^
queen:~/regex$
………………………………………………………………

다음 시간에는 POSIX Regex 함수를 살펴보겠습니다.

현재 할일이 밀려서 이번 시간은 조금 줄이도록 하겠습니다.

또치 한동훈 드림

번 호 : 619
게시자 : 한동훈 (ddoch )
등록일 : 1997-06-02 20:14
제 목 : [강좌] Regex (정규표현식) 라이브러리 (4)

GNU REGEX (정규표현식) 프로그래밍 강좌 (4)
——————————————-

6.2 POSIX Regex 함수
———————

POSIX 와 호환되는 코드를 작성하려면 여기에 나오는 함수들을 사용할 수 있습
니다.

6.2.1 POSIX 패턴 버퍼
———————-

POSIX 에서 정규표현식을 컴파일하거나 매칭작업을 하려면, BSD 와는 다르게
패턴 버퍼를 제공하여야 합니다. regex_t 타입인 POSIX 패턴 버퍼는,
re_pattern_buffer 타입인 GNU 패턴버퍼와 구성이 동일 합니다.

“regex.h” 에 보면 다음과 같이 형정의되어 있습니다.

typedef struct re_pattern_buffer regex_t;

패턴 버퍼란 이전에도 말씀드렸지만, 해당 정규표현식에서 패턴을 매칭시키기 위한
다양한 정보를 가지고 있는 버퍼입니다. 이것은 물론, 컴파일을 함으로써 사용가능
하게 됩니다.

그럼, 먼저 GNU 패턴 버퍼를 살펴볼까요?

여러분들은 서로 다른 여러종류의 패턴 버퍼를 동시에 보유할 수 있습니다.
“regex.h” 는 아래와 같은 패턴 버퍼를 정의하고 있습니다.

/* 컴파일된 패턴을 가르키는 포인터. 이것의 원소는 배열의 인덱스로
사용될 때가 있기 때문에 ‘unsigned char *’로 정의되었습니다. */
unsigned char *buffer;

/* ‘buffer’ 가 포인트하는 바이트수 */
unsigned long allocated;

/* ‘buffer’ 안에 사용되고 있는 바이트수 */
unsigned long used;

/* 패턴이 컴파일될 때 세팅되는 문법 */
reg_syntax_t syntax;

/* 어떤 fastmap 을 가르키는 포인터. NULL 이라면 포인팅 하지 않는 경우입니
다. re_search 는, 만일 fastmap 이 존재할 경우, 빠른 매칭을 위해서, 매칭
이 불가능한 출발 포인트는 건너 뛰게 됩니다. */
char *fastmap;

/* NULL 이 아니라면, 어떤 문자들을 비교하기 전에, 모든 문자들에 적용되는
변환테이블입니다. NULL 일 경우에는 변환이 없습니다. */
char *translate;

/* (정규표현식) 컴파일러에 의해 발견된 보조표현식의 수 */
size_t re_nsub;

/* 현재의 패턴이 빈문자열과 매칭할 수 없다면 0이 되고, 그외는 1이 됩니다.
이것은 ‘re_search_2’ 에서만 사용됩니다. */
unsigned can_be_null : 1;

/* REGS_UNALLOCATED : ‘regs’ 구조체에 RE_NREGS 나 re_nsub + 1 중 큰수
수 만큼 그룹을 할당합니다.
REGS_REALLOCATE : 필요하다면 공간을 재 할당합니다.
REGS_FIXED : 그냥 있는 것을 사용합니다. */
#define REGS_UNALLOCATED 0
#define REGS_REALLOCATE 1
#define REGS_FIXED 2
unsigned regs_allocated : 2;

/* 패턴을 ‘regex_compile’ 로 컴파일 할 때 0으로 세팅됩니다.
‘re_compile_fastmap’이 fastmap 을 업데이트 할 경우에는 1로 세팅됩니다. */
unsigned fastmap_accurate : 1;

/* 이것이 세트되어 있다면, ‘re_match_2’ 는 보조표현식에 관한 정보를 리턴하
지 않습니다. */
unsigned no_sub : 1;

/* 이것이 세트되어 있다면, 라인의 시작을 나타내는 표시기(일반적으로는 ‘^’)
는 문자열의 시작을 매칭하지 못합니다. */
unsigned not_bol : 1;

/* 이것은 라인의 끝을 나타내는 표시기(일반적으로는 ‘$’)와 유사합니다. */
unsigned not_eol : 1;

/* 이것이 세트되면, 뉴라인에서 표시기가 매칭됩니다. */
unsigned newline_anchor : 1;

사실, 이 가운데에서 자주 사용하는 것은 몇개 정도에 지나지 않을 것입니다.

6.2.2 POSIX 정규표현식 컴파일
——————————

패턴 버퍼를 컴파일하려면 ‘regcomp’ 를 사용합니다.

int regcomp (regex_t *preg, const char *regex, int cflags)

‘preg’ 는 초기화할 패턴 버퍼의 주소입니다. ‘regex’ 는 정규표현식의 주소입
니다. 그리고 cflags 는 조합가능한 컴파일 플래그입니다. 유효한 비트는 다음
과 같습니다.

REG_EXTENDED
POSIX 확장 정규표현식을 사용하겠다는 것을 의미합니다. 이것이 세트되어
있지 않다면 POSIX 기본 정규표현식을 사용하겠다는 것을 의미합니다.
regcomp 는 ‘preg’의 syntax 필드를 그에 알맞게 설정합니다.

REG_ICASE
대소문자를 무시한다는 것을 의미합니다. regcomp 는 ‘preg’ 의 ‘translate’
필드를 대소문자를 무시하는 변환데이블로 설정합니다.

REG_NOSUB
‘preg’ 의 ‘no_sub’ 필드를 세트하라는 의미입니다.

REG_NEWLINE
* match-any-character operator (‘.’)는 newline 을 매칭하지 못합니다.
* nonmatching list (‘[^…]’)는 newline 을 포함하지 못합니다.
* match-beginning-of-line (‘^’) 는 REG_NOTBOL 이 어떻게 설정되어 있는가
에 개의치 않고 newline 바로 뒤의 빈문쟈열을 매칭합니다.
* match-end-of-line operator (‘$’) 는 REG_NOTEOL 이 어떻게 설정되어 있는
가에 개의치 않고 newline 바로 이전에 오는 빈문자열을 매칭합니다.

regcomp 가 성공적으로 정규표현식을 컴파일하게 되면, 0을 리턴하고,
‘*pattern_buffer’ 를 컴파일된 패턴으로 설정합니다. syntax 를 제외하고는,
이후에 살펴볼 GNU 컴파일 함수와 같은 방법으로 같은 필드를 설정합니다.

regcomp 가 컴파일에 실패하게 되면, 아래의 에러코드 중 하나를 반환합니다.

REG_BADRPT
예를 들면, ‘a**’ 안의 연속적인 반복 연산자 ‘**’ 의 경우

REG_BADBR
예를 들면, ‘a\{-1’ 에서의 count ‘-1’ 같은 경우

REG_EBRACE
예를 들면, ‘a\{1’ 과 같이 ‘}’ 가 빠진 경우

REG_EBRACK
예를 들면, ‘[a’ 와 같이 ‘]’ 가 빠진 경우

REG_ERANGE
예를 들면, ‘[z-a]’ 나 ‘[[:alpha:]-|]’ 과 같이 잘못된 경우

REG_ECTYPE
예를 들면, ‘[[:foo:]’ 와 같이 잘못된 클래스 명칭인 경우

REG_EPAREN
예를 들면, ‘a\)’ 와 같이 ‘(‘ 를 빠뜨렸을 경우

REG_ESUBREG
예를 들면, ‘\(a\)\2’ 와 같이 존재하지 않는 그룹을 참조하는 경우

REG_EEND
예를 들면, 정규표현식이 더 이상의 명백한 에러를 야기하지 않을 경우

REG_EESCAPE
예를 들면, ‘a\’ 에서와 같이 ‘\’ 가 잘못 사용되었을 경우

REG_BADPAT
예를 들면, 확장 정규표현식 문법에서 ‘a()b’ 에서의 빈그룹 ‘()’ 이 나올 경우

REG_ESIZE
정규표현식이 패턴 버퍼의 크기로 65536 보다 큰 바이트를 필요로 할 경우

REG_ESPACE
정규표현식이 Regex 가 실행하는 데에 필요한 메모리를 모자라게 할 경우

6.2.3 POSIX 매칭
——————

한번, 패턴을 패턴버퍼로 컴파일을 했다면, 이제 매칭작업을 할 수 있습니다.
이 매칭작업을 ‘regexec’ 가 수행을 합니다.

int regexec (const regex_t *preg, const char *string,
size_t nmatch, regmatch_t pmatch[], int eflags)

‘preg’ 는 패턴을 컴파일한 패턴 버퍼의 주소이고, ‘string’ 은 매칭을 하기를
원하는 문자열입니다. ‘pmatch’ 에 대해서는 뒤에서 자세하게 설명이 됩니다.
‘nmatch’ 를 0으로 설정하거나, ‘preg’ 를 컴파일 옵션 REG_NOSUB 로 세팅하였다
면 ‘regexec’ 는 ‘pmatch’ 를 무시할 것입니다. 그렇지 않으면, 여러분들은 적
어도 ‘nmatch’ 원소들 만큼 할당해야 합니다. regexec 는 ‘nmatch’ 바이트 옵셋을
‘pmatch’ 에 기록을 할것이며, 사용되지 않는 원소를 -1부터 ‘pmatch[nmatch]-1’
까지 설정할 것입니다.

‘eflags’ 는 실행 플래그를 설정하며, REG_NOTBOL 과 REG_NOTEOL 이 될 수 있습
니다. REG_NOTBOL 을 설정한다면, match-beginning-of-line operator (‘^’) 는
항상 매칭에 실패를 합니다. REG_NOTEOL 은 match-end-of-line operator 에 있어
서 위와 유사하게 작동합니다.

regexec 는 컴파일된 패턴이 ‘string’ 과 매칭이 되었다면 0을, 그렇지 않다면,
REG_NOMATCH 를 리턴합니다.

6.2.4 에러 메시지 출력하기
—————————

regcomp 나 regexec 가 실패하게 되면, 0이 아닌 에러코드를 반환합니다. 이러한
에러코드들은 위의 6.2.2 와 6.2.3 에서 설명한 것들입니다. 에러코드에 해당하는
에러 문자열을 얻으려면 ‘regerror’를 사용할 수 있습니다.

size_t regerror (int errcode,
const regex_t *preg,
char *errbuf,
size_t errbuf_size)

‘errcode’ 는 에러코드이고, ‘preg’ 는 에러가 발생한 패턴버퍼이며, ‘errbuf’ 는
에러 버퍼이며, ‘errbuf_size’ 는 ‘errbuf’ 의 크기입니다.

regerror 는 ‘errcode’ 에 대응하는 에러 문자열의 바이트 크기(널문자까지 포함)
를 반환합니다. ‘errbuf’ 와 ‘errbuf_size’ 가 0이 아니라면, ‘errbuf’ 에 처음
errbuf_size-1 문자의 에러 문자열을 널문자를 추가해서 돌려줍니다.
‘errbuf_size’ 는 ‘errbuf’ 의 바이트 크기보다 작거나 같은 양수이어야 합니다.
여러분들은, ‘regerror’ 의 에러 문자열을 담아내는 데 얼마만큼 크기의’errbuf’
가 필요한지 알아보기 위해서 ‘errbuf’ 를 NULL로, ‘errbuf_size’ 를 0으로 해서
호출할 수 있습니다.

6.2.5 바이트 옵셋 사용하기
—————————

POSIX 에서, regmatch_t 형 변수는 GNU 의 레지스터와 비슷하지만, 똑같지는 않
습니다. POSIX 에서 레지스터의 정보를 얻으려면 regexec 에, regmatch 형 변수인,
0이 아닌 ‘pmatch’를 넘겨줄 수 있습니다. regmatch_t 형 구조체는 다음과 같습
니다.

typedef struct {
regoff_t rm_so;
regoff_t rm_eo;
} regmatch_t;

매칭 함수가 정보를어떻게 레지스터에 저장하는 지는 뒷부분에서 설명하겠습니다.

GNU Regex 의 ‘regs’ 와 POSIX 의 ‘regs’ 는 유사하게 대응합니다.

‘reg’ 의 ‘pmatch’, pmatch[i]->rm_so 는 regs->start[i] 와 대응하고
pmatch[i]->rm_eo 는 regs->end[i] 와 대응합니다.

6.2.6 POSIX 패턴 버퍼를 Free 하기
———————————-

패턴 버퍼에 할당된 것을 free 하는 함수는 ‘regfree’ 입니다.

void regfree (regex_t *preg)

‘preg’ 는 free 할, 할당된 패턴버퍼입니다. regfree 는 또한 ‘preg’의 ‘allocated’
와 ‘used’ 필드를 0으로 설정합니다. 패턴 버퍼를 free 한 이후에는, 매칭 작업을
수행하기 전에 정규표현식을 해당 패턴 버퍼에 다시 컴파일해야 합니다.

6.2.7 POSIX Regex 로 egrep 만들기
———————————-

grep 은 기본 정규표현식을 사용하고, egrep 은 확장 정규표현식을 사용하는데,
여기서는 egrep 의 기능을 간단하게 구현해 보도록 하겠습니다.

지금까지 설명한 기능만으로도 egrep 의 기본적인 기능은 쉽게 만들 수 있습니다.
grep 류의 기본적인 기능은 ‘매칭’ 이 아니라 ‘검색’이기 때문입니다.

우리가 만들’egrep’ 을 ‘my_egrep’ 이라고 부른다면, ‘my_egrep’ 의 기본적인
작동은 다음과 같이 하도록 합시다.

1) 특별한 옵션은 지원하지 않고, 인자는 모두 패턴이나 파일명으로 처리한다.
2) 입력파일명이 명시되지 않았을 경우에는 표준입력에서 받는다.
3) 컴파일 플래그는 ‘REG_EXTENDED’ 를 사용하여 확장정규표현식을 지원한다.

추가적인 옵션을 지원하는 것은 소스를 조금씩 고치면서 시도해 보시기 바랍니다.

—————————————————————————-

/* POSIX Regex 테스트 프로그램 : egrep 의 기본 기능 구현
*
* Designed by Han-donghun, 1997.5.31
*
* name : my_egrep.c
*
* compile : First, you must have “regex.c” and “regex.h”,
* in the current directory.
*
* To get “regex.o ” , type “gcc -c regex.c”
* Finally, to compile my_egrep.c, type follow.
*
* “gcc -o my_egrep my_egrep.c regex.o”
*
* usage : my_egrep pattern [files…]
*
* This is simple “pattern search” program
* using POSIX regex, like egrep.
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <errno.h>
#include “regex.h” /* regex main header file */

void main(int argc, char *argv[]) {
int ret = 0, error, i;
char *msg;
char buf[2048];
FILE *fp;
regex_t preg;

if (argc <= 1) {
fprintf(stderr, “usage: %s pattern [files..]\n”, argv[0]);
exit(1);
}

/* regex compile */
if ((error = regcomp(&preg, argv[1],
REG_EXTENDED | REG_NOSUB)) != 0) {
ret = regerror(error, &preg, NULL, 0);
msg = (char *)malloc(sizeof(char)*ret);
regerror(error, &preg, msg, ret);
fprintf(stderr, “%s: %s\n”, argv[0], msg);
free(msg);
exit (1);
}

if (argc == 2) {
while (fgets(buf, 2048, stdin) != NULL) {
/* regex matching */
if ((regexec(&preg, buf, 0, NULL, 0)) == 0) {
printf(“%s”, buf);
}
}
} else if (argc > 2) {
for (i = 2; i < argc ; i++) {
if ((fp = fopen(argv[i], “r”)) == NULL) {
fprintf(stderr, “%s: %s: %s\n”, argv[0], argv[i], strerror(errno));
continue;
}
while (fgets(buf, 2048, fp) != NULL) {
/* regex matching */
if ((regexec(&preg, buf, 0, NULL, 0)) == 0) {
printf(“%s”, buf);
}
}
}
}
regfree(&preg);
}

—————————————————————————

대소문자를 무시하게 만들려면, 정규표현식의 컴파일시에, regcomp 의
REG_EXTENDED 에 REG_ICASE 를 추가하시면 됩니다 (grep 류의 ‘-i’ 옵션).
grep 류의 ‘-v’ 나 -n’ 옵션을 지원하는 것은 이제 간단하게 해결될 것입니다.

다음은 테스트 한 결과입니다.

—————————————————————————

$ gcc -o my_egrep my_egrep.c regex.o
$ my_egrep regcomp 정규표현식강좌.네번째
패턴 버퍼를 컴파일하려면 ‘regcomp’ 를 사용합니다.
int regcomp (regex_t *preg, const char *regex, int cflags)
…………….
$ my_egrep “^[0-9]+\.[0-9]+\b” 정규표현식강좌.네번째
6.2 POSIX Regex 함수
6.2.1 POSIX 패턴 버퍼
6.2.2 POSIX 정규표현식 컴파일
6.2.3 POSIX 매칭
6.2.4 에러 메시지 출력하기
6.2.5 바이트 옵셋 사용하기
6.2.6 POSIX 패턴 버퍼를 Free 하기
6.2.7 POSIX Regex 로 egrep 만들기
$
—————————————————————————-

(다음 시간에 이어집니다..)

번 호 : 620
게시자 : 한동훈 (ddoch )
등록일 : 1997-06-02 20:15
제 목 : [강좌] DB를 편하게, gdbm (1)

GNU gdbm (GNU DataBase Manager) 프로그래밍 강좌 (1)
—————————————————-

글쓴이 : 한동훈 ddoch at hitel.kol.co.kr
날 짜 : 1997.5.30
저작권 : 상업적인 용도가 아닌한 어디로든 이동 및 게제 가능
부탁사항 : 질문과 관련된 내용이나 답변을 요하는 내용은 메일로 적어주지
마시고 관련 프로그래밍 게시판을 이용해 주시면 성의껏 답변해
드리겠습니다. 제가 상당히 게으른 관계로 질문메일에 제대로 답
변을 못해드리고 있는 점 죄송합니다.

▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧
▧ 목 차 ▧
▧ ▧
▧ 1. 들어가는 말 ▧
▧ 2. 도입과 전개 ▧
▧ 3. 여러 함수들 ▧
▧ 4. 그외의 다른 것 ▧
▧ 5. 프로그램을 한번 짜봅시다 ▧
▧ ▧
▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧ ▧

1. 들어가는 말

안녕하세요. ddoch 한동훈입니다.

리눅스를 사용하시면서, 데이터 처리를 많이 하실 것입니다. 예전에 DOS에서 돌아
가던 dBASE 같은 것을 사용하시던 기억이 어렴풋이 떠오르시는 분들도 계실겁니다.
많은 분들 중에서 리눅스에서 쓸만한 데이터베이스가 없나하고 찾으시는 분들도 많
이 계시더군요.

‘디비입문’… 글쎄요..
적당한 입문책을 하나 구하셔서 보셔도 좋을 것입니다. DB에 대해 전문프로그래
머가 아닌 일반 프로그래머나 사용자라면 굳이 그럴 필요까지는 없을 겁니다.

잘 아시겠지만, 리눅스에서 돌아가는 관계형 데이터베이스 시스템(RDBMS, 줄여서
‘RDB’ 라고도 하죠)으로는 많은 것들이 있습니다. 대표적인 것으로는 공개용인
postgreSQL이 있으며, 공개용은 아닌 mSQL, 그외에 다른 툴들이 많이 있습니다.
그외에도 현재 많은 상당히 훌륭한 기능들을 가진 RDBMS들이 나오고 있는 상태
입니다. 이러한 것들은 최종사용자에게도 훌륭한 DB 매니져가 되고, 프로그래머
에게도 활용할 수 있는 다양한 기능들을 제공합니다.

기회가 있으면, postgreSQL 같은 것은 다음기회에 소개하기로 하고,
이번 기회에 말씀드릴 것은, 프로그래머에게 요긴한 DB 관리 툴입니다.

“프로그램 작성중 DB관련 부분을 어떻게 처리하십니까?” 라는 질문에, RDBMS와 연
동하여 처리하시는 분들도 계실 것이고 모든 루틴을 내부적으로 만들어서 사용하
시는 분, C 나 C++로 짜여진 DB 처리툴을 사용하시는 분들도 있을 것입니다.
저도 사실 RDBMS 나 DB 처리툴들은 잘 사용하지 않았습니다. 사용하면 좋을 텐데,
단지 귀찮다는 하나의 이유만으로 내부적으로 펑션을 만들어서 처리를 많이 해
왔던 것 같습니다.

하지만, 프로그램이 대형화 되고, 중요한 DB를 다룬다던지, 또는 퍼포먼스 향상,
다중 프로세스 환경에서의 여러문제 들을 해결하고 싶다면 DB 처리전문 툴이나,
RDBMS 를 사용하시길 바랍니다.

gdbm 은 내부적으로 DB를 전문적으로 처리하는 여러 함수로 구성된 라이브러리입
니다. GNU ‘dbm’은 Philip A. Nelson 씨에 의해 쓰여졌습니다. 사실 GNU dbm은
다른 표준 UNIX dbm 인 ‘dbm’ 이나 ‘ndbm’과 호환성을 유지하기 위해서 내부에 따
로 정의된 헤더파일과 라이브러리를 가지고 있습니다.

이 소프트웨어들은 하이텔 리눅스동자료실(‘gdbm173.tgz’)이나 sunsite.unc.edu
의 “/pub/Linux/libs/db/” 에서 찾을 수 있습니다. 현재 쉽게 구할 수 있는 gdbm
의 버젼은 1.7.3 이며, 소스를 구하셔서 설치를 하시기 바랍니다. 물론, 공유라이
브러리와 헤더파일만으로 된 바이너리 배포판으로 구해서 설치해도 gdbm 을 사용하
는 데는 지장이 없지만 혹시라도 모를 ‘dbm’ 이나 ‘ndbm’ 과의 호환성을 유지하기
위한 라이브러리 및 헤더파일은 빠져 있으니 주의하시기 바랍니다. gdbm 은 1.7.1
의 메뉴얼을 내부에 info 파일형식으로 포함하고 있습니다. 그외에 버클리의 ‘db’
도 있긴 하지만, 버클리의 db 보다는 GPL을 따르고 있는 gdbm을 사용하시기를 권장
합니다.

‘gdbm’ 을 사용하면 어떤 것이 좋을까요?

1) 데이터 파일의 삭제, 갱신, 편집 등등의 관리에 직접 신경을 쓰지 않아도
됩니다. 이 작업은 gdbm 이 최적화된 상태를 유지하면서 에러상태와 여러
가지의 경우에 gdbm 이 내부적으로 대응하니 신경 쓸 필요는 없습니다.

2) 데이터 검색에 최선의 알고리즘을 사용하므로 속도면이나 퍼포먼스 면에서
상당한 효과를 가져올 수 있습니다. DB 를 일일이 하위 레이어까지 손으로
짤 경우에 발생하는 각종 고효율 알고리즘을 구현하기 위해서 머리를 썩일
필요성이 없다는 것입니다. 이 작업은 gdbm 이 최선의 효율성을 유지하면서
해쉬테이블을 통해 대신합니다.

3) DB 구축과 운용에 노력이 훨씬 적게 들어갑니다.
gdbm 에 적절한 데이터를 넘겨줌으로써 DB 구축은 끝이 나므로 나머지 노력
을 다른 곳에 쏟을 수 있습니다.
물론, RDBMS 의 장점인 복잡한 관계를 처리하거나 융통성을 발휘하지는 못
하지만 그만큼 사용이나 운용이 용이하다는 장점이 있습니다.

4) ‘dbm’, ‘ndbm’ 과의 호환성을 유지하는 루틴을 가질 수 있습니다.
gdbm 에서 제공하는 라이브러리를 사용하여 표준 UNIX ‘dbm’ 이나 ‘ndbm’ 과
호환하는 프로그램을 짤 수 있습니다. 그리고 ‘dbm’ 이나 ‘ndbm’ 에서 사용
하던 데이터베이스 파일을 ‘gdbm’ 이 사용하는 파일의 포맷으로 유틸리티를
사용하여 바꿀 수 있습니다. 물론, GNU ‘dbm’ 의 기능은 더욱더 향상된 기능
을 제공합니다.

+—————–+ request +—————–+
| GDBM function | ————-> | internal DB |
| | DB name, key | |
| gdbm_open | | db create |
| gdbm_fetch | result | hash table |
| ……… | <————- | searching … |
+—————–+ modified DB +—————–+
| return value | | |
+————————————-+ +——————-+
User area GDBM Internal area

[ GDBM 데이터 베이스 메니져의 개념 ]

결론적으로 말씀드리면, DB 구축이나 DB 내부에까지 프로그래머가 신경쓸 필요없
이 gdbm 라이브러리가 제공해주는 인터페이스 함수를 통해서 적절한 데이터만 넘
겨주면 된다는 이야기이므로 DB 프로그래밍에 아주 편리하게 사용할 수 있습니다.

단, 12개의 함수만 익히시면 여러분들의 DB 프로그래밍에 활력소가 될 것입니다.

본 강좌는 gdbm 의 info 메뉴얼에 바탕하면서, 예를 들어가면서 설명하도록 하겠
습니다.

2. 도입과 전개

gdbm 은 표준 UNIX dbm 의 함수와 유사하게 작동하는 데이터 베이스 함수모음집
입니다. 이 함수들을 사용하여 데이터베이스 파일들을 만들거나 처리할 수 있습
니다. gdbm 의 기본적으로 key/data 를 짝으로 데이터베이스 파일에 저장하여 처
리 합니다. key 는 중복되지 않는 유일한 값이어야 하며, 각각의 key는 단 하나의
데이터아이템과 결부지어져야 합니다. key는 곧장 순서대로 정리된 상태로 접근
할 수 없습니다. gdbm 의 기본적인 유닛은 다음의 구조체입니다.

typedef struct {
char *dptr;
int dsize;
} datum;

이 구조체는 크기가 제 각각인 key와 data 아이템을 허용합니다.
char * 형인 dptr 에 얼마던지 크고 복잡한 구조체라도 주소를 저장하여 값을 꺼
집에 낼때는 형변환하여 사용할 수 있습니다.

key/data 는 한짝으로 gdbm 의 디스크 파일에 저장이 됩니다. 이것은 gdbm 데이터
베이스라고 부릅니다. 하나의 응용 프로그램이 데이터베이스의 key와 data를 처리
하기위해서는 gdbm 데이터베이스를 열어야 합니다. 당연한 이야기겠지요..
또한, gdbm 에서는 하나의 어플리케이션이 동시에 여러 데이터베이스를 여는 것이
가능합니다. 하나의 어플리케이션이 한개의 gdbm 데이터베이스를 열 때, 그것은
‘reader’ 나 ‘writer’로 나타낼 수 있습니다. 보통 gdbm 데이터베이스는 한번에
하나의 writer에 의해 많이 개봉됩니다. 그러나, 많은 reader 들은 해당 데이터
베이스를 동시에 열 수 있습니다. reader 와 writer 는 동시에 gdbm 데이터베이스
를 열지는 못합니다.

gdbm 은 데이터베이스를 다루기위해서 다음의 함수들을 지원합니다. 함수이름과
인자들에서도 그 역할을 짐작할 수 있을 것입니다. 12개 정도의 함수셋이니 그리
많은 것은 아닐 겁니다. 이 함수들은 ‘gdbm.h’에 선언되어 있는데, ‘gdbm.h’는
설치시 ‘/usr/include’ 나 ‘/usr/local/include’ 에 위치 할 것입니다.

#include <gdbm.h>

GDBM_FILE gdbm_open(name, block_size, flags, mode, fatal_func);
void gdbm_close(dbf);
int gdbm_store(dbf, key, content, flag);