RegExp.Test() 메서드와 RegExp.Execute() 메서드의 활용

등록일시: 2002-04-29 11:24,  수정일시: 2018-04-07 23:15
조회수: 22,059
본문은 최초 작성 이후, 약 22년 이상 지난 문서입니다. 일부 내용은 최근의 현실과 맞지 않거나 동떨어져 있을 수 있으며 문서 내용에 오류가 존재할 수도 있습니다. 또한 본문을 작성하던 당시 필자의 의견과 현재의 의견에 많은 차이가 존재할 수도 있습니다. 이 점, 참고하시기 바랍니다.

본문에서는 VBScript를 사용해서 정규 표현식을 처리하는 간단한 샘플 코드 몇 가지를 살펴보도록 하겠다. 물론, 지난번 글과 마찮가지로 본문의 모든 내용과 제시되는 모든 샘플 코드는 가장 최신 버전인 VBScript 5.6를 기준으로 한다. 이미 살펴봤던 것처럼 VBScript에서 정규 표현식 기능을 사용하기 위해서는 RegExp 객체의 도움을 받아야 한다. 따라서, 가장 먼저 할 일은 다음과 같이 RegExp 객체를 생성하는 것이다.

Set oRegExp = New RegExp

이 코드와 같이 RegExp 객체는 New 연산자를 사용해서 생성하는데, 이는 VBScript에서는 좀처럼 찾아보기 힘든 특이한 경우다. 필자가 알고 있는 한, VBScript에서 객체 생성시 New 연산자가 사용되는 경우는 단 두 가지 경우 뿐이다. 첫 번째는 이 샘플 코드처럼 RegExp 객체를 생성하는 경우고, 다른 한 가지 경우는 역시 VBScript 5.0 버전에서부터 지원되는 클래스(Class) 문을 사용해서 사용자가 직접 정의한 클래스의 객체를 생성하는 경우다. Windows Script V5.6 온라인 설명서에서조차 New 연산자에 관한 얘기는 이 두 가지 기술에 관하여 설명할 때 잠깐 언급될 뿐이고, 그나마도 New 연산자 자체에 관한 설명은 전무하다.

이런 방법으로 생성된 RegExp 객체는 사용이 모두 끝나고 나면 언제나처럼 Nothing 키워드를 사용해서 참조를 해제해줘야 한다.

Set oRegExp = Nothing

이제 RegExp 객체의 생성 방법을 알았으므로 RegExp 객체에서 지원해주는 세 개의 메서드(Method)들 중 먼저 비교적 간단한 Test() 메서드에 대해서 알아본다. Test() 메서드는 특정 문자열 내에 지정한 정규 표현식 패턴이 존재하는지 여부를 Boolean 형으로, 즉 True나 False로 리턴해준다.

주의해야 할 점은 Test() 메서드는 지정한 정규 표현식 패턴이 대상 문자열의 내부에 존재하기만하면 무조건 True를 리턴해주므로, Test() 메서드의 실행 결과가 True인 경우라도 '정규 표현식 패턴 = 대상 문자열'이라는 등식이 항상 성립하는 것은 아니라는 것이다. 따라서, 보다 엄격한 패턴 검사를 하기 위해서는 지난 글에서 우리가 만들어 봤던 핸드폰 번호 정규 표현식 패턴과 같이 ^ 또는 $ 같은 메타 문자들을 적절히 사용해서 정규 표현식 패턴 자체를 요구 조건에 맞게 충실하게 작성해야만 한다.

다음 코드는 ASP에서 Test() 메서드를 사용하기 편리하도록 필자가 미리 함수로 만들어 놓은 것이다. 설명의 편의상 오류 처리는 생략했으므로 필요하신 분들은 각자 상황에 맞게 수정하여 사용하는 편이 좋을 것이다.

