page

CONFIDENTIAL Made by scgyong@bbcom.co.kr

Regular Expression

작성자 : 김기용

작성일자 : 1998년 9월 30일 수요일

들어가기

프로그램을 개발하다 보면 문자열에 대한 비교를 하게 되는 일이 종종 있다. C를 사용한다면 strcmp() 라는 함수를 사용하여 두 문자열이 같은 내용을 담고 있는지 확인해 볼 수 있다. JavaScript 에서는 ==, VB 라면 = 를, perl 이라면 eq 등의 연산자를 사용하기도 한다. 이들 연산자는 문자열의 내용이 정확히 같은지의 여부를 알려 준다. C 의 경우 strcasecmp() 라는 함수를 통해 대소문자를 구별하지 않는(Ignore Case) 비교를 할 수 있는데, 문자열에는 단순히 같은지의 비교뿐 아니라 특정 문자열을 포함하는지, 또는 반복되는지 등 다양한 연산이 필요할 때가 있다. 이럴 때 Regular Expression(이하 □정규식□)을 사용해 보자.

정규식은 □문자열의 집합을 나타내기 위한 표현 규칙□이라고도 표현된다. 방금 입력된 문자열이 키워드인지 알아보기 위해서 키워드이기 위해 만족되어야 하는 조건을 일관된 방법으로 표현하는 것이다.

문자열을 찾는(pattern matching) 것뿐만 아니라 치환(substitution)에도 유용하게 사용될 수 있다. Search & Replace 는 웬만한 에디터에서는 다 지원하는 기능인데, 보통 Ignore Case 나 Whole word only 정도의 옵션을 제공한다. MSDEV 와 같은 코딩용 에디터에서는 정규식을 지원하여, Search & Replace 에 강력한 기능을 부여한다.

이번 세미나에서는 정규식 의 간단한 문법과 각종 언어에서의 지원, 실제 활용 예 등에 대해 접해 보는 것을 목표로 하면 좋을 것이다.

어디서 사용되는가

정규식은 Unix에서 사용되는 많은 도구들에서 사용되어 왔다. grep 이나 sed, awk 등에서 사용되었고, vi 나 emacs 와 같은 에디터에서도 사용되었다. 필자는 Turbo C 2.0 의 에디터에서 정규식이 지원되는 것을 보았고, 그 뒤로 Borland 나 MS 의 개발환경에 포함된 에디터는 모두 정규식을 지원한다. 언어로는 Perl 이나 Python 등의 scripting language에서 잘 지원되고, JavaScript 도 Language 차원에서 지원한다. 그 외 C 나 C++, Java, VB 등에서는 이러한 지원이 언어차원에서 이루어지지 않기 때문에 관련 라이브러리를 사용해야 한다. MFC에서 사용할 수 있는 CRegExp 라는 라이브러리를 사용해 보았는데 기능은 좀 떨어지지만 꽤 쓸만했다. 정규식을 다룰 수 있는 COM component를 만든다면 VB를 비롯한 많은 언어에서 쉽게 사용할 수도 있다.

기초 문법

Pattern matching 에 사용되는 정규식은 대개 / (slash) 로 시작하여 / 로 끝나는 것이 전통적이며, Perl 에서는 이것조차 바꿀 수 있도록 되어 있다. 두 / 사이에 찾고자 하는 문자열을 넣으면 간단한 정규식이 만들어진다.

/bbcom/

위의 정규식은 bbcom 이라는 문자열을 찾는다.

위의 문자열을 찾는 예를 몇 개 들어 보자.

Perl:

$str = "my bbcommunication";

if ($str =~ /bbcom/) {

print "Good\n";

}

JavaScript:

var str = "my bbcommunication";

if (str.match(/bbcom/)) {

print "Good\n";

}

MSDEV:

MFC with CRegExp:

CString str = "my bbcommunication";

CRegExp re = "bbcom";

if (re.Match(str)) {

AfxMessageBox("Good");

}

여기까지는 우습다. 이제 정규식에서 사용되는 각종 특수 문자들에 대해 알아보자.

아무개문자 (wild card)

도스 등에서 파일명을 지정할 때 wildcard 라는 말을 썼다. ? 와 * 의 사용방법은 굳이 설명하지 않아도 알 것이다. 정규식에서는 모든 문자에 해당하는 것으로 . (dot) 를 사용한다.

/bb.om/ =~ "bbbom", "bbcom", "bbdom", ...

모든 문자를 지정하지 않고 subset을 지정할 수도 있다. [] 안에 가능한 글자를 열거하면 된다.

/bb[abc]om/ =~ "bbaom", "bbbom", "bbcom", ...

