SSISO Community

시소당

정규식을 사용하여 문자열 파싱하기

프로그램을 작성하다보면 파일에서나 문자열중에서 원하는 부분만 추출하는 작업이 필요할때가 아주 많다. 물론 xml과 같은 구조화된 문서일 경우 아주 쉽게 원하는 부분을 추출할 수 있겠지만 특별한 경우를 제외한 대부분의 경우에는 노가다 작업에 의존하게 된다. 가장 많이 사용하는 방법으로 if문과 String 비교의 조합으로 사용을 하게 되는데 코드가 길어질뿐더러 나중에는 내가 뭘하고 있는지 회의감마저 드는 작업이되게 되어 버린다. 좀 더 진보된 방법을 사용하기 위해 StringTokenizer를 사용하여 작업을 하게 되는데.. 이 경우도 문서의 구조가 아주 단순화되어 있지 않다면 노가다이기는 마찬가지이다.
 그래서 생각한것이 정규식이다. 정규식은 실제로 우리의 대부분의 에디터에서 사용되고 있는 기능인데 익스플로어에서 Ctrl + F를 이용한 찾기 기능마저도 실제로는 정규식이다. 정규식의 힘을 느끼게 되는 진정한 프로그램은 가장 많이 사용되는 에디터중에 하나인 VI 에디터에서 진가를 볼 수 있다. 명령어 모드에서 '/'키를 누르고 정규식을 입력하면 원하는 데이터를 정확하게 찾아준다.
 각 설하고 JAVA에서는 정규식을 java.util.regex 패키지에서 지원을 하고 있다. Pattern 클래스와 Matcher 클래스만으로도 우리가 원하는 문자열 파싱을 진행 할 수 있다. 자세한 내용은 JAVA API를 참조하기로 하고 다음으로 진행한다.
 만일 HTTP 헤더의 Request line을 분석한다고 하자. Request Line 형식은 아래와 같다.

사용자 삽입 이미지
method는 GET, POST, PUT, HEAD 등등 15가지나 되지만 지금은 GET과 POST에만 관심을 두고
URL은 /login/check.jsp?userid=test&passwd=test 형식으로 파라미터 세퍼레이터인 ?를 기준으로 앞부분은 디렉토리 및 파일구조를 나타내고 뒷부분은 파라미터를 나타내고 있다.
HTTP version은 0.9, 1.0, 1.1, 3가지가 있다.

즉 우리가 작성해야 될 정규식에 매칭되는 문자열은 다음의 문자열들이다.
GET /login/check.jsp HTTP/1.0
POST / HTTP/1.0
GET /site/index.jsp?flag=true HTTP/1.1
... 기타 등등

(?:^(GET|POST) ([^?]+)[?]?(.*) (HTTP/(0.9|1.0|1.1))$) 이 정규식이 내가 원하는 데이터를 추출 할 수 있는 정규식이다. 간단히 해석을 해보면
(): 캡춰할 영역
^: 문자열 시작
$: 문자열 종료
|: 둘중 하나

^(GET|POST): 문자열의 시작이 GET이나 POST로 시작하고 이영역은 내가 캡춰하길 원하는 영역

[]: 문자 클래스지정 ^: 문자 클래스 내에서의 ^기호는 제외의 의미다
?: 수량 0 또는 1
+: 수량 1이상 (1 또는 more)
*: 수량 없을 수도 있고 무한정 많을 수도 있다.(0 또는 more)
.: 모든 문자셋

([^?]+)[?]?(.*): 앞쪽은 ?문자를 제외한 문자열이 하나이상 반복되고 이 영역은 내가 캡춰하길 원하는 영역, ?문자는 0 또는 1이며 뒷쪽은 모든 문자가들어갈 수 있으며 내가 캡춰하길 원하는 영역

HTTP/(0.9|1.0|1.1)$: HTTP/0.9, HTTP/1.0, HTTP/1.1 이라는 문자열

자 아래는 이문자열을 파싱하기 위한 테스트 프로그램이다.

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TestReg {
 Pattern pattern = Pattern.compile("(?:^(GET|POST) ([^?]+)[?]?(.*) (HTTP/(0.9|1.0|1.1))$)");
 
 public static void main(String[] args) {
  TestReg reg = new TestReg();
 
  String line = "POST /tomcat-docs/images/tomcat.gif?userid=admin HTTP/0.9";
 
  reg.testRun(line);
 }
 
 public void testRun(String line) {
  Matcher matches = pattern.matcher(line);
 
  if (matches.find()) {
   System.out.println("Match find");
   System.out.println("Method: " + matches.replaceAll("$1"));
   System.out.println("URL: " + matches.replaceAll("$2"));
   System.out.println("Param: " + matches.replaceAll("$3"));
   System.out.println("Version: " + matches.replaceAll("$4"));
  }
 }
}

---------- 실행결과 ----------
Match find
Method: POST
URL: /tomcat-docs/images/tomcat.gif
Param: userid=admin
Version: HTTP/0.9

정규식을 compile하여 Pattern을 작성하고 Pattern.matcher()함수에 인자로 검사할 문자열을 입력한다.
그 결과물로 Matcher가 나오는데 Matcher.find()함수는 찾은 문자열이 있을 경우 true를 반환한다.

Matcher.replaceAll() 함수에 "$1", "$2"와 같은 인자들은 캡춰할  데이터 영역을 지정하는 것이다.

(?:^(GET|POST) ([^?]+)[?]?(.*) (HTTP/(0.9|1.0|1.1))$)의 경우에는
빨간색: $1
파란색: $2
녹   색: $3
회   색: $4

위에 보는것과 같이 정규식을 이해하는데에 대한 노력을 조금만 하면 실제로 코드는 굉장히 짧아지게 되며 속도 또한 스트링 비교나 StringTokenizer를 사용하는것보다 훨씬 빠른것을 알 수 있다.
 
정규식에 관한 정보는 http://www.pcre.org에 서 찾아 볼 수 있다. pcre는 perl에 호환되는 정규 표현식을 c 라이브러리로 만드는 프로젝트이다. 물론 JAVA에서 사용되는 정규식과는 조금은 다른 표현이 있기는 하지만 크게 다르지 않고 과거부터 지금까지 가장 많이 사용되었고 사용되고 있는 표현식이므로 반드시 읽고 익히도록 하자



출처 : Developer Story

2010 view

4.0 stars