<%
  
  '******************************************************
  '*
  '* Public Function RegExpTest(Patrn, TestStr)
  '*
  '*    RegExp.Test() 메서드를 일반화한 함수
  '*
  '******************************************************

  
  Public Function RegExpTest(Patrn, TestStr)
  
    Dim ObjRegExp
    
  On Error Resume Next    
    
    Set ObjRegExp = New RegExp
    
    ObjRegExp.Pattern = Patrn               '** 정규 표현식 패턴
    ObjRegExp.IgnoreCase = True             '** 대.소문자 구분 안함
    
    RegExpTest = ObjRegExp.Test(TestStr)
    
    Set ObjRegExp = Nothing
    
  End Function
  
%>

일단 서두에서 설명한 바와 같이 New 연산자를 사용하여 RegExp의 객체를 만든다. 그 다음에는 RegExp 객체가 갖고 있는 세 개의 프로퍼티(Property)인, Pattern, Global, IgnoreCase의 값을 상황에 알맞게 설정해줘야 한다. 그러나, 이 중에서 Global 프로퍼티는 그 설정값에 상관없이 Test() 메서드의 실행 결과에는 하등 영향을 미치지 않으므로, 위의 함수에서 볼 수 있는 것처럼 Test() 메서드의 사용시에는 무시하는 것이 일반적이다.

이 프로퍼티들 중, 가장 중요한 프로퍼티인 Pattern 프로퍼티에는 문자열 형식의 정규 표현식 패턴을 설정한다. 그리고, IgnoreCase 프로퍼티에는 정규 표현식 패턴 검색시 대.소문자를 구분할지 여부를 Boolean 형식으로 설정한다. 이처럼 필요한 프로퍼티값들을 모두 설정했으면 마지막으로 정규 표현식 검색의 대상이 되는 대상 문자열을 인자로 Test() 메서드를 호출하기만 하면 된다.

따라서, 위의 함수를 사용해서 임의의 핸드폰 번호, '011-1234-5678'이 올바른 핸드폰 번호인지를 확인하려면 다음 코드와 같이 사용하면 된다.

Response.Write RegExpTest("^0(?:11|16|17|18|19)-(?:\d{3}|\d{4})-\d{4}$", "011-1234-5678")

이 코드를 직접 실행시켜보면 True가 출력될 것이다. 이 말은 곧 '011-1234-5678'이라는 핸드폰 번호는 우리가 지정한 정규 표현식 패턴과 일치하는 올바른 형태의 핸드폰 번호라는 의미가 된다. 이처럼 정규 표현식 패턴을 작성하는 작업에만 조금 익숙해지면 이를 활용하기 위한 코드 그 자체는 매우 쉽고, 게다가 일정한 패턴까지 있으므로 여러가지 상황에서 매우 편리하게 활용할 수 있다.

가령, 다음 코드는 지정한 문자열이 'yyyy-mm-dd' 형식으로 입력된 날짜 문자열인지를 검사하고 결과를 출력한다.

Response.Write RegExpTest("^\d{4}-\d{2}-\d{2}$", "2002-01-31")

이 밖에도 활용예는 충분하다. 일반적으로 가장 생각하기 쉬운 것이 E-Mail 주소나 URL 등의 사례들일 것이다. 하지만, 사실 이런 응용이 보다 효율적으로 사용되려면 Test() 메서드가 실행되는 위치가 클라이언트 측, 즉 웹 브라우저인 편이 휠씬 더 좋다. 그러나, 이는 현실적으로 잡다한 많은 제약이 존재한다. 일단, Netscape에서는 VBScript 자체가 지원되지 않으며 JavaScript도 정규 표현식을 지원하지 않는다.

물론, Internet Explorer에서 실행되는 JavaScript는 정규 표현식을 지원한다. 그러나, 한 가지 확실히 해둬야 할 점은 Internet Explorer에서 실행되는 JavaScript는 엄밀히 말해서 JavaScript가 아니라 JScript이며, 이는 VBScript와 마찮가지로 Microsoft Windows Script 5.X로부터 지원되는 것으로 클라이언트에 설치된 Scripting Engine의 버전에 영향을 받는다는 뜻이 된다는 것이다.

