2010년 8월 25일 수요일

[프로그래밍] ASP 문자열 관련 함수


 































문자열 관련 함수
1. UCASE, LCASE 문자열의 내용을 대문자, 소문자로 변환시켜준다.
2. LEN 문자열의 길이를 반환한다.
3. LEFT, RIGHT, MID 문자열의 좌, 우, 중간에서 지정한 만큼의 문자열을 뽑아낸다.
4. INSTR, INSTRREV 해당되는 문자열의 좌측, 우측 위치를 반환한다.
5. LTRIM, RTRIM, TRIM 문자열의 좌측, 우측, 전체의 공백을 제거한다.
6. REPLACE 문자열의 내용중 일부를 다른 문자열로 변경한다.
7. SPLIT 문자열에서 특정 문자열을 기준으로 나누어 배열로 저장한다.

오늘도 여전히 다룰게 많군요. (그래도 재밌는 함수들이 많이 있습니다. ^^)
그러면 이제부터 본격적으로 위에 언급한 함수들의 자세한 내용을 알아보도록 할까요?

1. UCASE, LCASE

UCASELCASE 함수는 각각 문자열을 '대문자', '소문자' 로 변환시켜 주는 함수입니다.
(UCASE 는 'uppercase (대문자)', LCASE 는 'lowercase (소문자)' 의 약자입니다.)
예를 들어 strTest 라는 변수에 "I Love You" 라는 문자열이 있다고 가정해 보겠습니다.

Dim strTest, strUpper, strLower

strTest = "I Love You"
strUpper = UCASE (strTest)
strLower = LCASE (strTest)

이런 경우 strUpper 변수에는 "I LOVE YOU" (대문자로 변환됨) 라는 문자열이,
그리고 strLower 변수에는 "i love you" (소문자로 변환됨) 라는 문자열이 들어가게 됩니다.
어떻습니까? 쉽게 이해를 하실 수 있으시겠지요.

그렇다면 대,소문자로 변환할 수 없는 한글이나 숫자를 입력하는 경우에는 어떻게 될까요?
똑똑한 ASP 는 대,소문자로 변환할 수 없는 경우에는 원본 문자열을 그대로 반환합니다.
즉, UCASE ("사랑해요") 또는 LCASE ("486486") 처럼 변환이 불가능한 문자열을 입력했다면 원본 문자열에 아무런 수정을 가하지 않고 그대로 돌려준다는 의미가 되는 것입니다.

2. LEN

LEN 은 길이를 의미하는 'length' 의 약자로서 '문자열의 길이' 를 반환합니다.
LEN 함수는 한글, 숫자, 영문, 특수 문자를 가리지 않고 한 글자당 1씩 계산하는데요.
주의하셔야 할 점은 '공백' 도 문자로 인식하여, 길이에 포함시킨다는 사실입니다.
예를 들어 strTest 라는 변수에 "I Love You..." 라는 문자열이 들어 있다고 해볼까요?

Dim strTest, intLength

strTest = "I Love You..."
intLength = LEN (strTest)

이 경우 intLength 에는 13이라는 정수형 값이 들어가게 되는 것이지요.
만약 strTest 라는 변수에 아무런 값도 들어있지 않거나, 빈 문자열이라면?
LEN 함수는 이러한 경우에 0 을 반환합니다. 해당 문자열의 길이가 없기 때문이지요.

3. LEFT, RIGHT, MID

LEFT (RIGHT) 함수는 문자열의 왼쪽 (오른쪽) 에서 지정한 길이 만큼의 문자열을 반환하고,
MID 함수는 문자열의 지정한 위치에서, 지정한 길이 만큼의 문자열을 반환합니다.
이 함수들에 대한 설명은 예를 들어 설명드리는 것이 가장 좋을 것 같네요.

Dim strTest, strLeft, strRight, strMid

strTest = "Welcome to Dukyoung.net"
strLeft = LEFT (strTest, 7)
strRight = RIGHT (strTest, 12)
strMid = MID (strTest, 4, 7)

자, 위와 같은 문장을 ASP 에서 실행하면, 각각의 변수에는 어떤 값들이 들어가게 될까요?
우선 strLeft 에는 strTest 의 왼쪽에서 7글자.. "Welcome" 이 들어가게 됩니다.
그리고 strRight 에는 strTest 의 오른쪽에서 12글자.. "Dukyoung.net" 이 들어가지요.
마지막으로 strMid 에는 strTest 의 (왼쪽에서) 4번째 글자인 c 부터의 7글자..
즉, "come to" 라는 문자열이 들어가게 됩니다. (공백도 하나의 문자로 인정합니다.)

사용법이 상당히 간단하지요? 잘 기억해 두시기를 바랍니다.

4. INSTR, INSTRREV

INSTRINSTRREV 함수는 '특정 문자열의 위치' 를 알아내는 함수입니다.
예를 들어서 "내가 그린 기린 그린 그림은 잘 그린 기린 그린 그림이다." 이라는 문장에서
'그린' 이라는 문자가 어디있는지 찾아내려면 다음과 같이 사용하시면 됩니다.

Dim strTest, intTest

strTest = "내가 그린 기린 그린 그림은 잘 그린 기린 그린 그림이다."
intTest = INSTR (strTest, "그린")

이 문장을 실행하고 난 다음 intTest 변수에 들어갈 값은 4가 됩니다.
즉, "그린" 이라는 문자는 strTest 문자열의 4번째 위치에 있다는 뜻이 되는 것이지요.
(만약 "그린" 이라는 문자열이 없다면 intTest 에는 0 이라는 값이 들어가게 됩니다.)
그런데.. INSTR 함수에서는 또 다른 옵션을 줄 수가 있는데요.
그것은 바로 '시작 위치' 를 지정할 수가 있다는 것입니다.
위의 예를 조금 변형하여 다음과 같이 수정을 해보겠습니다.

intTest = INSTR (5, strTest, "그린")

이 함수의 의미는 과연 무엇일까요?
이것은 "그린" 이라는 문자를 찾긴 찾는데.. 5번째 문자 이후부터 찾겠다는 뜻이 됩니다.
즉, 가장 앞에 있는 5 라는 인자는 '시작 위치' 를 나타내는 것이지요.
따라서 이 함수를 실행시킨 후 intTest 의 값은 4가 아닌 10이 됩니다. (두번째 '그린'의 위치)
(사실.. 이 함수에는 가장 마지막 인자로 이진(Binary), 텍스트(Text) 비교에 관련된 인자가
하나 더 있습니다만, 그다지 사용되지 않으므로 설명을 생략하도록 하겠습니다.)

INSTRREV 함수는 INSTR 함수와 사용법이 같은데.. 시작하는 위치가 반대입니다.
즉 INSTR 함수가 문자열의 처음부터 찾는 것에 반해, INSTRREV 함수는 문자열의 끝에서부터 역순으로(거꾸로) 찾는 것이지요.

intTest = INSTRREV (strTest, "그린")

이렇게 하면 intTest 값에는 25라는 숫자가 들어가게 됩니다.
끝에서부터 찾았을 때 가장 먼저 나온 문자열의 위치를 반환하는 것이지요.
이 함수는 '파일의 확장자' 를 알아낼 때에 특히 유용하고, 자주 쓰이는 함수입니다.
(그 내용에 관해서는 나중에 이야기할 기회가 있을 것 같네요. ^^)

INSTRREV 함수 역시 시작 위치를 설정할 수 있는데요..
INSTR 함수와는 다르게 세번째 인자로서 설정합니다. (혼동하지 마시기 바랍니다.)
시작 위치를 설정하면 그 위치부터 역순으로(거꾸로) 검색하게 되는 것이지요.

intTest = INSTRREV (strTest, "그린", 24)

이렇게 사용한 경우 intTest 값에는 19라는 값이 들어가게 됩니다.

5. LTRIM, RTRIM, TRIM

TRIM 이라는 함수는 단어 뜻 그대로 '잘라내는' 함수입니다.
아무거나 잘라내는 것은 아니고, 문자열의 앞뒤에 있는 '공백' 을 잘라내는 함수가 되겠습니다.
마찬가지로 LTRIM좌측의 공백을, RTRIM우측의 공백을 잘라내는 함수입니다.
이 함수들의 예를 들어보면 다음과 같습니다.

Dim strTest, strAll, strLeft, strRight

strTest = "    My Baby Grand    "
strAll = TRIM (strTest)
strLeft = LTRIM (strTest)
strRight = RTRIM (strTest)

strTest 라는 문자열의 앞,뒤로 3칸의 공백 문자가 들어가 있습니다.
그렇다면 TRIM 함수를 실행한 strAll 이라는 변수에는 어떤 값이 들어가게 될까요?
strAll 변수에는 양 옆의 공백이 사라진 "My Baby Grand" 이라는 문자열이 들어가게 됩니다.
마찬가지로 strLeft 에는 좌측의 공백이 사라진 "My Baby Grand???" 문자열이,
strRight 에는 우측의 공백이 사라진 "???MY Baby grand" 문자열이 들어가게 되는 것이지요.

사용자들이 어떤 페이지에서 값을 입력했을 때, 좌우에 공백을 포함하여 입력한 경우
상당히 유용하게 쓰이는 함수가 되겠습니다.

6. REPLACE

문자열 중에서 특정 내용을 다른 내용으로 바꾸려 할 때 REPLACE 함수를 사용합니다.
이 함수의 사용법은 다음과 같습니다.

Dim strBefore, strAfter

strBefore = "I like you.. Do you like me?"
strAfter = REPLACE (strBefore, "like", "love")