!~ "bbdom", "bb.om", ...

열거할 때는 - 를 써서 간단히 표현할 수도 있다. ASCII code 값이 작은 것을 앞에 두고 큰 것을 뒤에 두는 방식으로 할 수 있으며, 결합해서 사용할 수도 있다.

/bb[a-z]om/ =~ "bbaom", "bbbom", ... , "bbzom"

!~ "bbAom", "bb.om", "bb!om", ...

/bb[a-ckA-C0-8]om/ =~ "bbaom", "bbkom", "bbAom", "bb7om", ...

!~ "bbdom", "bbDom", "bb9om", ...

필요한 문자를 지정할 뿐 아니라 필요 없는 문자를 지정할 수도 있다.

/bb[^ag-k]om/ =~ "bbbom", "bbcom", ...

!~ "bbaom", "bbgom", ..., "bbkom"

사용되는 툴에 따라서 단축하여 사용할 수도 있다.

이름	단축명령	내용

----------------------------------------------------

WhiteSpace	\s	[ \t\n\r\f]

Word Character	\w	[a-zA-Z0-9]

Digit	\d	[0-9]

Non-WhiteSpace	\S	[^ \t\n\r\f]

Non-Word Character	\W	[^a-zA-Z0-9]

Non-Digit	\D	[^0-9]

단축명령들은 사용되는 툴에 따라 지원되는 종류가 매우 다양한데, Perl 과 JavaScript에서 충분히 지원해 준다.

수량사 (quantifier)

앞에서 ? 에 해당하는 것만 이야기하고 * 은 빼먹었다고 생각했는지 모르겠는데, 이것은 수량사에서 설명하기 위해서였다. 정규식에서는 개수를 표현하기 위해 { } (curly brace)를 사용한다.

/bbc{2}om/ =~ "bbccom"

!~ "bbcbbcom"

/bb[abc]{2}om/ =~ "bbabom", "bbbbom", "bbcaom"

!~ "bbcom", "bbabcom"

/bb[a-z]{3,7}om/ =~ "bbcomom", "bbcomcomom", "bbscgyongom"

!~ "bbabom", "bbbluekissom"

간단한 수량사로 ?, +, * 가 있다.

의미	단축	내용

-----------------------------

zero or one	?	{0,1}

zero or more	*	{0,}

one or more	+	{1,}

사용예는 다음과 같다.

/bbc?om/ =~ "bbom", "bbcom"

/bbc+om/ =~ "bbcom", "bbccom", "bbcccom", ...

!~ "bbom"

/bbc*om/ =~ "bbom", "bbcom", "bbccom" ...

이제까지는 한 글자만을 다루었다. 여러 개의 글자를 하나로 묶기 위해서 ( ) (parenthesis)를 사용한다.

/b(bco)*m/ =~ "bm", "bbcom", "bbcobcom"

/bb([a-z]o)+m/ =~ "bbcom", "bbzom", "bbaobocodoeom"

!~ "bbm", "bbazm"

경계 (boundary)

^ 이나 $를 사용하여 처음이나 끝 등의 경계를 지정할 수 있다. 툴에 따라 \b를 word boundary 로, \B를 non-word boundary 로 사용하는 경우도 있다.

/bbcom/ =~ "my bbcomm"

/^bbcom/ =~ "bbcomm"

!~ "my bbcomm"

/bbcom$/ =~ "my bbcom"

!~ "bbcomm"

/\bbbcom\b/ =~ "bbcom good"

!~ "bbcomm"

선택

| (vertical bar)를 사용하여 몇 가지 경우 중에서 선택할 수 있다.

/bb(com|micro)soft/ =~ "bbcomsoft", "bbmicrosoft"

!~ "bbsoft"

치환 (substitution)

전통적인 방식은 찾을 패턴(search pattern)과 대치 문자열(replacement text)을 세 개의 / 사이에 배치하는 방식이다. 다음은 간단한 치환문이다.

/microsoft/bbcom/

앞에서와 같이 여러 툴에서 치환하는 방법을 알아보자.

Perl:

$str = "The microsoft conquers the market";

$str =~ s/microsoft/bbcom/;

Sed:

# cat text.txt

The microsoft conqures the market

# cat text.txt | sed 's/microsoft/bbcom/'

The bbcom conquers the market

#

MSDEV:

Back Reference ( \ )

수량사 부분에서 괄호를 사용하여 그룹을 만드는 것에 대해 언급했었는데, 괄호는 치환할 때 아주 중요하게 사용된다. 1부터 9까지의 num 에 대해 \num 은 num 번째 괄호 그룹을 뜻한다. 다음 몇 가지 예제를 보자.

Perl:

$str = "first last";