따라서, 클라이언트의 웹 브라우저 버전이나 종류, 혹은 타겟 디바이스 종류 등에 관계 없이 정규 표현식 기능을 일관되게 처리해주기 위한 최선의 방법은, 정규 표현식 처리 그 자체는 서버 측에서 실행하고 그 결과와 렌더링 정보만 클라이언트의 상황에 맞도록 처리해서 반환해주는 것인데, 바로 이런 아이디어를 실제로 구현한 것이 ASP.NET의 RegularExpressionValidator 컨트롤인 것이다.

정정: 앞의 몇 문단에는 일부 잘못된 정보들이 포함되어 있는데, 2002년 본문 작성 당시의 필자의 무지로부터 비롯된 것이다. 결론부터 말하자면 버전에 따라 Netscape의 JavaScript도 정규 표현식을 지원한다. 따라서, 이 부분의 설명들 중 일부 내용은 올바르지 않은 설명이다. 물론, 현대의 거의 모든 메이저 웹 브라우저들은 대부분 JavaScript에서 정규 표현식을 지원하고 있다. 참고로, 정규 표현식은 1999년의 ECMA 스크립트 기술 규격 3판에서 추가됐다. 또한, ASP.NET 유효성 검사 컨트롤도 클라이언트 측 유효성 검사를 활성화시킨 경우 Javascript의 정규 표현식 기능을 활용한다.

다만, 필자가 본문을 작성하던 시점이 Netscape로부터 Internet Explorer로 웹 브라우저 업계의 헤게모니가 막 넘어가고, 크롬은 물론 파이어폭스도 존재하지 않았으며, 정규 표현식이라는 기술 자체도 국내 웹 개발 업계에 그리 널리 알려지지 않은 시기였다는 점을 감안해주기 바란다. 게다가, .NET 기술 자체도 이제 막 성숙해지려는 시기였기 때문에 필자로서도 부족한 점이 많을 수 밖에 없었다는 것이 필자의 구차한 변명이다.

그러면, 이번에는 Execute() 메서드에 관해서 알아보자. Execute() 메서드는 지정한 대상 문자열 내에서 지정한 정규 표현식 패턴을 만족하는 모든 문자열들의 집합을 Matches Collection 형태로 리턴해준다. 이 Collection은 Match라는 객체 형식의 Item을 검색된 문자열의 갯수만큼 갖고 있게 되는데, 이 Match 객체를 사용하면 검색된 문자열들의 위치, 길이, 값에 대한 정보에 접근할 수 있다. 간단하게 말해서 매우 강력한 검색 기능을 제공해주는 것이다.

지금까지는 VBScript를 사용해서 특정 문자열 내부에 임의의 검색 대상 문자열이 몇 개나 존재하는지, 문자열의 위치는 어디에서부터 어디까지인지 등에 관한 정보를 얻기 위해서는 루프문을 돌면서 문자열의 처음부터 끝까지 InStr() 함수를 사용하는 등의 방법 외에는 대안이 없었다. 그리고, 계속 강조한 것처럼 이런 방식은 융통성이 부족할 뿐더러 검색 결과에 대한 정보를 저장해두기 위해서는 별도의 처리 과정을 관리해야 하는 번거로움이 있다.

다음 코드는 Test() 메서드와 마찮가지로 ASP에서 Execute() 메서드를 사용하기 편리하도록 필자가 미리 함수로 만들어 놓은 것이다. 역시 필요하신 분들은 각자 상황에 맞게 수정하여 사용하면 된다.

<%
  
  '******************************************************
  '*
  '* Public Function RegExpExec(Patrn, TestStr)
  '*
  '*    RegExp.Execute() 메서드를 일반화한 함수
  '*
  '******************************************************

  
  Public Function RegExpExec(Patrn, TestStr)
  
    Dim ObjRegExp
    
  On Error Resume Next    
    
    Set ObjRegExp = New RegExp
    
    ObjRegExp.Pattern = Patrn               '** 정규 표현식 패턴
    ObjRegExp.Global = True                 '** 문자열 전체를 검색함
    ObjRegExp.IgnoreCase = True             '** 대.소문자 구분 안함
    
    Set RegExpExec = ObjRegExp.Execute(TestStr)
    
    Set ObjRegExp = Nothing
    
  End Function
  