이것은 strBefore 이라는 변수에 담긴 "I like you.. Do you like me?" 라는 문자열 중에서
"like" 라는 문자를 찾아서 그것들만 "love" 라는 문자로 바꾸라는 의미가 되겠습니다.
이 함수를 실행하면 strAfter 이라는 변수에는 변경된 문자열인
"I love you.. Do you love me?" 값이 들어가게 되는 것입니다.
(당연한 이야기지만, 해당되는 문자열이 없을 때에는 문자열에 아무런 변화가 없게 됩니다.)

7. SPLIT

마지막으로 알아볼 함수는 SPLIT 함수입니다.
이 함수는 어떤 문자열에서 특정한 문자열 또는 기호를 기준으로 문자열을 분해한 다음,
분해된 문자열들을 배열에 저장하는 함수입니다.









여기서 잠깐!!

자.. 드디어 '배열' 이 나왔습니다.
(배열을 두려워하거나, 잘 안쓰시는 초보 분들이 의외로 많은 것 같더군요..)
지레 겁먹지 마시고, 차근차근하게 한번 이해해 보도록 하겠습니다.

배열은.. '관련있는 변수들의 모임' 이라고 생각하시면 이해가 빠를 것 같습니다.
예를 들어 주소록을 작성하려고 하는데, 사람들의 이름을 저장하기 위하여 이름마다 각각 다른 변수를 선언해서 저장하려 한다면 상당히 불편한 일이 될 것입니다.
예를 들면 다음과 같습니다.

Dim strNameOne, strNameTwo, strNameThree, strNameFour

strNameOne = "김덕영"
strNameTwo = "김태영"
strNameThree = "윤영지"
strNameFour = "장경수"

4명의 이름을 저장하기 위해서 하나하나 각각 변수를 지정하면 위처럼 사용해야 합니다.
이와 같은 경우에 배열을 사용하면 다음과 같이 표현할 수가 있겠습니다.

Dim arrName (3)

arrName (0) = "김덕영"
arrName (1) = "김태영"
arrName (2) = "윤영지"
arrName (3) = "장경수"

배열의 선언 방법은 간단합니다. 일반 변수의 오른쪽에 괄호만 붙여주면 되거든요.
우선 가장 윗 줄에서 arrName (3) 이라는 배열을 선언했습니다.
3이라는 숫자는 '이 배열이 4개의 항목을 가질 것이다' 라는 것을 알려주는 것입니다.

(자, 여기서 질문이 들어올 것 같군요. '4개의 항목인데 3이라니.. 오타지?' 라고 말이지요.
이것은 오타가 아닙니다. 우리는 숫자를 셀 때 1부터 세는 것을 당연스럽게 여기지만,
배열에서는 1이 아닌 0을 기준으로 합니다. 때문에 배열 요소의 개수는 언제나 괄호안의 숫자에 1을 더한 크기가 되는 것이지요.)

이렇게 배열이 선언되었다면, 위의 예에서 보듯이 숫자 (인덱스 번호)를 이용해
각각의 항목에 값을 저장할 수가 있습니다.

그렇다면 역으로.. 배열의 괄호 안에 들어가는 '인덱스 번호' 만 알고 있다면
아주 간단하게 그 '인덱스 번호' 에 해당하는 값을 알아낼 수가 있습니다.

배열에 대한 더 자세한 설명은 다음 기회에 보충하도록 하겠습니다.

SPLIT 의 사용 예를 들어보면 다음과 같습니다.

Dim arrName, strTest

strTest = "김덕영,김태영,윤영지,장경수"
arrName = SPLIT (strTest, ",")

strTest 라는 문자열에서는 쉼표(,) 를 기준으로 사람들의 이름을 구분하고 있습니다.
그래서 SPLIT 함수를 사용할 때 쉼표를 구분자로 사용하여 문자열을 분해했습니다.

이렇게 SPLIT 함수를 사용하고 난 후에 arrName 이라는 변수에는 배열이 저장되게 됩니다.
쉼표(,)를 기준으로 구분했을 때 가장 먼저 구분된 값은 "김덕영" 이 될테고요.
이 값은 배열의 가장 첫번째인 arrName (0) 에 저장되게 됩니다.
(배열에서 맨 처음은 1이 아닌 0이라는 사실을.. 반드시 기억하시기 바랍니다.)
마찬가지로 다음으로 구분된 값인 "김태영" 은 arrName (1) 에 저장되고,
이런 식으로 arrName (2) 에는 "윤영지", arrName (3) 에는 "장경수" 가 저장됩니다.

그렇게 저장된 값들은 '인덱스 번호' 를 이용하여 쉽게 접근할 수가 있습니다.
3번째로 구분된 값을 알고 싶다면 arrName (2) 라고 하면 되는 것이지요.
(위에서 말씀드렸지만 배열은 1이 아닌 0부터 시작하기 때문에 arrName (2) 가 됩니다.)

주의하실 점은, 범위를 초과하는 '인덱스 번호' 로 접근 하면 오류가 발생된다는 점입니다.
위의 예에서는 arrName (3) 까지만 값이 저장되었는데, 만약 arrName (4) 처럼
범위를 넘어선 '인덱스 번호' 로 접근하려 하면 오류가 발생된다는 뜻이지요.


자, 이것으로 '문자열 관련 함수' 에 관한 강좌를 마치도록 하겠습니다.
오늘의 강좌에서는 문자열을 다루는 대표적인 함수들에 대해서 알아보았습니다.
오늘 배운 함수들은 비교적 사용이 간편하고, 이해 하기가 비교적 쉬운 함수들인데요.
하지만 사용 빈도가 상당히 높아.. 앞으로 ASP 를 공부하면서 지겹도록 만나게 될 것 같습니다.

혹시나 지금 당장은 잘 이해가 가지 않는다고 하더라도,
이런 함수들이 있었다는 것 정도는 기억을 해 두시는 것이 좋겠습니다.
그래야 나중에 기능들을 필요로 할 때, 당황하지 않고 찾을 수가 있을테니까요. ^^

[프로그래밍] Regular Expression: ASP Strip Tags



This week, I have been mostly coding MAZIN. You will find out what that means when listening to Ram FM soon.


As part of the project, I had to create a page with a WYSIWYG editor (also known as Rich Text Editors) that would allow users to compose copy that may or may not include simple HTML tags such as bold, italic, lists, breaks and paragraphs.


As all sites we develop these days are XHTML based and standards compliant, I found that FCKEditor was the best choice - even though it is what Rich would call “Bloatware” - i.e, it’s rediculously large in terms of directories/language files/etc. That reminds me, I need to go through it and delete all the unwanted languages and plugin-in scripts before going live.


The problem with FCKEditor is that it will still allow users to post HTML that is not allowed i.e, you have told FCKEditor that you only want users to be able to make text bold, italic or whatever. This means that when we send the form, we need a strip_tags function, like PHP has.


Haven’t you already posted an ASP one!? I hear you ask. Well, I did. But the PHP version of strip_tags allows you to specify which tags you want to remain: www.php.net/strip_tags


After some researching, it seems that nobody has come up with an ASP version of this sweet function, so I wrote my own et voila:

'	=============================================================================================================

' @name stripHTML
' @desc strips all HTML from code except for tags seperated by commas in the param "allowedTags"
' @returns string
' =============================================================================================================
function stripHTML(strHTML, allowedTags)

dim objRegExp, strOutput
set objRegExp = new regexp

strOutput = strHTML
allowedTags = "," & lcase(replace(allowedTags, " ", "")) & ","

objRegExp.IgnoreCase = true
objRegExp.Global = true
objRegExp.MultiLine = true
objRegExp.Pattern = "< (.|
)+?>" ' match all tags, even XHTML ones
set matches = objRegExp.execute(strHTML)
objRegExp.Pattern = "< (/?)(w+)[^>]*>"
for each match in matches
tagName = objRegExp.Replace(match.value, "$2")
tagName = "," & lcase(tagName) & ","

if instr(allowedTags,tagName) = 0 then
strOutput = replace(strOutput, match.value, "")
end if
next

stripHTML = strOutput 'Return the value of strOutput
set objRegExp = nothing
end function



Usage is simple, just do:


html = stripHTML(html, "b,i,strong,em,p,br")


Where b, i, strong, em, p and br are the tags you are allowing.


That’s all for now





Useful Regular Expressions in ASP

ASP, Regular Expressions No Comments »

While working on an ASP ticket system today that required regular expressions, I came up with a couple of useful regular expression patterns that may save people a few hours of thinking time.


Matching and extracting a string


Problem: I have the following chunk of arbitrary text and I want to extract the order number prefixed “ORD_”:


The quick brown fox... ORD_1012345678 ...jumped over the lazy dog


Solution: ORD_[a-zA-Z0-9_-]*


What is going on? Well, quite simply the regular expression engine is being asked to match the first three letters “ORD” followed by an underscore “_”. It then requires a series (*) of letters, numbers, underscores or dashes (but nothing else). Therefore, once the regular expression engine has found the order number “ORD_1012345678″ and then it comes to a whitespace, new line, period or whatever - it stops parsing.


ASP VBScript Code:

Set regEx = New RegExp

With regEx
.Pattern = "ORD_[a-zA-Z0-9_-]*"
.IgnoreCase = true
.Global = false
End With
set matches = regEx.Execute(text)
if matches.count > 0 then
result = matches.item(0).value
end if


The string “ORD_1012345678″, extracted from the chunk of text, will be stored in the variable “result”


A very similar version of string extraction