$str =~ s/^(\S+)\s+(\S+)$/\2 \1/;

MSDEV:

Find:	^\([0-9A-Za-z\.]+\) \(.+\)$

Replace:	<TR><TH><A HREF="http://\1">\1</A></TH><TD>\2</TD>

특수 문자를 일반 문자처럼 사용하기 위해, 즉 . (dot)를 아무개 문자로 사용하지 않고 그 자체로 사용하고자 한다면 \를 사용하여 escape 시켜야 한다. ( 와 ) 를 일반 문자로 보고 \( 와 \) 를 특수 용도로 사용하는 툴들이 있는데, MSDEV 나 vi 가 그렇다. 계속해서 Back reference 가 다른 툴에서 사용되는 예를 보자.

Perl:

$time = "12:34:56";

if ($time =~ /(..):(..):(..)/) {

$hours = $1;

$minutes = $2;

$seconds = $3;

}

JavaScript #1:

var re = /(..):(..):(..)/;

var str = "12:34:56";

if (str.match(re)) {

var hours, minutes, seconds;

hours = re.$1;

minutes = re.$2;

seconds = re.$3;

}

JavaScript #2

var re = /(..):(..):(..)/;

var str = "12:34:56";

var arr = str.match(re);

if (arr) {

var hours, minutes, seconds;

hours = arr[1];

minutes = arr[2];

seconds = arr[3];

}

MFC with CRegExp:

CRegExp re = "(..):(..):(..)";

CString str = "12:34:56";

if (re.Match(str)) {

CString hours, minutes, seconds;

hours = re[1];

minutes = re[2];

seconds = re[3];

}

Back reference 는 replacement text 에서뿐 아니라 pattern 에서도 사용할 수 있다.

/(.+)\1/ =~ "bbcom", "beebeecom"

Maximal / Minimal matching

수량사 (quantifier) 를 사용할 때 주의할 것이 하나 있는데, 지정한 수량사 중에서 가능한 한 큰 숫자에 맞추려고 노력한다는 점이다. 이것을 maximal matching 또는 greed matching 이라 부른다.

$str = "bbcommunication";

$str =~ s/bbcom*/babo/; # str becomes: "babounication";

여기까지는 당연해 보이지만 다음을 보자.

$str = "bbcommunication";

$str =~ s/(bbcom*)(m*uni)/\1_\2/; # str -> "bbcomm_unication"

\1 과 \2를 나눌 때 bbco 와 mmuni 로도, bbcom 과 muni 로도, bbcomm 과 uni 로도 일치가 되지만, 먼저 나온 수량사인 m* 이 먼저 m 두 개를 잡아 먹고 (greedy), 나머지를 m*uni 가 가져간다는 말이다. Minimal matching을 하기 위해서는 수량사 뒤에 ?를 붙인다.

$str = "bbcommunication";

$str =~ s/(bbcom*?)(m*uni)/\1_\2/; # str -> "bbco_mmunication"

Search options

Ignore Case를 위한 옵션 i 와 Global Searce를 위한 옵션 g 는 대부분의 툴에서 지원된다.

/bbcom/i =~ "BbCom"

$str = "BbCom";

$str =~ s/b/Bee/gi; # str -> "BeeBeeCom"

Perl 의 m, o, s, x 등 몇 가지 옵션이 더 있는데 설명은 생략한다.

응용예

.html 파일 내에서 .GIF 와 .gif를 섞어 사용했을 때 이것을 .gif 로 통일하는 명령을 살펴 보자.

cat test.html | sed 's/(src\s*=\s*".)(\.gif")/\1.gif"/gi' > test.html

ASP에서 사용자들에 대한 정보를 DB 로부터 읽어 주소 중 앞의 두 단어만 출력하는 경우이다.

<%@ Language = JScript %>

<HTML><BODY>

<TABLE>

<%

var conn = Server.CreateObject("ADODB.Connection");

conn.Open("Users", "", "");

var sql = "SELECT * FROM tblUsers";

var rs = conn.Execute(sql);

while (!rs.EOF) {

var addr = String(rs("addr"));

var arr = addr.match(/(\S+)\s+(\S+)/);

var faddr = arr ? arr[0] : "&nbsp;";

%>

<TR><TH><%=rs("name")%></TH><TD><%=faddr%></TD></TR>

<%

rs.MoveNext();

}

rs.Close();

conn.Close();

%>

</TABLE>

</BODY></HTML>

마치며

Regular Expression 은 고전적이면서도 문자열을 다루는 데 매우 강력하게 사용될 수 있다. 각종 프로그래밍이나 툴 사용중에 유용하게 사용될 수 있기를 기대한다.