%>

코드를 살펴보면 바로 느낄 수 있겠지만 Execute() 메서드를 사용하는 방법이나 Test() 메서드를 사용하는 방법이나 양자 간에 그다지 차이점이 없다는 것을 알 수 있다. 다만, Execute() 메서드는 Test() 메서드와 달리 실행 결과를 Collection 객체 형식으로 리턴해주므로, 빨간색으로 강조해 놓은 부분에서처럼 Set 문을 사용해야 한다는 점에 유의하기 바란다.

그리고, 이번에는 Global 프로퍼티가 사용되고 있다는 것을 알 수 있는데, 이 프로퍼티는 정규 표현식 패턴 검색시 검색 대상 문자열 내에 존재하는 정규 표현식 패턴과 일치하는 모든 문자열을 찾을 것인지, 아니면 가장 첫 번째로 찾은 문자열만 리턴하고 검색을 중단할 것인지를 결정한다.

이 코드처럼 Global 프로퍼티가 True로 설정되면 대상 문자열 내에 존재하는 모든 정규 표현식 패턴 문자열을 대상으로 정규 표현식 검색이 이루어지며, 반대로 False로 설정되면 가장 첫 번째로 찾은 정규 표현식 패턴 문자열만을 리턴한 상태로 검색이 중지된다. 그러나, 두 경우 모두 Execute() 메서드로부터 리턴되는 것은 역시 Matches Collection이므로 특별히 두 경우의 코드를 따로따로 작성할 필요는 없다.

이제 실제로 이 함수를 사용해보자. 다음 문장은 필자가 설명을 위하여 임의로 야후의 백과 사전 서비스에서 '메모리'라는 단어를 검색해서 나온 결과 중 아무 부분이나 가져와본 것이다. 이 문장에서 '저장'이라는 단어가 모두 네 차례 나온다는 것을 확인할 수 있다.

컴퓨터가 자료를 처리하려면 그 자료와 이를 처리할 수 있는 프로그램을 저장할 수 있는 곳이 있어야 한다. 처리장치로 프로그램을 불러내어 처리하는 과정에서 일시적인 저장이 필요하기도 하고, 또한 지금 바로 처리장치가 필요로 하지 않는 자료와 프로그램을 얼마 동안 보존했다가 필요할 때 꺼내서 사용할 수 있는 저장장치도 필요하다. 이러한 저장 기능을 담당하는 장치를 통털어 기억장치라 일컫는다.

다음과 같이 코드를 작성하면 이 문장에 존재하는 '저장'이라는 문자열을 모두 찾을 수 있다. 이 때, 위에서 작성한 RegExpExec() 함수를 아래의 코드가 작성된 ASP 파일에 Include하거나 동일한 ASP 파일에 복사해서 붙여넣기 등의 방법을 사용해 첨부하여 RegExpExec() 함수를 자유롭게 사용할 수 있는 상황이라고 전제한다.

<%

  Dim Test_String                   '** 검색 대상 문장을 담을 변수
  Dim Result_Match                  '** Match 를 참조할 변수
  Dim Result_Matches_Collection     '** Matches Collection 을 참조할 변수
  
  
  '** 검색 대상 문장
  Test_String = "컴퓨터가 자료를 처리하려면 그 자료와 이를 처리할 수 있는 프로그" & _
                "램을 저장할 수 있는 곳이 있어야 한다. 처리장치로 프로그램을 불러" & _
                "내어 처리하는 과정에서 일시적인 저장이 필요하기도 하고, 또한 지" & _
                "금 바로 처리장치가 필요로 하지 않는 자료와 프로그램을 얼마 동안 " & _
                "보존했다가 필요할 때 꺼내서 사용할 수 있는 저장장치도 필요하다. " & _
                "이러한 저장 기능을 담당하는 장치를 통털어 기억장치라 일컫는다."

                
  '** RegExpExec() 함수 실행  
  Set Result_Matches_Collection = RegExpExec("저장", Test_String)
  
  
  '** 루프를 돌면서 정보를 출력한다.  
  For Each Result_Match In Result_Matches_Collection
  
    With Response
      .Write "<font size=""2"">"
      .Write "문자열의 첫 글자의 Index 위치 :: " & Result_Match.FirstIndex & "<br>"
      .Write "문자열의 길이 :: " & Result_Match.Length & "<br>"
      .Write "문자열의 내용 :: " & Result_Match.Value & "<br>"
      .Write "</font><br>"
      .Flush
    End With
    
  Next  
  