Problem: I have the following chunk of arbitrary text and I want to extract the ID number in square brackets (prefixed “[#”):


The quick brown fox jumped over the lazy dog [#101234-56789]


Solution: [#([a-zA-Z0-9_-]*)


What is going on? In a similar way to the first one, this regular expression match pattern is asking for a square bracket followed by a hash “[#” - but because the opening square bracket is a reserved character (used to define sets), we have to escape it with a backwards slash before hand. We then surround the series of allowed characters with parenthesis ( ) which groups the match as a “sub match”.


ASP VBScript Code:

Set regEx = New RegExp

With regEx
.Pattern = "[#([a-zA-Z0-9_-]*)"
.IgnoreCase = true
.Global = false
End With
set matches = regEx.Execute(text)
if matches.count > 0 then
result = matches(0).subMatches(0)
end if


The ID number “101234-56789″ will be stored in “result”


The important difference to note in this code is the use of “subMatches(0)” which returns the first match found in the brackets.


Stripping HTML tags


This function can be used to strip HTML tags from a string. It is very similar to the PHP function strip_tags(); but this one is not as advanced (yet).


A more advanced version is now available here


Let’s just jump straight to the code, you don’t really need to know what is going on (you can probably guess anyway)…


ASP VBScript Code:

function stripTags(strHTML)

dim regEx
Set regEx = New RegExp
With regEx
.Pattern = "< (.|
)+?>"
.IgnoreCase = true
.Global = false
End With
stripTags = regEx.replace(strHTML, "")
end function


Trimming unwanted whitespace


If you want to trim unwanted whitespace from a string, e.g: turning “Text[space]spaced[space]normally[space][space][space]or[space][space]not?” into: “Text[space]spaced[space]normally[space]or[space]not?” use the following method:

function trimWhitespace(strIn, singleSpacing)

dim regEx
Set regEx = New RegExp
With regEx
.Pattern = "s+"
.IgnoreCase = true
.Global = false
End With
if singleSpacing then
space = " "
else
space = ""
end if
trimWhitespace = regEx.replace(strIn, space)
end function


When set to false, the second parameter “singleSpacing” will simply remove all whitespaces from a string, giving: “Textspacednormallyornot?”


I hope the above examples help someone!


You may find the following websites useful, I certainly did!


[프로그래밍] ASP 정규식 파일 파싱


Parsing Data Files
Data files come in a multitude of formats and descriptions. XML files, delimited text and even unstructured text are often the sources of the data our applications need. The example we'll look at below is a delimited text file that uses qualified strings - delimiters like quotes to indicate strings that must be kept together even if they contain the delimiter character used to split the records into individual fields.

A very plain and ordinary flat ASCII text data file might look like this:





LAST NAME, FIRST NAME, PHONE, QUOTE

Lowe, Richard, 312 555 1212, ASP is good
Huston, John, 847 555 1212, I make movies

In this file, the data is simply and atomically presented with a header (in caps) and two records with each field delimited by a comma character. Parsing is a simple matter of first splitting the file by rows (newline chars) and then dividing each record up into its fields. But what happens when you want to include a comma in the data itself:





LAST NAME, FIRST NAME, PHONE, QUOTE

Lowe, Richard, 312 555 1212, I like ASP, VB and SQL
Huston, John, 847 555 1212, I make movies

Trying to parse the first record creates a problem because the last record will be considered to be two fields by a parser that only considers commas. In order to circumvent this problem, fields that contain the delimiter character are qualified - distinguished usually by being enclosed in quotes. A text qualified version of the above data file would look like this:





LAST NAME, FIRST NAME, PHONE, QUOTE

Lowe, Richard, 312 555 1212, 'I like ASP, VB and SQL'
Huston, John, 847 555 1212, 'I make movies'

Now there is way to tell which commas should be used to split the record up and which should be left as part of a field, every comma inside the single quotes should be treated as part of the text. All that remains is to implement a regular expression parser that can tell when to split based on the comma and when not to. The challenge here is a bit different from most regular expressions. Typically, you will only be looking at a small portion of text and seeing if that matches your regular expression. But in this case, the only way to reliably tell what is inside the quotes is to consider the entire line at once. Here's an example of what I mean, take this partial line of text from a fictional data file:





1, Ford, Black, 21, ', dog, cat, duck, ',

Since there is data to the left of the 1, the above line is really quite ambiguous, we don't know how many single quotes have come before this segment of the data, and therefore we don't know which text is the qualified text (which we should not split up in our parsing). If there are an even number (or no) single quotes before this text, then ', dog, cat, duck, ' is a qualified string and should be kept together. If there are an odd number then 1, Ford, Black, 21, ' is the end portion of a qualified string and should be kept together.

To solve this our regular expression must examine the entire line of text and consider how many quotes appear in it to determine whether we are inside or outside of a set of quotes:





,(?=([^']*'[^']*')*(?![^']*'))

This regular expression first finds a comma, then looks to make sure there that the number of single quotes after the comma is either an even number or none at all. It works on the premise that an even number of quotes following a comma denotes that the comma is outside of a string. Here's how it breaks down:






























, Find a comma
(?= lookahead to match this pattern:
( start a new pattern
[^']*'[^']* [not a quote] 0 or many times then a quote
[^']*'[^']*) [not a quote] 0 or many times then a quote, combined with the one above it matches pairs of quotes
)* end the pattern and match the whole pattern (pairs of quotes) zero, or multiple times
(?! lookahead to exclude this pattern
[^']*' [not a quote] 0 or many times then a quote
) end the pattern

Here is a VBScript function that accepts a string and retuns an array which is split based on using commas as delimiters and the single quote as the text qualifier:





Function SplitAdv(strInput)

Dim objRE
Set objRE = new RegExp

' Set up our RegExp object
objRE.IgnoreCase = true
objRE.Global = true
objRE.Pattern = ",(?=([^']*'[^']*')*(?![^']*'))"

' .Replace replaces the comma that we will use with
' chr(8), the b character which is extremely unlikely
' to appear in any string it then splits the line into
' an array based on the b

SplitAdv = Split(objRE.Replace(strInput, "b"), "b")
End Function

In summary, parsing text data files with regular expressions is efficient and saves your development time, because you're spared from looping through your text to pick out complex patterns to break the file up with. In a highly transitional time where there is still plenty of legacy data floating around (data that is still very imporant to the businesses that use it), knowing how to create an efficient parsing routing is a valued skill.

In Part 4 we will conclude our examination of regular expression usage with an examination of using regular expressions to replace strings (providing much more power than the simple VBScript Replace function)!


[프로그래밍] ASP 정규식표현 RegExp Match


Extracting Specific Sections From An HTML Page
The main challenge in extracting data from an HTML page is finding a way to uniquely identify the section of data you are wanting to extract. Take for example this (fictional) HTML code snippet which shows the headline for a particular site:





<table border="0" width="11%" class="Somestory">

<tr>
<td width="100%">
<p align="center">In the news...</td>
</tr>
</table>
<table border="0" width="11%" class="Headline">
<tr>
<td width="100%">
<p align="center">It's War!</td>
</tr>
</table>
<table border="0" width="11%" class="Someotherstory">
<tr>
<td width="100%">
<p align="center">In the news...</td>
</tr>
</table>

Seeing this snippet makes it pretty obvious that the headline is presented in the middle table that has a class attribute set to Headline. (If you are getting HTML that is not directly controlled by you, you might find it handy to use an optional feature of IE that allows you to view partial source based on what you highlight: http://www.microsoft.com/Windows/ie/WebAccess/default.ASP). For this exercise, we'll assume that this is the only table with the class attribute set to Headline. (This example won't delve into the mechanics of grabbing information from another Web page - rather, this example will focus on picking out particular HTML from a page. To learn how to grab the HTML contents from another Web page through an ASP page, be sure to read: Grabbing Information From Other Web Servers and the FAQ: How can I treat some other web page as data on my own site?)

Now we need to create a regular expression that will find this and only this table to include in our pages. First, add the code supporting the regular expression:





<%

Dim re, strHTML
Set re = new RegExp ' creates the RegExp object

re.IgnoreCase = true
re.Global = false ' quits searching after first match
%>

Then we need to consider the area we want to capture: In this case we want the entire <table> structure with the ending tag and headline text (no matter what it is) intact. Start by finding the opening <table> tag:





re.Pattern = "<table.*(?=Headline)"

This will match the opening table tag and return everything after it (except new lines) up to the text Headline. Here is how you return the matched HTML:





' Puts all matched HTML in a collection called Matches:

Set Matches = re.Execute(strHTML)

' Show all the matching HTML:
For Each Item in Matches
Response.Write Item.Value
Next

' Show one specific piece:
Response.write Matches.Item(0).Value

Executed against our HTML snippet above, this expression returns one match that looks like this:





<table border="0" width="11%" class="

The (?=Headline) portion of the expression doesn't capture characters, so you won't see the class of the table in this partial match. Capturing the remainder of the table is quite simple for Version 5.5 (and greater) of VBScript (and JScript):





re.Pattern = "<table.*(?=Headline)(.|
)*?</table>"

To break it down: (.|
)
captures ANY charater, translated it means (any character except newline OR newline (which, obviously, translates to ANY character)). Followed by a * matches any charater 0 to many times and the ? makes the * non-greedy. Non-greedy means that the expression should match as little as possible before the next part of the expression is found. The </table> is the end of the Headline table.

The ? qualifier is important because it prevents the regular expression from returning the contents of other tables. For example in given the HTML snippet above, removing the ? from in front of the * would return this:





<table border="0" width="11%" class="Headline">

<tr>
<td width="100%">
<p align="center">It's War!</td>
</tr>
</table>
<table border="0" width="11%" class="Someotherstory">
<tr>
<td width="100%">
<p align="center">In the news...</td>
</tr>
</table>

It not only captured the ending <table> tag from the Headline table, but also from the Someotherstory table as well, thus the need for the non-greed qualifier (?). (For more information on the non-greedy qualifier, be sure to read: Picking Out Delimited Text with Regular Expressions!)

This example described a fairly ideal condition for returning portions of HTML, in the real world it is often more complicated, especially in cases where you don't have any influence over the source of the HTML you are pulling. The best approach is to examine small amounts of HTML surrounding the content you want to extract and build a regular expression slowly, testing often, to ensure you're getting only the matches you want. It's also important to handle the case where your regular expression doesn't match anything from the source HTML. Content can change quickly, and you want to ensure your page is not displaying an unprofessional looking error simply because someone else changed their content's format.

In Part 3 we'll look at another real-world use for regular expressions: parsing data files!


[프로그래밍] ASP 정규식 예제


이번글에서는 RegExp 객체가 가지고 있는 세 개의 메소드 (Method) 들 중에서 Replace() 메소드에 관해서 중점적으로 살펴보도록 하겠다. 쉽게 예상할 수 있겠지만 RegExp.Replace() 메소드는 작업 대상 문자열에서 지정한 정규 표현식 패턴을 만족하는 문자열(들)을 찾고 그 찾아낸 문자열(들)을 지정한 문자열로 치환한다.

다음의 코드는 ASP 상에서 Replace() 메소드를 사용하기 편리하도록 언제나처럼 필자가 미리 함수로 만들어 놓은 것으로서 역시 필요하신 분들께서는 각자의 상황에 알맞게 수정하여 사용하면 된다. 그리고, 이 코드는 이전글에서 Test() 메소드Execute() 메소드를 설명하기 위해서 만들었던 두 개의 함수, 즉 RegExpTest() 함수RegExpExec() 함수를 통해서 이미 익숙해진 패턴을 거의 그대로 따르고 있으므로 특별히 추가적인 설명은 필요없을 것이라고 믿고 바로 글을 진행하도록 하겠다.







 <% 


'******************************************************

'*

'* Public Function RegExpReplace(Patrn, TrgtStr, RplcStr)

'*

'* RegExp.Replace() 메소드를 일반화한 함수

'* '******************************************************

Public Function RegExpReplace(Patrn, TrgtStr, RplcStr)

Dim ObjRegExp

On Error Resume Next

Set ObjRegExp = New RegExp

ObjRegExp.Pattern = Patrn '** 정규 표현식 패턴

ObjRegExp.Global = True '** 문자열 전체를 검색함

ObjRegExp.IgnoreCase = True '** 대.소문자 구분 안함

RegExpReplace = ObjRegExp.Replace(TrgtStr, RplcStr)

Set ObjRegExp = Nothing

End Function %>

그렇다면 이제 실제로 이 함수를 사용하여 실무에서 빈번히 발생할 수 있을 듯한 사례를 처리해 보도록 하자. 다음은 Taeyo's ASP & .NETQ & A 메뉴 중, ASP 게시판 에서 발췌한 하나의 실제 사례이다. 이 질문의 원문을 올리셨던 분께는 필자가 E-Mail 로 인용에 대한 양해를 드렸으며 그 분께서는 별다른 조건없이 너무도 흔쾌히 인용을 허락을 해주셨다. 이 자리를 빌어서 다시 한 번 감사드린다.

이제 이 사례의 경우 발생한 문제점이 무었이었는지 다음의 글을 한 번 살펴보자.







데이타 베이스 내에서 가져올때 변환을 시켜서 가져올라고 하는데 좀처럼 쉽지 않네요.......
예를 들어 데이타 내에 테이블 사이즈인 width=500 이런것들이 불규칙하게 존재합니다......
그런데 그 데이타를 가져올때 테이블 크기땜에 자꾸 삐져나갑니다.....
그래서 Replace 함수를 써서 조금 변형 되게 가져 올라고 하는데.....특정한건 되지만
예를 들어 width 가 500 보다 큰것은 전부 500으로 바꾸어 줄라면 어떠케 해야 하나요??
데이타를 가져올때의 문제인가요??아님 방법이 없는걸까요??
도와주세요..........^^


즉 현재 데이타베이스의 특정 필드 (Filed) 에는 HTML 과 일반 문자열들이 혼합된 일련의 문장들이 저장되어져 있고, 이 문장들을 가져와서 미리 준비된 HTML Template 의 특정 위치에 출력하는 작업이 요구되는 상태이다. 그러나 한 가지 문제가 있는데 가져와야 될 문자열 내부에는 HTML Tag 들이 불규칙하게 섞여 있고 이 중 일부 HTML Tag 에 설정된 Width 어트리뷰트 (Attribute) 의 값 때문에 최종 출력물의 레이아웃 (Layout) 이 깨지는 현상이 발생하는 것이다.

이같은 상황은 게시판의 내용보기 페이지 등에서 HTML Tag 의 출력을 허용하는 경우라든가 또는 ASP.NET 의 Code Behind 와 같이 유지, 보수를 용이하게 하기 위한 목적으로 ASP 코드와 HTML 문서를 분리하기 위하여 자체적으로 HTML Template 을 정의하여 사용하는 경우 매우 흔하게 접할 수 있다. 이런 경우 대부분 문제가 되는 쪽은 기술적인 측면보다는 데이타베이스에서 가져온 HTML Tag 와 일반 문자열들의 혼합 문자열을 HTML Template 에 출력할 때 이 HTML Template 의 레이아웃이 파괴되어 버리는 현상이다.

가령 프로그래머는 폭 500 Pixel 의 테이블 안에 모든 내용을 출력하려는 생각으로 프로그램을 개발했다고 생각해보자. 그러나 실제로 사용자가 입력한 문자열 안에는 폭 600 Pixel 의 크기를 가진 Table Tag 가 포함되어져 있다면 결과적으로 폭 500 Pixel 의 테이블 내부에 폭 600 Pixel 인 테이블이 위치하게 되는 결과가 된다. 따라서 최종 출력물의 레이아웃이 일그러지는 것은 매우 당연한 일이고 위의 사례도 결국은 이와 같은 범주에 속한다고 말할 수 있을 것이다.


이와 동일한 사례를 설명하기 위해 다음과 같이 간단한 약식의 HTML 문자열을 가정해 보도록 하겠다. 지면 관계상 이처럼 작업 대상 문자열을 매우 짧고 간단하게 구성하였으나, 실제로는 보다 긴 문자열이나 HTML Tag 또는 일반 문자열들이 복잡하게 얽힌 문자열을 대상으로 하더라도 지금부터 설명하는 정규 표현식 처리의 결과는 동일하다.







<table border=0 cellpadding=0 cellspacing=0 width=550>
<table border=0 cellpadding=0 cellspacing=0 width=500>
<table border=0 cellpadding=0 cellspacing=0 width=450>
<table border=0 cellpadding=0 cellspacing=0 width=600>
<table border=0 cellpadding=0 cellspacing=0 width="650">
<table border=0 cellpadding=0 cellspacing=0 width='750'>

각 Table Tag 의 가장 끝 부분에 위치한 'Width' 어트리뷰트를 주의해서 보기 바란다. 여러가지 경우를 가정하기 위해 어트리뷰트의 값이 ''''"' 로 둘러쌓인 경우와 그렇지 않은 경우를 모두 포함시켰다. 일단은 가장 단순한 방법을 사용하여 이 문제를 해결해 보기로 한다. 즉 RegExp.Replace() 메소드만을 사용하고 'Width' 어트리뷰트의 값이 500 보다 큰 경우 무조건 값을 500 으로 치환하는 경우를 가정해보겠다.


결론부터 말해서 위의 RegExpReplace() 함수를 사용하여 이 문제를 해결하기 위한 코드는 다음과 같다. 조금 복잡하게 보이겠지만 알고 나면 간단하므로 너무 걱정하지 말기 바란다. 그리고 한 번 더 강조하지만 이 코드에 사용된 정규 표현식 패턴은 다만 필자가 이렇게 구성했다는 것일 뿐 이 문제에 대한 오직 하나뿐인 정답은 절대 아니며 이보다 더 효율적인 정규 표현식 패턴도 얼마든지 만들어 낼 수 있다는 점을 기억하기 바란다.

    RegExpReplace("(<table.*width=(?:|'|""))([56789]d{2}|d{4})((?:|'|"").*>)", Sample_String, "$1500$3")


이제 이러한 결과가 나오기까지의 과정을 한 번 차분히 살펴보도록 하자. 당면한 문제는 결국 세 자리 혹은 네 자리로 된 숫자들을 비교하고 500 보다 큰 경우 500 으로 치환해 주어야 하는데 거슬리는 부분은 숫자라고해서 아무 숫자나 막 바꿀수가 없다는 것이다. 즉 HTML Tag 가 아닌 일반 문자열 중에서도 숫자값이 있을 수 있고 HTML Tag 중에서도 특정 Tag 의 숫자만 바꾸어야 하는 경우도 있다. 이에 더하여 하나의 HTML Tag 내에서도 여러 개의 어트리뷰트들이 비슷한 숫자값을 가지는 경우가 허다하다.

따라서 우리가 원하는 HTML Tag 에서 원하는 어트리뷰트의 값만 비교하여 치환을 해주어야 하는데 바로 이것이 난제인 것이다. 먼저 우리의 관심의 대상인 Width=xxx 부분을 살펴보도록 하자. 필자는 이 부분을 다음과 같이 구성하였다.

    width=(?:|'|")([56789]d{2}|d{4})(?:|'|")


당연히 이 정규 표현식 패턴의 가장 처음 부분은 width= 로 시작된다. 그리고 그 다음 부분에는 실제 숫자값이 위치하게 되는데 여기에서 한 가지 고려해야 할 사항이 있다. 실제로 현업에서 HTML 문서를 작성할 때는 width=xxx, width='xxx', 그리고 width="xxx" 와 같은 세 가지 유형의 어트리뷰트 값 표현 방식들이 모두 비슷한 빈도로 사용된다는 점이다. 사견으로 이러한 특성은 HTML 의 가장 나쁜 일면 중 하나라고 생각하는데 이러한 특성은 결국 HTML 문서의 유효성 (Validation) 을 엉망으로 만드는 원인이 되기 때문이다.

따라서 정규 표현식 패턴을 작성할 때에도 이런 점을 고려해 주어야만 하고 필자는 이를 (?:|'|") 라는 정규 표현식 패턴으로 구성했다. 즉 아무것도 없거나 ' (작은따옴표) 이거나 " (큰따옴표) 인 경우를 찾는 것이다. 이 때 한 가지 주의해야 할 점은 VBScript 에서 " 는 문자열을 의미하는 리터럴이므로 실제로 RegExpReplace() 함수에서 사용할 때는 "" 로 바꿔주어야 한다는 것이다.

그리고 이 부분에서 단순히 소괄호를 사용하지 않고 ?: 메타 문자를 사용하여 (?:...) 형식으로 표현한 것은 지난 글에서도 한 번 설명했지만 역참조를 사용하지 않기 위한 것이다. 일단 이 부분에 대해서는 잠시 설명없이 넘어가도록 한다.

이번엔 실제로 치환이 이루어져야 할 숫자 부분을 살펴보자. 현재 조건을 고려해 볼 때 3 자리 이하의 숫자는 치환할 필요가 없으며 5 자리 이상의 숫자도 일반적으로 유통되는 그래픽 카드에서 지원되는 해상도를 고려해 볼 때 그다지 큰 의미가 없다. 따라서 결국 3 자리 숫자 또는 4 자리 숫자만 고려하면 된다는 결론인데 이와 같은 조건은 ([56789]d{2}|d{4}) 이라는 정규 표현식 패턴으로 표현이 가능하다. 즉 5 에서 9 사이의 숫자로 시작되는 모든 3 자리 숫자, 또는 모든 4 자리 숫자를 찾는 것이다. 결국 이와 같이 구성된 정규 표현식 패턴의 조각들을 모두 조합하면 width=(?:|'|")([56789]d{2}|d{4})(?:|'|") 이라는 정규 표현식을 얻을 수 있다.

이제 그 다음 나머지 부분들에 관해 생각해 보자. 지금 우리에게 필요한 것은 무조건 문자열 내부에 존재하는 모든 width="xxx" 스타일의 문자열을 치환하는 것이 아니다. 단지 Table Tag 내부에 존재하는 width="xxx" 스타일의 문자열만을 치환해야 하는 것이다. 따라서 정규 표현식 <table.*.*> 를 위에서 만든 정규 표현식의 앞뒤에 추가하면 다음과 같은 결과가 나오게 된다.

    <table.*width=(?:|'|")([56789]d{2}|d{4})(?:|'|").*>


지금까지의 과정을 모두 잘 이해했다면 이제 정규 표현식 전체을 통틀어서 가장 흥미로운 기능이라고 말할 수 있는 역참조 기능에 대해서 알아봐야 될 순서다. 위와 같이 구성된 정규 표현식 패턴 그 자제만으로는 당연히 치환이 불가능하다. 치환할 대상 문자열을 찾기위해서 이처럼 정규 표현식 패턴을 구성했지만 아직까지 찾아낸 대상 문자열을 어떻게 치환할 것인가는 결정하지 않았기 때문이다. 정규 표현식에서는 패턴과 일치하는 문자열들을 찾아 놓고서 찾은 문자열의 일부분들을 조합하여 치환에 재사용할 수가 있는데 이러한 기능을 '역참조' 라고 한다. 만약 정규 표현식에서 이 역참조가 불가능했다면 문자열 처리 작업에 정규 표현식을 사용해야 할 이유가 크게 줄어들었을 것이다.

일반적으로 대상 문자열에서 치환을 하고자 하는 부분은 패턴 전체가 아니라 패턴의 일부분인 경우가 많고 지금의 사례와 같은 경우도 마찮가지다. 우리가 치환하기를 원하는 부분은 <Table ... > Tag 전체가 아니라, 단지 그 중 일부인 width="xxx" 일 뿐이다. 이와 같은 경우 역참조 기능이 매우 유용하게 사용되는데 다음을 보도록 하자. 위에서 작성한 정규 표현식을 크게 세 부분으로 나누었다.

    <table.*width=(?:|'|")  +  ([56789]d{2}|d{4})  +  (?:|'|").*>


위와 같은 세 부분 중에서 우리가 치환하기를 원하는 부분은 단지 가운데 부분뿐이다. 오히려 처음 부분과 끝 부분은 바뀌면 절대 안된다. 이 정규 표현식을 다음과 같이 각각 소괄호로 묶어보자.

    (<table.*width=(?:|'|"))  +  (([56789]d{2}|d{4}))  +  ((?:|'|").*>)


그리고, 다시 합친다.

    (<table.*width=(?:|'|"))(([56789]d{2}|d{4}))((?:|'|").*>)


이제 정규 표현식이 완성되었다. 이와 같이 소괄호를 잘 사용하면 재미있는 작업이 가능해진다. 이 정규 표현식 패턴으로 RegExp.Replace() 메소드를 실행시키면 패턴에 매치되는 각각의 문자열을 찾을 때마다 미리 약속된 변수명 $n (여기에서 n 은 1, 2, 3 ...) 으로 패턴의 소괄호안에 있는 문자열들을 순서대로 저장해둔다.

즉, 위의 사례의 경우 가장 첫 번째로 패턴과 일치하는 문자열은 <table border=0 cellpadding=0 cellspacing=0 width=550> 이 될 것이다. 이 때 패턴에 사용된 소괄호를 따져보면 다음과 결론을 얻을 수 있다.

    $1 이라는 변수에 문자열 "<table border=0 cellpadding=0 cellspacing=0 width=" 이 저장된다.

    $2 이라는 변수에 문자열 "550" 이 저장된다.

    $3 이라는 변수에 문자열 ">" 이 저장된다.


그리고, 더욱 고무적인 사실은 이 $n 변수를 바로 아래의 Code 에서와 같이 치환할 문자열에서 사용할 수 있다는 점이다. 아래에서 파랗게 강조된 부분이 바로 치환할 문자열을 나타내는 부분인데 결국 이 문자열은 역참조 변수 $1 과 바꾸려는 값 500 그리고 또 하나의 역참조 변수 $3 이 결합된 문자열인 것이다.

    RegExpReplace("(<table.*width=(?:|'|""))([56789]d{2}|d{4})((?:|'|"").*>)", Sample_String, "$1500$3")


따라서 방금 예로 들었던 가장 첫 번째로 패턴과 일치하는 문자열 <table border=0 cellpadding=0 cellspacing=0 width=550> 의 경우 아래와 같은 작업이 벌어지게 되고 <table border=0 cellpadding=0 cellspacing=0 width=550> 문자열은 아래의 작업 결과로 만들어진 <table border=0 cellpadding=0 cellspacing=0 width=500> 으로 치환된다.

    $1 + 500 + $3  =  "<table border=0 cellpadding=0 cellspacing=0 width=" + "500" + ">"
                            =  "<table border=0 cellpadding=0 cellspacing=0 width=500>"



주의해야 할 점은 위에서도 한 번 얘기했던 것처럼 (?:...) 과 같이 ?: 등의 메타 문자가 포함된 소괄호의 경우에는 따로 그 부분을 $n 변수에 저장하지 않는다는 것이다. 일련의 ?: 메타 문자 시리즈들은 바로 그런 목적을 위해 만들어진 것으로 그 외에도 각각 특수한 기능들을 가지고 있다. 이에 관한 추가적인 사항은 각자 알아보기 바란다.

다음은 지금까지 설명한 내용을 처리하는 실제 코드이다. 출력을 위해서 Replace() 함수를 몇 번 호출하는 부분이나 검색 대상 문자열을 만드는 부분을 빼고 나면 지금까지의 긴 설명이 무색할 정도로 간단하다.







 <% 


Dim Sample_String '** 치환 대상 문장을 담을 변수

Dim Result_String '** 치환 결과를 담을 변수

Dim RegExp_String '** 정규 표현식 패턴을 담을 변수

'** 검색 대상 문장

Sample_String = "<table border=0 cellpadding=0 cellspacing=0 width=550>" & vbCRLF & _

"<table border=0 cellpadding=0 cellspacing=0 width=500>" & vbCRLF & _

"<table border=0 cellpadding=0 cellspacing=0 width=450>" & vbCRLF & _

"<table border=0 cellpadding=0 cellspacing=0 width=600>" & vbCRLF & _

"<table border=0 cellpadding=0 cellspacing=0 width=""650"">" & vbCRLF & _

"<table border=0 cellpadding=0 cellspacing=0 width='700'>"

'** 정규 표현식 문장

RegExp_String = "(<table.*width=(?:|'|""))([56789]d{2}|d{4})((?:|'|"").*>)"

'** RegExpReplace() 함수 실행
String, 


"$1500$3") Result_String = Server.HTMLEncode(Result_String)

Response.Write Replace(Result_String, vbCRLF, "<br>") %>

정규 표현식에 관한 첫 번째 글에서 필자가 날짜 문자열을 처리하는 방법에 관해서 얘기했던 적이 있다. 이번에 그와 같은 날짜 문자열들에 대한 간단한 예를 살펴본다. '2000년 10월 13일''10/13/2000' 형식으로 치환하려면 다음과 같은 코드를 사용하면 된다.

    RegExpReplace("(d{4})년 (d+)월 (d+)일", "2000년 10월 13일", "$2/$3/$1")


또한, 그 반대의 경우는 다음과 같다.

    RegExpReplace("(d+)/(d+)/(d{4})", "10/13/2000", "$3년 $1월 $2일")


이 정도 수준만 되도 불편했던 프로그래밍이 한결 수월해진다. 그러나 당연한 얘기겠지만 RegExp.Replace() 메소드도 만능은 아니다. 예를 들어 위의 사례에서와 같은 경우 Width 어트리뷰트의 값을 항상 500 으로 치환해야 하는 것이 아니라, 기존의 값에 20 을 더한 값으로 치환해야 한다면 위의 정규 표현식은 사용할 수 없다.

즉, 다음과 같은 코드는 원하는 대로의 결과가 나오지 않는다는 뜻이다.

    RegExpReplace("(<table.*width=(?:|'|""))([56789]d{2}|d{4})((?:|'|"").*>)", Sample_String, "$1($2+20)$3")


이는 정규 표현식이 문자열들의 모든 요소를 말 그대로 일괄적으로 문자열로 취급하기 때문인데 이런 경우에는 지난번 글에서 설명했던 RegExp.Execute() MethodMatches Collection, SubMatches Collection 등의 기능과 함께 VBScript 의 일반적인 함수들을 사용하여 추가적인 작업을 구성해 주어야 한다. 사실 역참조 변수 $n 은 SubMatches Collection 의 Item 들과 정확하게 일 대 일로 대응되는 개념이다.

그리고 필자의 사견으로는 VBScript 의 정규 표현식 처리는 Perl 과 같은 여타 다른 프로그래밍 언어들의 그 것에 비교하여 상당히 부족하다는 느낌이다. 물론 구버전의 VBScript 에서 정규 표현식이 지원되지조차 않았던 것을 생각한다면 지금의 상태도 매우 큰 발전이겠지만 아쉬움이 남는 것은 어쩔수 없는 것 같다.

그러나 그렇다고 해서 VBScript 에서의 정규 표현식에 투자하는 시간이 낭비라고는 생각하지 않는다. 나름대로 VBScript 의 정규 표현식은 Visual Basic.NET 의 정규 표현식 기능으로 접근해 나가는 과도기적인 역활을 훌륭히 수행할 수 있다고 생각하며 그 자체만으로도 매우 강력한 기능을 가지고 있다.

또한 정규 표현식 자체가 특정 프로그래밍 언어에 종속되는 것이 아니고 해당 언어에서 정규 표현식을 얼마나 충실하게 구현했는가에 따라 그 특성이 바뀌는 것이므로, 비교적 접근하기 쉬운 VBScript 의 정규 표현식은 아마도 다른 프로그래밍 언어들의 정규 표현식 구현에 접근하기 위한 좋은 시작점이 될 수 있지 않을까 한다.


역시 이번글도 그 분량이 필자의 예상을 휠씬 초과했다. 지난번 글에서도 얘기했지만 원래 이번글에서는 위에서 설명한 RegExp.Replace() 메소드와 함께 JavaScript 상에서의 정규 표현식을 간략하게나마 소개하고 넘어가려고 했었다. 그러나 글의 분량이 약간 어정쩡해져 버려서 애초에 의도했던 것과는 달리 이번 글은 이정도에서 마무리를 해야 할 것 같다. JavaScript 상에서의 정규 표현식에 관한 내용도 나름대로 그 분량이 매우 많기 때문에 괜한 무리를 하는 것은 바람직한 일이 아닐것 같다.

그리고 아쉽지만 다음글에서도 역시 JavaScript 상에서의 정규 표현식에 관한 내용은 다루지 않는다. 지금 진행되고 있는 일련의 글들은 모두 VBScript 5.6 의 특성에 관한 것으로 일종의 연재물적인 성격을 가지고 있다. 따라서 지금 갑자기 JavaScript 에 관한 이야기를 새로 시작하는 것은 전체적인 글의 흐름에 있어 무리를 가져올 것 같다는 생각이다.

그런 이유로 JavaScript 상에서의 정규 표현식에 관한 얘기는 VBScript 5.6 에 관한 글들이 모두 마무리된 후에야 비로소 다시 시작할 수 있을 것 같다. 다음글에서는 VBScript 5.6 의 클래스 (Class) 에 관해서 알아보고 지면이 허락된다면 이 클래스를 이용하는 Remote Scripting 에 관해서도 알아보도록 하겠다.



[프로그래밍] Content 중 URL을 자동으로 인식하여 링크를 시켜주는 함수


해당 함수는 Content 중 URL을 자동으로 인식하여 링크를 시켜주는 함수 입니다.

<HTML>

<HEAD>
<TITLE>ASP 정규식</TITLE>
</HEAD>
<BODY>
<h1>ASP 정규식</h1><p>
<font size=2 face=굴림>
<%
Function UrlLink(strng)
Dim regEx
Set regEx = New RegExp
regEx.Pattern = "([^=""|='](http|https|ftp|telnet|news)://[a-z0-9-]+.[][a-zA-Z0-9:&#@=_~%;?/.+-]+)"
regEx.IgnoreCase = True
regEx.Global = True
UrlLink = regEx.Replace(strng,"<a href='$1' target='_blank'>$1</a>")
End Function

content = "홈페이지 : http://www.ihelpers.co.kr"

response.write "<b>Source : </b>" & content & "<br>"
response.write "<b>함수적용 : </b>" & UrlLink(content)
%>
</font>
</BODY>
</HTML>

[프로그래밍] webthumb -- web page snapshot-taker for Linux



WEBTHUMB(8)






WEBTHUMB(8)
Linux System Manager's Manual
WEBTHUMB(8)

NAME

webthumb -- web page snapshot-taker for Linux

SYNOPSIS

webthumb URL

PRACTICAL EXAMPLE

Take a snapshot of our home page and reduce it to a thumbnail no larger than 100x100, preserving the original aspect ratio (enter this command as one line, of course):

webthumb http://www.boutell.com/ | pnmscale -xysize 100 100 | pnmtojpeg > thumb.jpg

VERSION

Version 1.01, 12/31/2003. Version 1.01 adds locking, which forces webthumb tasks to run sequentially so that each task receives the correct screenshot.

REQUIREMENTS

You must have the following tools, which are often already installed and/or available as packages for your Linux distribution:


  • The Xvfb virtual framebuffer X server. Sometimes not installed by default, but easily available as a package for Red Hat, Debian, et cetera.
  • The Mozilla web browser.
  • The netpbm image manipulation utilities. Almost always installed; it's easy to build them yourself or install them as a package.
  • The Linux operating system. webthumb uses the /proc file system to determine whether processes are still running properly.

WHERE TO GET

Download from our web server.

DESCRIPTION

Creates a PPM-format image of the first screenful of a web page and writes this image to standard output. This is done using the Xvfb virtual framebuffer X server, which provides an environment for the mozilla web browser. To minimize CPU impact, the Xvfb and mozilla processes are kept resident in memory and reused by future invocations of webthumb. See the top of the Perl source for useful settings.

BUGS

The "chrome" of the browser is displayed. This could be fixed with a set of custom XUL files and the -chrome command line option. If you have run Mozilla or Netscape 4 interactively as the current Unix user before, Mozilla may display dialog boxes telling you about profile upgrades and so forth, which prevent the display of the desired page. This can be addressed by deleting the .mozilla and .netscape folders from the home directory of the current Unix user, if they do not contain files that are important to you. It would be nice if webthumb could set up your Mozilla preferences at first use in an appropriate way.

In an ideal world, mozilla would have a command line option like this:

mozilla -htmltops URL (THIS DOESN'T REALLY EXIST)

In order to produce a high-quality postscript version of a web page without the need for any X server at all, and this could then be piped through ghostscript to produce all manner of great things. But there is no such feature, so webthumb is a handy workaround.


[프로그래밍] NEW WAY TO HANDLE VARIABLES


The EASIEST way to deal with variables!!


Allow me to introduce myself.


My name is Rob Collyer, I lay eyes upon ASP years ago and have been heavily coding since.
It always struck me as a complete pain in the neck the way in which we have all been dealing with variables in ASP, for as long as we have used ASP.


What do I mean? ........ Allow me to explain with this example of the hard way:-








<%
FirstName = Request.Form("FirstName")
Surname = Request.Form("Surname")
Address1 = Request.Form("Address1")
Address2 = Request.Fomr(Address2")
...
...
%>


Rather than go on longer than necessary into yet more lines looking exactly the same as the ones above, to put my point across, I'll put you out of your misery.


Why do variables have to be this TEDIOUS to deal with??? I wanted a way to read in all variables at once in one go, with just one function call.... I must be mad.... sure enough, I was told this as well as things like 'It's not possible', etc.


Sure enough, at the time, it wasn't possible. To my rescue came Microsoft, with Version 5 of the VBScript engine and a new command called Execute. Execute(string) basically executes a string as though it were ASP.. So Execute("Response.write ""Hello"") .... would have the same effect as just using response.write "hello"


As soon as I saw this new feature I knew instantly what I could do with it:-







<%
For Each Field in Request.Form
TheString = Field & "= Request.Form(""" & Field & """)"
Execute(TheString)
Next
%>


We are setting variables automatically. That one little tiny piece of code, has saved me so much time over the last year, you will not believe.
Ok, it does have it flaws, Forms with image submit buttons pass form variable names like "submit.X", and "submit.Y" (Co-ordinates where you clicked on the submit image) which obviously wont do for variable names in ASP, and results in the white error page from hell.... But we can start to prevent these things:-







<%
For Each Field in Request.Form
tmpField = Replace(Field,".","") 'remove full stops
TheString = tmpField & "= Request.Form(""" & Field & """)"
Execute(TheString)
Next
%>



Why stop there?? There are many silly characters that are fine in HTML controls as names, but when setting variable names automatically like this in VBScript, these characters will not do. You can easily add additional lines as above to escape other characters like spaces, hyphens and all manner of other ones too.


You've got multiple controls on forms.... how are you gonna assign variables to multiple select boxes?
As a developer, I found it handy to read these 'multiple' part form variables into an array... I will build upon the above code to handle multiple items and create arrays from them:-









<%
For Each Field in Request.Form
tmpField = Replace(Field,".","") 'remove full stops
ItemCount=Request.Form(Field).count
IF ItemCount > 1 then
execute "redim " & field & "(" & itemcount -1 & ")" 'Dynamically dimension the array
For Item = 0 To ItemCount - 1
TheString = tmpField & "("&Item&")= request.form(""" & field & """).item(" & Item + 1& ")"
Next
Else
TheString = tmpField & "= Request.Form(""" & Field & """)"
End If
Execute(TheString)
Next
%>


I hope by now you can see the potential of what all of the above means to you as the developer.....
I urge you all to write your own functions that'll handle variables the way you want them to, and please remember.... FORMS were just an example, don't stop there, after all there are Querystrings, Cookies, Session variables, Application Variables, ServerVariables. Then there are recordsets (why not?) and the dictionary objects, etc, etc, etc.


Just remember, you are now armed with info which will save you a great deal of time, why not get busy writing some variable handling functions of your own??


If anybody wants a function to selectively handle Form, Querystring, Cookies, Session, Application and Server variables then I have one...just mail me. It also has a parameter to run in DEBUG mode which will show you variables in all objects, their values and whether their names present a problem during conversion. It'll also flag those controls that can be set to arrays, etc.


Anyway, those that do feel inspired to get writing their own handlers, let me ask one thing.... Send me a copy of your finished function(s), this is pretty new stuff and I'm well open to being inspired by YOUR code.


Feel free to contact me with your feedback.


Enjoy!


Rob Collyer


[프로그래밍] ssh 를 이용한 rsync 및 스크립트


유닉스 계열에는 백업 또는 동기화로써 많이 사용되는 rsync 란 툴이 있다.
나 역시 rsync 를 백업 툴로써 사용한다. 그런차에 ssh 를 이용해 볼까 생각이 들었다.

우선 rsync를 간략히 설명하자면 서비스서버의 TCP/873 포트를 통해 특정 디렉토리의 데이터를
백업 서버의 로컬로 가져오며 2번째 실행부터는 업데이트된 내용만 가져올수 있다 그러니 백업에 대한 트래픽을
상당히 절약할 뿐 아니라 백업에 대한 서버의 부하도 줄일수 있다
다만 서비스 서버에 중요파일을 지워지거나 수정되었고 백업서버에서 rsync 를 통해 백업을 했다면
백업서버를 통해 복구할수 없는 경우가 대부분이다.
이는 rsync 가 백업툴이 아니라 동기화 툴이기 때문이다.
이런 개념을 잘 생각하고 백업툴로써 사용할것인지 아닐것인지는 사용자의 몫으로 남겨놓겠다.

일반적으로 rsync는 서비스 서버에서 xinetd 에 종속되어 실행되며 TCP/873 포트를 통해 접속한다.
그리고 tcpwrapper 등으로 접속 IP를 제어한다. 여기서 설명하고자 하는건 일반적인 rsync 를 이용하는게
아니니 일반적인 rsync를 설치하려면 다른 문서들을 참조하기 바란다.

다음은 그 설치 방법이다 환경이 다르면 약간의 차이가 있을수 있으나 조금만 노력하면 설치할수 있으리라
생각된다.

[테스트 설치 환경]
서비스서버 : CentOS 5
백업서버 : Redhat 7.3

우선 백업서버에 rsync가 설치되어 있어야 한다.
rsync 가 없다면 yum 을 이용해 설치해 보자.
# yum install rsync
혹시 redhat 7.3을 이용하는 사람이 있어 yum 이 설치되지 않았거나 yum repository 를 찾지 못하는 분들을
위해 rpm 화일을 하단에 첨부하니 다운받아서 설치하길 바란다.

rsync 가 설치되었다면 rsync 명령어를 이용해보자.
일반적인 rsync 와 ssh 를 통한 rsync 가 다른점이 이제 나타난다.
일반적인 rsync 는 서비스서버쪽에 xinetd를 통해 TCP/873 포트로 LISTEN 하는 데몬을 띄운다.
하지만 ssh를 이용한 건 서비스 서버쪽에 rsync 데몬이 필요없다.

# rsync -avz --delete -e 서비스서버도메인또는IP:/백업대상폴더 /백업을넣을폴더
# rsync -avz --delete --rsh="ssh -l 서비스서버계정" 서비스서버도메인또는IP:/백업대상폴더 /백업을넣을폴더

위 2명령어의 차이는 첫번째는 현재 백업서버의 계정이름으로 똑같이 서버에 접근한다는 것이다.
즉 root 권한이면 서비스서버에 root 권으로 접근하고 일반계정이면 서비스서버의 일반계정으로 접근을한다는
것이다. 그래서 불편한 점이 있고 게다가 root 권한이면 서비스서버에 root 로 접속해야 하니..
ssh에서 root 로그인을 풀어야 한다.

그래서 개인적으로는 두번째 명령어를 이용한다.
현재 백업계정에서 root 로 작업하는 상태라고 할때도 서비스 서버의 일반계정을 지정할수가 있기 때문이다.
물론 여기에도 단점은 있다. 서비스서버에 일반계정이 접근할수없는 화일을 가져올수 없는 문제가 발생한다.
예를들면 root 만 접근 가능한 화일들 또는 아파치만 접근가능한 파일들 업로드 되어 nobody 만 읽을수 있는
화일들이 그 대상이다. 그러니 웹 디렉토리를 백업하려거든 권한에 주의 해주어야 한다.
예를 들면 아파치가 nobody 권한으로 실행된다면 웹에서 화일을 upload 한뒤 최소한 704 가 되도록한다.

그럼 다시 돌아가서 위의 명령어중 2번째 명령어를 실행시키자.
그럼 처음 ssh 접속시에 나오는 key 값을 받아오며 저장하겠냐고 물어본다.
yes 하고 나면 password 를 물어온다.
위에서 지정한 서비스 서버 계정의 패스워드를 입력하자.
그럼 데이터를 받아오기 시작할것이다.

여기서 걸리는게 있다. 백업을 매일 수작업으로 하기 싫다는것이다.
대부분의 사람들이 여기서 멈추고 일반적인 rsync 로 돌아가는것 같다.
그래서 스크립트를 하나 짜서 돌리기로 하자.
==================================================================================================
#!/usr/bin/expect
spawn bash -c "rsync -az --delete --rsh="ssh -l 서비스서버계정" 서비스서버도메인또는IP:/백업대상폴더 /백업을넣을폴더";
expect -re "Password:"
sleep 0.2
send "서비스서버계정패스워드r"
interact
==================================================================================================
위의 내용을 backup.sh 와같이 화일로 만든후 권한을 700 권한을 주자..
그리고 /etc/crond.daily 같은 폴더에 넣어둔다.

그럼 매일 업데이트된 내용이 백업이 될것이다.


[프로그래밍] 정규표현 메뉴얼




정규표현


한동훈




                ddoch@hitel.kol.co.kr
            


















고친 과정
고침 0.9 2004년 3월 4일 23시
1차 Docbook 변환 작업 종료
고침 0.8 2004년 2월 29일 20시
Docbook로 옮기기 시작






차례
1. 정규표현식 문법


1.1. 문법 비트
1.2. 미리 정의된 문법
1.3. 백슬래쉬 문자

2. 공통적인 오퍼레이터


2.1. 자신을 매칭시키는 오퍼레이터
2.2. 아무거나 한문자 오퍼레이터 (.)
2.3. 연결 오퍼레이터
2.4. 반복 오퍼레이터


2.4.1. 0개 이상 매칭 오퍼레이터 (*)
2.4.2. 하나이상 오퍼레이터 (+)
2.4.3. 0개나 한개 오퍼레이터 (?)
2.4.4. 간격 오퍼레이터 ({...})

2.5. 대체 오퍼레이터 (|)
2.6. 리스트 오퍼레이터([...])


2.6.1. 문자 클래스 오퍼레이터 ([:...:])
2.6.2. 범위 오퍼레이트 (-)

2.7. 그룹화 오퍼레이터 ((...) or (...))
2.8. 거꾸로참조 오퍼레이터 (숫자)
2.9. (anchoring) 오퍼레이터(^,$)


2.9.1. 라인시작 오퍼레이터(^)
2.9.2. 라인의 끝 오퍼레이터 ($)

3. GNU 오퍼레이터


3.1. 워드 오퍼레이터


3.1.1. 이맥스가 아닌 문법 테이블
3.1.2. Match-word-boundary 오퍼레이터(b)
3.1.3. Match-within-word Operator (B)
3.1.4. Match-beginning-of-word Operator (<)
3.1.5. Match-end-of-word Operator (>)
3.1.6. Match-word-constituent Operator (w)
3.1.7. Match-non-word-constituent Operator (W)

3.2. 버퍼 오퍼레이터


3.2.1. Match-beginning-of-buffer Operator (`)
3.2.2. Match-beginning-of-buffer Operator (')

4. GNU 이맥스 오퍼레이터


4.1. 문법 클래스 오퍼레이터 (syntactic class operators)


4.1.1. Match-syntactic-class Operator (sclass)
4.1.2. Match-not-syntactic-class Operator (Sclass)

5. Regex 프로그래밍


5.1. BSD Regex 함수
5.2. POSIX Regex 함수


5.2.1. POSIX 패턴 버퍼
5.2.2. POSIX 정규표현식 컴파일
5.2.3. POSIX 매칭
5.2.4. 에러 메시지 출력하기
5.2.5. 바이트 옵셋 사용하기
5.2.6. POSIX 패턴 버퍼를 Free 하기
5.2.7. POSIX Regex로 egrep 만들기

5.3. GNU Regex 함수


5.3.1. GNU 패턴 버퍼
5.3.2. GNU 정규표현식 컴파일
5.3.3. GNU 매칭
5.3.4. GNU 검색
5.3.5. 분리된 데이터로 매칭과 검색하기
5.3.6. fastmap으로 검색하기
5.3.7. GNU 변환 테이블
5.3.8. 레지스터 사용하기
5.3.9. GNU 패턴버퍼를 free 하기

6. 나오는 말




안녕하세요. 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 버젼을 만들어 보도록 하겠습니다.


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







1. 정규표현식 문법


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






1.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)이나 오픈그룹연산자('(')나 대체 연산자('|')의 바로뒤(예: (*.., |*)에 오지 않을 때에만 이것을 반복 오퍼레이터로 취급합니다.







1.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)






1.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' 과 매칭됩니다.







2. 공통적인 오퍼레이터


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


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




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

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

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


대부분의 오퍼레이터는 리스트 ('[', ']')안에서는 그 특수한 의미를 상실합니다. 그럼, 이제 각각의 오퍼레이터들을 하나씩 살펴보도록 하겠습니다.






2.1. 자신을 매칭시키는 오퍼레이터


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






2.2. 아무거나 한문자 오퍼레이터 (.)


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




 

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

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






2.3. 연결 오퍼레이터


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






2.4. 반복 오퍼레이터


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






2.4.1. 0개 이상 매칭 오퍼레이터 (*)


이 연산자는 해당 스트링을 정규표현식으로 매칭시키기 위해 가능한한 가장적은 반복횟수(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)
---^^ ---^^






2.4.2. 하나이상 오퍼레이터 (+)


RE_LIMITED_OPS 로 오퍼레이터 제한을 가하면, Regex 는 이 오퍼레이터를 인식 하지 못합니다. 만일 RE_BK_PLUS_QM 이 세팅되어 있다면, '+' 가 그 역할을 하고, 아니면 '+' 가 됩니다. 이것은 앞서의 뻥개이상 오퍼레이터 ('*')와 적어도 하나는 매칭시킨다는 점을 제외하고는 같습니다. 가령, '+'가 이 오퍼레이터면, 'ca+r' 은 'car', 'caaaar'과 매칭되고, 'cr'과는 매칭되지 않습니다.






2.4.3. 0개나 한개 오퍼레이터 (?)


이것도 역시 RE_LIMITED_OPS 가 설정되어 있으면, 인식하지 못합니다. 아울러, RE_BK_PLUS_QM 의 세팅여부에 따라, '?' 나 '?'가 그 역할을 합니다. 이 오퍼레이터는 뻥개이상의 오퍼레이터와 한개나 하나도 매칭시키지 않는다는 점만 제외하면 비슷합니다. 예를 들면, 'ca?r'은 'car'나 'cr'을 매칭시키고, 다른 것들은 매칭되지 않습니다.






2.4.4. 간격 오퍼레이터 ({...})


이 오퍼레이터를 사용하면, 특정 패턴의 출현빈도를 지정할 수 있습니다. 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 "%%
x{5} printf("only five
"); " | flex
queen:~/regex$ gcc lex.yy.c -lfl
queen:~/regex$ a.out
xxxxx
only five

^D
queen:~/regex$






2.5. 대체 오퍼레이터 (|)


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


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


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






2.6. 리스트 오퍼레이터([...])


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


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


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




  • ']' : 리스트를 닫는 역할을 합니다. 다만 '[' 다음에 ']' 가 바로오면 그냥 문자입니다.



  • '' : RE_BACKSLASH_ESCAPE_IN_LISTS 문법 비트가 세트되었다면 다음문자를 이스케이프 시키는 역할을 합니다.



  • '[:' : RE_CHAR_CLASSES 가 세트되고 그뒤에 문법에 맞는 클래스 이름이 따라 온다면 문자 클래스 오퍼레이터가 됩니다.



  • ':]' : 문자 클래스를 닫는 역할을 합니다.



  • '-' : 리스트의 처음에 오지 않고 (예: '[-.]'), 범위지정에서 끝 포인터에 오지 않는 다면(예: '[a--]') 범위 오퍼레이터의 역할을 합니다.







2.6.1. 문자 클래스 오퍼레이터 ([:...:])


이것은, 유사한 성격의 문자들을 사용자가 알아보기 쉽게 단어로 그룹을 지어서 사용하는 것입니다. 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:]' 와 같이 사용하면 다른 의미를 가지게 됩니다.






2.6.2. 범위 오퍼레이트 (-)


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








주의

문자 클래스는 범위에서 시작과 끝포인터에 사용될 수 없습니다. 그것은 하나의 문자가 아니라 문자그룹이기 때문에 그렇죠. 잘못된 경우 : '[[:digit:]-[:alpha:]]'

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


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




  • 리스트의 첫부분이나 마지막에 삽입한다.



  • 범위의 시작포인터가 '-'보다 작게 하고, 끝포인터를 '-'와 같거나 크게 한다.

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






2.7. 그룹화 오퍼레이터 ((...) or (...))


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




  • 대체오퍼레이터 ('|')나 반복오퍼레이터 ('+'나 '*')에서 인자의 범위를 지정 합니다.



  • 주어진 그룹과 매칭되는 보조문자열의 인덱스의 자취를 유지합니다. 이 그룹오퍼레이터를 사용하면, "거꾸로참조" (back-reference)오퍼레이터를 사용할 수 있습니다. 레지스터를 사용할 수 있습니다.

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


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






2.8. 거꾸로참조 오퍼레이터 (숫자)


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


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


조금더 세부적인 이야기를 해보겠습니다. '(a)1' 은 'aa'와 매칭합니다. '1'은 첫번째 그룹을 나타내며, '(a)'로 괄호로 둘러쌈으로써 그룹을 표시하는 것입니다. 마찬가지로, '(bana)na1bo1'은 'bananabanabobana'와 매칭됩니다.


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




     1번 그룹

+------+
....... |
'((a*)b)*12'
.... |
+--------+
2번 그룹
매칭되는 순서를 살펴봅시다. '--' 는 매단계에서 서로 매칭되는 부분입니다.



1) ((a*)b)*12     aabababa

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



queen:~/regex$ echo "aabababa" | egrep "((a*)b)*12"

aabababa
queen:~/regex$ echo "aababa" | egrep "((a*)b)*12"
aababa


'(one()|two())-and-(three2|four3)' 은 '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번째의 보조표현이 없다면 매칭은 실패하게 됩니다. 재미있지 않습니까? ^^






2.9. (anchoring) 오퍼레이터(^,$)


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






2.9.1. 라인시작 오퍼레이터(^)


이 오퍼레이터는 문자열의 시작이나 뉴라인 문자 다음의 빈문자열와 매칭할 수 있습니다. 다음의 경우에 '^'는 이 오퍼레이터의 역할을 하고, 다른 경우에는 그냥문자가 됩니다. * '^' 이 패턴에서 처음에 위치한다. 가령, '^foo' 같은 경우 * 문법비트가 RE_CONTEXT_INDEP_ANCHORS 가 세트되었고, 골호나 그룹..등의 밖에 있을 경우 * 오픈그룹이나 대체 오퍼레이터 다음에 따라올 경우, 예를 들면, 'a(^b)', 'a|^b'


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






2.9.2. 라인의 끝 오퍼레이터 ($)


이 오퍼레이터는 문자열의 끝이나 뉴라인 문자의 이전의 빈 문자열과 매칭됩니다. 이것은 항상 '$'로 나타납니다. 예를 들면, 'foo$'는 'foo
bar'의 처음 세글자와 매칭이 됩니다. 다음 시간에는 GNU 오퍼레이터와 GNU emacs 오퍼레이터를 잠깐 살펴보고 재미있는 Regex 프로그래밍에 들어가겠습니다.






3. GNU 오퍼레이터


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






3.1. 워드 오퍼레이터


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


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






3.1.1. 이맥스가 아닌 문법 테이블


문법 테이블은 일반적인 문자세트의 문자들에 의해 인덱스화된 하나의 배열입니다. 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가 정의된 상태에서 컴파일되었다면 어떤 일이 일어나는 지는 뒤에서 설명합니다.







3.1.2. Match-word-boundary 오퍼레이터(b)


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


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




grep "bintb" regex.c


mcnt = (int) Sword;
int mcnt;
.........
queen:~/regex$
위의 예를 살펴볼 때, 단어는 "공백문자(화이트문자)나 부호문자('(', ']', '-', ..) 가 끼어들지 않는 문자의 연속된 집합" 정도로 생각할 수 있습니다.






3.1.3. Match-within-word Operator (B)


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






3.1.4. Match-beginning-of-word Operator (<)


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






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






3.1.6. Match-word-constituent Operator (w)


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






3.1.7. Match-non-word-constituent Operator (W)


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




queen:~/regex$ echo " int " | grep "Wiwt"

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






3.2. 버퍼 오퍼레이터


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






3.2.1. Match-beginning-of-buffer Operator (`)


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






3.2.2. Match-beginning-of-buffer Operator (')


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






4. GNU 이맥스 오퍼레이터


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






4.1. 문법 클래스 오퍼레이터 (syntactic class operators)


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






4.1.1. Match-syntactic-class Operator (sclass)


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






4.1.2. Match-not-syntactic-class Operator (Sclass)


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


지겹게 지금까지 많을 것을 설명드렸지만, 사실 이 모든 것을 다 한꺼번에 기억하실 필요성은 없습니다. 자주 사용하시면서 그때그때 마다 조금씩 익숙하게 익히시는 것이 좋으리라 봅니다. 이제, 조금 더 재미있는 Regex 프로그래밍에 들어가겠습니다.






5. Regex 프로그래밍


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






5.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. 검색작업을 합니다.