%>

이 코드를 직접 실행시켜 보면 다음과 같이 문장 내부에 존재하는 '저장'이라는 문자열 네 개에 관한 정보가 모두 출력되는 것을 확인할 수 있을 것이다.

문자열의 첫 글자의 Index 위치 :: 39
문자열의 길이 :: 2
문자열의 내용 :: 저장

문자열의 첫 글자의 Index 위치 :: 91
문자열의 길이 :: 2
문자열의 내용 :: 저장

문자열의 첫 글자의 Index 위치 :: 171
문자열의 길이 :: 2
문자열의 내용 :: 저장

문자열의 첫 글자의 Index 위치 :: 187
문자열의 길이 :: 2
문자열의 내용 :: 저장

간단하나마 이 정도의 정보를 갖고 있으면 실제 프로그래밍 작업시에도 충분히 이 정보에 기반해서 대부분의 후속 작업을 무난히 진행할 수 있을 것이다. 그렇다면, 이번엔 위의 코드를 한 줄씩 살펴보도록 하자.

먼저 눈에 띄는 점은 RegExpExec() 함수의 실행 결과를 받을 때, 다시 Set 문이 사용된다는 것이다. 이는 지극히 당연한 일이므로 더 이상 설명하지 않도록 하겠다. 그 다음으로 눈에 띄는 점은 이 코드에서 사용되고 있는 정규 표현식 패턴인데 '저장'이라는 매우 간단한 형태를 갖고 있다. 잠시 뒤에 이 패턴을 약간씩 수정해서 그 때 마다의 검색 결과의 변화를 살펴보기로 한다.

가장 흥미로운 부분은 For Each ... Next 문으로 검색 결과에 대한 정보들을 표시하는 부분인데, 이미 설명했던 것처럼 Execute() 메서드로 검색된 각각의 문자열들에 대한 정보는 검색된 문자열들에 대응하는 Match 객체들을 담고 있는 Matches Collection 형태로 리턴된다. 이처럼 Match 객체는 Execute() 메서드의 실행의 결과로만 생성되며 직접 생성하는 것은 불가능하다. 따라서, 만약 위의 코드에서 검색된 문자열이 한 개도 없다면 Matches Collection 형인 Result_Matches_Collection 객체 변수의 프로퍼티, Count에는 값 0이 들어 있을 것이다. 그러나, 이 샘플 코드의 경우 검색된 문자열이 모두 네 개이므로 Count 프로퍼티에는 값 4가 들어있으며 이 값을 참고하여 루프문의 순환 횟수를 결정할 수 있다.

다만, 이 샘플 코드에서는 그보다 좀 더 간편한 방법인 For Each ... Next 문을 사용하고 있다는 것을 알 수 있는데, 이는 각자의 취향이나 그때 그때의 상황에 맞추어 두 가지 방법 중 아무 것이나 선택하여 사용하면 된다.

이 Match 객체에는 모두 세 개의 프로퍼티가 존재한다. 그 중, FirstIndex 프로퍼티는 검색된 문자열의 가장 첫 글자가 위치하는 검색 대상 문자 내에서의 인덱스(Index) 값으로서 그 값은 0부터 시작한다. Length 프로퍼티는 검색된 문자열의 길이값을 가지고 있으며, Value 프로퍼티는 검색된 문자열 그 자체를 값으로 가지고 있다. 이 Match 객체의 세 가지 프로퍼티는 모두 읽기 전용이라는 점에 주의하기 바란다.

그렇다면, 이번에는 이 코드에서 사용된 정규 표현식의 패턴을 다음의 코드와 같이 약간 수정하고 그 결과를 살펴보도록 하자.

Set Result_Matches_Collection = RegExpExec("저장\S+", Test_String)

메타 문자 \S는 공백이 아닌 문자를 의미한다. 그리고, 메타 문자 +는 + 앞의 문자가 적어도 한 번 이상 반복된다는 것을 의미한다. 그러므로, 그 실행 결과는 다음과 같다. '저장'이라는 문자열 다음에 바로 공백 문자가 들어간 네 번째 '저장'은 출력되지 않았다는 것을 알 수 있다.

문자열의 첫 글자의 Index 위치 :: 39
문자열의 길이 :: 3
문자열의 내용 :: 저장할

문자열의 첫 글자의 Index 위치 :: 91
문자열의 길이 :: 3
문자열의 내용 :: 저장이

문자열의 첫 글자의 Index 위치 :: 171
문자열의 길이 :: 5
문자열의 내용 :: 저장장치도

이번엔 아예 정규 표현식 검색 패턴 자체를 바꿔보도록 하자. 검색 대상 문장 내에는 '~~장치'라는 문자열이 다음과 같이 모두 세 종류, 네 개가 존재하고 있다.

컴퓨터가 자료를 처리하려면 그 자료와 이를 처리할 수 있는 프로그램을 저장할 수 있는 곳이 있어야 한다. 처리장치로 프로그램을 불러내어 처리하는 과정에서 일시적인 저장이 필요하기도 하고, 또한 지금 바로 처리장치가 필요로 하지 않는 자료와 프로그램을 얼마 동안 보존했다가 필요할 때 꺼내서 사용할 수 있는 저장장치도 필요하다. 이러한 저장 기능을 담당하는 장치를 통털어 기억장치라 일컫는다.

이 문장 중 '저장장치'와 '기억장치'라는 문자열만 검색하고 싶다면, 다음과 같은 정규 표현식 패턴을 사용하면 된다. 이와 같이 하면 '처리장치'라는 문자열은 검색되지 않는다.

Set Result_Matches_Collection = RegExpExec("(?:저장|기억)장치", Test_String)

다음은 이 코드의 실행 결과다. 우리가 원하는 대로 '처리장치'라는 문자열은 검색되지 않았다는 것을 확인할 수 있다.

문자열의 첫 글자의 Index 위치 :: 171
문자열의 길이 :: 4
문자열의 내용 :: 저장장치

문자열의 첫 글자의 Index 위치 :: 207
문자열의 길이 :: 4
문자열의 내용 :: 기억장치

이번에는 약간 특별한 경우를 살펴보기로 한다. 정규 표현식 패턴에 사용되는 메타 문자 중에는, '('')' 의 쌍, 즉 다시 말해서 소괄호가 있는데 이 메타 문자는 약간 특별한 기능을 가지고 있다. 예를 들어서 다음과 같은 정규 표현식 패턴을 사용하여 검색을 한다고 가정해보자.

(저장|기억|처리)장치

이제 이 정도 수준의 정규 표현식 패턴에는 익숙할 것이므로 그 의미도 쉽게 파악할 수 있을 것이다. 이 패턴은 '저장장치', '기억장치', '처리장치'라는 문자열을 의미한다.

그런데, 지난번 글에서도 한 번 언급했지만, 대상 문자열이 정규 표현식 패턴과 일치할 경우 소괄호 내부에 감싸인 문자열 부분은 메모리상에 미리 지정된 이름의 변수로 차례차례 저장이 된다 즉, 일반적인 논리식에서와 같은 논리 연산의 연산 그룹을 정해주는 기능 외에도 해당 요소를 별도로 저장해 두는 기능을 가지고 있는 것이다. 사실 이 기능은 Execute() 메서드에서 보다는 Replace() 메서드에서 더욱 유용하게 사용되는데, 이 부분에 관해서는 Replace() 메서드에 관해 설명할 때 더 상세하게 얘기하도록 하겠다.

이렇게 별도로 저장된 부분 일치 문자열은 추후 재사용을 위해서 접근이 가능한데 다음의 코드에서 그 사례를 살펴볼 수 있다. 이 코드는 바로 위에서 예로 들었던 코드를 일부 수정한 것이다. 정규 표현식 패턴을 '(저장|기억|처리)장치'로 바꾸고, SubMatches라는 Collection을 이용하여 부분 일치 문자열 요소의 정보를 출력하는 코드를 한 줄 추가했다. 붉은색으로 강조한 부분을 살펴보기 바란다.

<%

  Dim Test_String                   '** 검색 대상 문장을 담을 변수
  Dim Result_Match                  '** Match 를 참조할 변수
  Dim Result_Matches_Collection     '** Matches Collection 을 참조할 변수
  
  
  '** 검색 대상 문장
  Test_String = "컴퓨터가 자료를 처리하려면 그 자료와 이를 처리할 수 있는 프로그" & _
                "램을 저장할 수 있는 곳이 있어야 한다. 처리장치로 프로그램을 불러" & _
                "내어 처리하는 과정에서 일시적인 저장이 필요하기도 하고, 또한 지" & _
                "금 바로 처리장치가 필요로 하지 않는 자료와 프로그램을 얼마 동안 " & _
                "보존했다가 필요할 때 꺼내서 사용할 수 있는 저장장치도 필요하다. " & _
                "이러한 저장 기능을 담당하는 장치를 통털어 기억장치라 일컫는다."

                
  '** RegExpExec() 함수 실행  
  Set Result_Matches_Collection = RegExpExec("(저장|기억|처리)장치", Test_String)
  
  
  '** 루프를 돌면서 정보를 출력한다.  
  For Each Result_Match In Result_Matches_Collection
  
    With Response
      .Write "<font size=""2"">"
      .Write "문자열의 첫 글자의 Index 위치 :: " & Result_Match.FirstIndex & "<br>"
      .Write "문자열의 길이 :: " & Result_Match.Length & "<br>"
      .Write "문자열의 내용 :: " & Result_Match.Value & "<br>"
      .Write "부분 일치 문자열의 내용 :: " & Result_Match.SubMatches(0) & "<br>"
      .Write "</font><br>"
      .Flush
    End With
    
  Next  
  
%>

다음은 이 코드를 실행한 결과이다. 각 항목의 네 번째 줄을 자세히 살펴보기 바란다.

문자열의 첫 글자의 Index 위치 :: 59
문자열의 길이 :: 4
문자열의 내용 :: 처리장치
부분 일치 문자열의 내용 :: 처리

문자열의 첫 글자의 Index 위치 :: 114
문자열의 길이 :: 4
문자열의 내용 :: 처리장치
부분 일치 문자열의 내용 :: 처리

문자열의 첫 글자의 Index 위치 :: 171
문자열의 길이 :: 4
문자열의 내용 :: 저장장치
부분 일치 문자열의 내용 :: 저장

문자열의 첫 글자의 Index 위치 :: 207
문자열의 길이 :: 4
문자열의 내용 :: 기억장치
부분 일치 문자열의 내용 :: 기억

이 코드에서 처음으로 사용된 SubMatches Collection의 각 Item에는 각각의 부분 일치 문자열들이 들어 있다. Collection이라는 점에서도 눈치챌 수 있겠지만 부분 일치 문자열의 갯수에는 상식적인 조건하에 제한이 없고, 그 갯수는 당연히 정규 표현식 패턴에 사용된 소괄호의 갯수와 일치한다. 또한, SubMatches Collection도 For Each ... Next 문이나 Count 프로퍼티의 사용이 가능한 일반적인 Collection으로, 읽기 전용이므로 수정이 불가능하다는 특징을 가지고 있다.

그리고, 또 한가지 중요한 점은 SubMatches Collection은 VBScript 5.5에서부터 지원된다는 것이다. 따라서, 경우에 따라 사용이 불가능할 수도 있다. 그러나, 이 제약은 Microsoft Windows Script 5.6을 설치하기만 하면 간단하게 해결되므로 그다지 걱정할 만한 일은 아닌 것이다.

그렇다면 이런 기능이 실제로 우리에게 어떤 도움을 줄 수 있는 것일까? 가령, 이런 경우가 있을 수 있다. 우리가 필요한 임의의 패턴을 지닌 문자열들을 검색하고 그 결과 문자열들의 부분 요소를 해체해서 분석하거나 그 중 일부 요소만 재사용할 필요가 있을 때가 있다. 이를테면 URL을 해체하는 것과 같은 경우다. 이 사례는 Windows Script V5.6 온라인 설명서에서 발췌한 것이다.

http://msdn.microsoft.com:80/scripting/default.htm

이 URL은 임의로 선택된 것이다. 이 URL을 해체해서 각각의 요소를 얻어낸다고 생각해보자. 이 때, 다음과 같은 정규 표현식 패턴이 생성 가능하다.

(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)

이 정규 표현식 패턴에서는 소괄호가 전부 네 번 사용되었다. 따라서, 이 패턴을 사용해서 대상 URL을 해체한 결과는 다음과 같다. 별반 큰 노력 없이도 만족할 만한 결과를 얻을 수 있다는 점을 깨달을 수 있을 것이다. 그러나, 그보다 더 중요한 사실은 이와 같이 잘 구성된 정규 표현식 패턴은 비슷한 대부분의 경우를 포괄할 수 있으므로 재사용이 매우 용이하다는 점이다.

SubMatches(0) 이 갖고 있는 값 → http
SubMatches(1) 이 갖고 있는 값 → msdn.microsoft.com
SubMatches(2) 이 갖고 있는 값 → :80
SubMatches(3) 이 갖고 있는 값 → /scripting/default.htm

그러나, 이런 식으로 부분 일치 문자열 정보를 항상 별도의 변수에 저장해 두는 것이 언제나 좋은 것만은 아니다. 사용하지도 않는 정보를 위해서 메모리를 낭비할 필요는 없다는 점에는 누구나 동의할 것이다. 그래서, 정규 표현식에서 제공해 주는 것이 (?:)이다. 위에서 사용된 정규 표현식 패턴, '(저장|기억|처리)장치'의 경우, 좀더 효율적인 패턴 '(?:저장|기억|처리)장치'로 변경이 가능하다. 이렇게 하면 OR 논리 연산자의 기능을 가진 메타 문자인 파이프(|)에 대한 연산 그룹을 설정하는 기능만 가질뿐, 별도의 변수에 부분 일치 문자열을 저장하는 기능은 실행되지 않는다. 물론 당연한 얘기지만, 이 경우에는 SubMatches Collection을 사용할 수 없다. 그 밖에도 소괄호와 관련된 메타 문자로는 (?=)(?!)가 있는데, 이에 관해서는 각자 알아보기 바란다.

이번에도 계획과는 달리 글의 분량이 매우 길어졌다. 그래서, 어쩔수 없이 본문은 이 정도에서 마무리 짓고 정규 표현식에 관한 글을 한 번 정도 더 작성해야 할 것 같다. 다음글에서는 정규 표현식 기능의 핵심이라고도 말할 수 있는 Replace() 메서드에 관해서 살펴보도록 하겠다. 그리고, 간단하게나마 JavaScript를 사용해서 클라이언트에서 정규 표현식을 사용하는 방법에 관해서도 다뤄보려고 한다. 비록, 사용이 제한적이기는 하지만 인트라넷(Intranet)과 같은 특수한 환경에서는 가끔씩 접하게되는 독특한 상황에서 돌파구가 될 수도 있을 것이다.