강좌
클라우드/리눅스에 관한 강좌입니다.
프로그램 분류

Jakarta 프로젝트의 Regexp(정규식) 패키지 사용하기

작성자 정보

  • 웹관리자 작성
  • 작성일

컨텐츠 정보

본문

icon01.gif title29.gif

서창근 <chang@webdox.co.kr>

2001년 2월 11일

개요

  정규식(Regular Expression)이란?

정규식이란 어떠한 언어를 표현할 수 있는 문자식을 의미한다. 정규식의 이론적인 개념은 이 문서의 범위 밖이므로 접어두기로 하고 단지 정규식이 문자열의 패턴을 표현할 수 있는 것이라고만 알아두자. Perl이나 sed를 사용해본 독자들은 벌써 알겠지만 정규식은 문자열의 패턴매칭(Pattern matching)을 위하여 사용할 수 있다. 즉, 정규식을 사용하여 문자패턴을 표현하고 어떤 문자열이 그 패턴과 동일한지 알 수 있도록 해주는 역할을 한다.

  왜 정규식을 사용하는가?

자바는 이전의 프로그래밍 언어에 비해 개발이 용이하고 특히 웹 개발에 많이 이용되므로 현재 많은 인기를 누리고 있다. 그러나, 자바는 고급언어이므로 특정 작업을 세세히 구현하는 기능을 모두 제공하지는 않는다. 그런 취약점 중의 하나가 바로 자바의 문자처리 능력이다. Perl을 사용해 보았다면 Perl의 능수능란한 문자처리 능력에 놀란 적이 있을 것이다. 패턴매칭은 물론 문자열의 삭제, 삽입, 수정등이 아주 간단하게 수행된다.

자바의 문자처리 능력은 한마디로 빈약하다. String 클래스가 어느 정도 그러한 기능을 구현하긴 하지만 Perl에서와 같은 기능을 발휘하기엔 턱없이 부족하다. 그러나, 자바로 개발하다 보면 Perl에서와 같은 강력한 문자처리 능력이 긴요하게 쓰일때가 있다. 예를 들어 HTML 문서의 폼(form)에서 사용자가 주민등록번호, 생년월일, 또는 전화번호를 입력하였을 경우 어떤 특정한 형태로 입력하라고 쓰여져 있는 웹페이지들을 많이 보았을 것이다. 그런데, 사용자에게 일일이 개발자가 원하는 형태로 정보를 입력하라고 하는 것보다는 개발자가 직접 사용자가 입력한 정보에서 자기가 원하는 것을 추려서 사용하는 것이 사용자에게 불편함을 덜어줄 수 있고 많은 에러들을 피할 수 있다. 게다가 정규식을 사용하면 사용자가 어떤 형태의 데이터를 입력하든지 개발자가 원하는 형태로 사용할 수 있다. 그러므로, 특정한 형태의 데이터를 입력하라고 사용자에게 요구하는 것보다 정규식을 사용하는 것이 더욱 효율적이고 강력하며 세련된 프로그래밍이라고 할 수 있겠다. 이 외에 웹 환경에서뿐만 아니라 자바로 순수 애플리케이션을 개발하는 경우에도 Perl에서와 같은 문자처리 능력이 긴요하게 쓰일 곳은 얼마든지 있다. 현재 필자는 다운받은 HTML 페이지를 정규식을 이용하여 파싱(parsing)하는 작업을 하고 있다. 정규식은 이러한 패턴매칭, 파싱, 그리고 문서처리에 아주 효과적이다.

현재 Jakarta 프로젝트에서 맡아서 개발하고 있는 정규식 패키지는 Regexp 패키지와 ORO 패키지 두 가지가 있다. 이중 ORO 패키지는 Perl과 똑같은 패턴매칭 기능은 물론 Perl의 다른 명령어(예를 들어 s///)까지 구현하고 있으며 일반적인 문서처리(text processing) 기능까지 포함하므로 사용법이 Regexp 패키지보다 훨씬 더 복잡하다. 이번 글은 패턴매칭을 중심으로 알아볼텐데 단순히 패턴매칭 기능만을 사용할 경우 ORO 패키지는 배보다 배꼽이 더 큰 격이다. 그러므로 이번 글에서는 사용법이 더 간편한 Regexp 패키지에 대해 알아보고 설치방법과 사용법을 구체적인 예제를 통하여 살펴보기로 하자.

설치방법

  다운로드 받기

일단 Jakarta 프로젝트의 홈페이지에 있는 Regexp 홈페이지에서 다음의 링크밑에 있는 Regexp 패키지를 다운받는다.


파일은 zip과 tar.gz중 독자 여러분이 편한 포맷으로 다운받으면 된다.

  설치

파일을 다운 받았으면 컴퓨터에 설치를 하여야 한다. 원하는 곳에 디렉토리를 만들어 주고 다운 받은 파일에 맞는 압축해제 프로그램(예: winzip, tar, gzip)으로 압축을 푼다. 여기서는 윈도우 시스템에서 사용한다고 가정하고 설명할테지만 리눅스에서도 사용법은 같다. 일단 C:Program Filesjakarta-regexp 디렉토리에 풀었다고 가정하자. 패키지가 인스톨이 되었으면 실제로 자바프로그램에 사용할 수 있도록 Regexp 패키지의 클래스들을 컴파일 해야한다. C:Program Filesjakarta-regexpuild 디렉토리로 가서 build-regexp.bat 파일을 실행하면 자동으로 C:Program Filesjakarta-regexpinclassesorgapache egexp 디렉토리에 클래스들이 생성된다. 다음으로 자바컴파일러가 Regexp 클래스들을 찾을수 있도록 CLASSPATH에 C:Program Filesjakarta-regexpinclasses를 추가해준다.

  프로그램에 사용하기

위에서처럼 설치가 됐으면 이제 프로그램에서 사용하는 일만 남았다. 자바의 import 명령어를 이용하여 사용하고 싶은 프로그램 시작부분에 다음과 같이 패키지를 추가시킨다.


이제 Regexp 패키지를 사용할 수 있게 되었다. 그렇다면 지금부터는 실제로 프로그램에서 Regexp 패키지를 사용하는 방법과 용도를 알아볼텐데 그전에 일단 정규식의 사용방법부터 공부하자. 정규식의 사용방법도 모른 채 Regexp 패키지를 사용할 수는 없으니까 말이다. 벌써 정규식의 사용방법을 아는 독자들은 곧바로 클래스 사용방법으로 가면 되겠다.

정규식

  객체

그럼 먼저 정규식의 객체에 대해 살펴보자. 정규식의 모든 객체를 설명하려면 너무 복잡하고 어려우므로 여기에선 일단 가장 중요한 것들만 나열한다.

    문자

  • 유니코드 문자 - 유니코드 문자는 문자 그대로 표현한다. 즉, a는 문자열의 a와 매치된다.

  • (backslash) - 정규식에 사용되는 특수문자, 예를 들어 * 나 등을 매치할때 사용한다. 즉, * 를 매치하려면 * 라고 써줘야한다.

  • - 탭 문자와 매치된다.

  • - 새줄문자와 매치된다.

  • - 리턴문자와 매치된다.

  • f - form feed문자와 매치된다.

예를 들어 문자열 "ac*"와 매칭을 하고 싶다면 정규식은 "a\bc*"와 같이 사용한다. 와 *가 모두 정규식에 쓰이는 특수문자들이므로 앞에 을 붙여주고 나머지는 문자 그대로 사용한다.

    문자클래스

  • [a-zA-Z] - a에서 z까지, 그리고 A에서 Z사이에 있는 모든 문자와 매치된다.

  • [^a-zA-Z] - 위와 반대다. 즉 a에서 z까지, A에서 Z까지 사이에 없으면 매치된다.

만약 소문자와 매치하려면 [a-z]를 사용하거나 [^A-Z]를 사용하면 된다. 숫자도 마찬가지다. [0-9]를 사용하면 숫자와 매치되고 숫자가 아닌 것과 매치하려면 [^0-9]를 사용하면 된다.

    사전에 정의된 클래스

  • . - 새줄문자 이외의 모든것

  • w - 알파벳과 _ (영어단어에 쓰이는 문자)

  • W - 알파벳과 _이 아닌 것

  • s - 빈 공간

  • S - 빈 공간이 아닌 것

  • d - 숫자

  • D - 숫자가 아닌 것

예를 들어 "1a 2a" 또는 "3g 9i" 등의 문자열과 매치하고 싶을 땐 "dSsdS"와 같이 정규식을 써주면 된다.

    단어나 줄의 경계를 매치할때

  • ^ - 줄의 맨앞과 매치된다.

  • $ - 줄의 맨끝과 매치된다.

  • - 단어와 단어의 경계와 매치된다.

예를 들어 한 줄에 "aaaaa"라는 문자열만 있는 줄을 매치하고 싶으면 "^aaaaa$"와 같이 정규식을 써주면 된다. 만약 다른 문자열 속에 포함돼있는 "aaaaa"를 매치하고 싶다면 (예를 들어 "bbbbbaaaaabbbbb") 그냥 "aaaaa"만 해줘야 매치가 된다.

    그 이외의 것들

  • A|B - A 또는 B와 매치된다.

  • (A) - A와 매치한것을 나중에 다시 사용할 때 쓴다.

어떤 문자열에서 매치한 부분을 나중에 다시 사용하고 싶을 때는 괄호를 사용한다. 예를 들어 사용자가 입력한 전화번호를 정규식을 사용하여 패턴매칭을 했다고 하자. 이렇게 매치된 전화번호를 데이터베이스에 저장하고 싶을 때 바로 정규식 안에서 괄호를 이용하여 매칭을 한다. 괄호 안에 매칭된 부분은 나중에 다시 사용하는 것이 가능하기 때문이다. 이것은 굉장히 중요하므로 나중에 예제로 다시 설명하겠다.

    매치된 객체의 재사용

  • 1 - 첫번째 괄호에 매치된 부분

  • 2 - 두번째 괄호에 매치된 부분

  • 세번째는 $3, 네번째는 $4 등으로 사용하면 됨

정규식 안에서 괄호에 매치된 부분을 다시 사용하고 싶을 때 쓰는 객체들이다. 예를 들어 "1 - 1" 처럼 처음과 나중의 숫자가 같은 문자열만 매칭하고 싶을 경우, "(d+) - 1"과 같은 정규식을 사용하면 간단하게 매칭할 수 있다. 여기서 1 은 그 앞에서 d+ 로 매치된 숫자를 가리킨다.

  수량

수량은 각 객체가 문자열 안에서 몇 번이나 나타나는지 그 회수를 정해준다. 아마 많은 독자들이 윗부분을 보면서 정규식은 문자를 한 번에 한 개밖에 매치하지 못하나 하고 의문을 품을지 모르겠다. 그러나, 바로 밑에 나열한 것들로 수량을 정해줄 수 있다. 일단 그것들을 보고 예제를 통해서 사용 방법을 살펴보자.

  • A* - A를 0번, 아니면 그 이상 매치한다.

  • A+ - A를 한번, 아니면 그 이상 매치한다.

  • A? - A를 0번, 아니면 한번만 매치한다.

  • A{n} - A를 정확히 n번 매치한다.

  • A{n,} - A를 n번 이상 매치한다.

  • A{n,m} - A를 최소한 n번, 하지만 m번 이하로 매치한다.

  정규식 예제

지금까지 나열한 모든 객체들을 예제를 통해서 자세히 알아보도록 하자. 만약 전화번호를 매치하고 싶다면 어떻게 해야할까? 일단 전화번호는 지역번호가 있을수도, 없을수도 있다. 사람마다 지역번호를 괄호에 넣거나, 아니면 그냥 국번과 -로 구별하기도 한다. 그럼 그것에 맞는 정규식은 어떻게 써야할까? 일단 지역번호를 매치해보자. 지역번호는 세자리 숫자이므로


으로 하면 매치가 된다. 물론 너무 엄격하게 하고 싶지 않으면


로 해도 된다. 그렇다면 괄호의 있고 없고 여부는 어떻게 해야할까? 여기에선 ?나 *를 쓰면 해결할 수 있다. 즉,


을 쓰면 지역번호와 패턴매칭을 할 수 있다. 전화번호의 나머지도 간단하다.


로 하면 간단히 매치된다. 국번은 세자리, 또는 네자리지만 번호는 네자리밖에 없다. 요약하면 전화번호를 매치하기 위해선


와 같은 정규식이 필요하다. 그럼 위의 정규식을 써서 전화번호를 찾아냈다고 하자. 하지만 그 전화번호가 무엇인지 어떻게 알 수 있을까? 문자열을 그대로 다시 출력하면 매칭한 부분뿐만 아니라 다른 부분들까지 출력하게 된다. 여기서 매치된 부분만 다시 사용하기 위해 괄호를 사용하면 된다. 예를 들어, 위의 전화번호를 지역번호와 국번, 그리고 번호의 세 부분으로 나눠서 재사용 해보기로 하자. 그렇다면 다음처럼 각 부분에 괄호를 사용하면 된다.


처음보단 훨씬 복잡하게 보이지만 객체 하나하나를 풀어서 보면 쉽게 알 수 있다. 만약 전화번호가 123-456-7890 이였다면 첫번째 괄호는 123, 두번째 괄호는 456, 그리고 마지막 괄호는 7890과 매치하게 된다. 이제 매치된 부분을 재사용하려면 Regexp 패키지의 getParen()이라는 함수를 사용하면 된다. 즉, getParen(1)은 123을, getParen(2)는 456을, 그리고 getParen(3)은 7890을 돌려준다. 이것은 클래스 사용방법에 더 자세히 설명돼 있으므로 패키지 사용방법 문서를 읽어주기 바란다.

그럼 여기서 연습을 한 번 해보자. 만약 주민등록번호를 매치하고 싶다면 어떤 정규식을 써야할까? 이건 독자 여러분이 각자 해보기 바란다. 정답은 이 문서 맨 밑에 있다.

여기서 한 가지 짚고 넘어가야 할 것은 정규식엔 한 가지의 정답이란 없다는 것이다. 개발자가 원하는 결과가 제대로 나오도록만 정의해주면 된다는 것이다. 예를 들어 앞의 전화번호의 예제를 보자. 국번의 경우 d{3} 도 가능했고 d+ 도 가능했다. 개발자가 원하는 것을 사용하면 된다.

Regexp 클래스 사용방법

  클래스 목록

일단 Regexp 패키지에서 가장 많이 사용되는 클래스들을 중심으로 알아보기로 하자. 가장 유용하게 쓰이는 클래스는 다음과 같다.

  • RE

  • RECompiler

  • REProgram

  • RESyntaxException

  RE 클래스

RE 클래스가 Regexp 패키지의 중심 클래스이며 이 클래스를 이용하여 패턴매칭을 한다. 다음은 생일날짜의 문자열을 패턴매칭하는 예제이다.


여기서 한가지 주의해야 할것은 RE 클래스의 생성자를 사용할 때 정규식이 "" 안에 들어간다는 사실이다. 정규식 자체가 문자화되기 때문에 만약 정규식 안에서 를 사용하려면 \ 를 대신 써야만 한다. System.out.println() 함수를 사용할때 만약 를 직접 화면에 출력하고 싶으면 "\" 라고 해야 하는 것과 같은 이치다. 그러므로 정규식에 문자가 들어갈 때는 항상 두 개씩 써준다. 일단 정규식을 써준뒤 가 들어가는곳에 를 하나씩 더 추가할것을 필자는 권한다.

위의 예제에서 볼 수 있듯이 RE 클래스의 사용방법은 대단히 간단하다. 일단 생성자로 객체를 만든 다음, match() 함수를 통해서 패턴이 맞는지 틀린지를 가려낸다. 만약 패턴매칭이 됐다면 그 다음엔 getParen() 함수를 통해서 매치된 문자들을 추려낼 수 있다. 그 다음 처리는 물론 개발자 마음이다.

만약 실제로 위의 예제를 실행시키면 에러가 날것이다. RE 클래스의 객체를 생성할때 RESyntaxException 예외가 발생할 수 있는데 그것을 잡아줘야만 한다. 예외에 대해선 뒤에 설명하겠다.

  RECompiler 와 REProgram

정규식은 사용하기 전에 먼저 컴파일 해서 기계가 알아볼 수 있는 코드로 변환시켜야 한다. 하지만 RE 클래스를 사용하면 프로그램의 실행중에 자동으로 컴파일이 되므로 위의 예제에선 걱정할 필요가 없었다. 하지만 아무래도 실행 중에 다수의 복잡한 정규식을 컴파일 하려고 하면 프로그램의 성능에 영향을 미치게 된다. 그러므로 프로그램의 효율성에 신경이 쓰일경우엔 정규식을 미리 컴파일 할 수 있다. 그것을 가능하게 해주는 것이 RECompiler 클래스이다. RECompiler 클래스로 정규식을 컴파일 하면 REProgram 의 객체가 나오는데 이 REProgram의 객체를 RE 클래스의 생성자의 인자로 사용해 주면 된다. 앞의 예제를 이용하여 이번엔 정규식을 미리 컴파일 해보자.


하지만 실제로 프로그램의 실행중에 이렇게 정규식을 컴파일 하는 일은 거의 없을 것이다. 그래서 Regexp 패키지에 같이 포함되어 나오는 클래스가 recompile 클래스이다. 이 클래스는 프로그램의 실행 중이 아니라 따로 독립적으로 사용할 수 있도록 되어 있으며 정규식을 REProgram 이 인식할 수 있는 프로그램으로 컴파일 해준다.

위의 예제를 통하여 다시 한번 살펴보자. 일단 위에서 사용하던 정규식을 도스창(명령 프롬프트)에서 다음과 같이 컴파일 한다. 여기서 실행에 주의해야 하는것이 디렉토리이다. 만약 recompile.class 파일이 C:Program Filesjakarta-regexpinclassesorgapache egexp 에 있다면 C:Program Filesjakarta-regexpinclasses 디렉토리에서 다음과 같이 실행시켜야 한다.


위에서처럼 실행시키면 다음과 같이 출력될 것이다.


위에서처럼 출력이 됐으면 그것을 그대로 프로그램에 추가해서 사용하면 되겠다. 만약 사용하는 정규식이 많고 복잡할 경우 이렇게 미리 컴파일해서 사용하면 실행속도를 개선할 수 있을 것이다.

  RESyntaxException

RESyntaxException 은 예외클래스로서 Regexp 패키지의 거의 모든 클래스의 함수들이 던진다. 위에 있는 예제들을 컴파일하려고 하면 RESyntaxException 을 잡아줘야 한다고 에러가 날 것이다. 다음의 예제와 같이 해주면 되겠다.


예제

지금까지 공부한 모든 것을 정리하여 하나의 예제로 묶어보기로 하자. 이 예제는 사용자에게 생년월일과 주민등록번호를 입력받아 submit 버튼을 눌렀을경우 데이터를 JSP로 보낸다. 그럼 JSP가 데이터를 받아서 정규식을 이용하여 패턴매칭으로 필요한 데이터를 추려낸다. 만약 패턴매칭이 불가능할 경우엔 사용자가 잘못 입력 하였다는 메세지를 띄운다. 일단 예제를 보자.


예제 테스트

위의 예제에서 사용하고 있는 정규식을 보면 생년월일의 경우 상당히 포괄적임을 알 수 있다. 숫자사이에 /, -, 또는 빈칸만 있으면 입력이 될 수 있도록 했고 꼭 yyyy/mm/dd 의 형태로, 즉 1974/01/01의 형태로 입력을 하지 않아도 된다. 주민등록번호의 경우엔 반대로 앞자리는 여섯자리, 중간엔 빈칸이나 -, 뒷자리의 첫자리는 1 이나 2, 그리고 그뒤에 여섯자리의 숫자가 딸려오도록 생년월일보단 더 엄격하게 정규식을 적용하고 있다. 특히 여기서 눈여겨봐야 할것은 []와 |를 사용하여 생년월일의 년도, 월, 그리고 날짜 사이에 /, -, 또는 빈칸중 하나는 꼭 존재하도록 정규식을 정의한 점이다. 그리고 패턴매칭이 된 경우엔 getParen() 함수를 통하여 입력한 데이터를 다시 사용하고 있다.

위의 예제를 보면 일단 사용자가 submit 버튼을 눌렀을 경우에 server 쪽에서 데이터를 확인해주는 형태이지만 실제론 이것이 별로 효과적이지 못하다는 것을 아실것이다. 즉, 만약 데이터에 에러가 포함되어 있다면 server 에 보내지 말고 직접 client 쪽에서 처리해주는 것이 더 효율적이기 때문이다. 만약 실제로 데이터를 확인하는 프로그램을 구현한다면 client 쪽에서 데이터의 유효성을 확인해 주어야 한다. 그런 프로그램은 JavaScript 를 사용하여 Regexp 패키지가 아니라 RegExp 클래스를 사용하면 구현할 수 있다. FORM 의 text field 의 onchange 이벤트 처리자를 이용하여 그 안에서 사용자가 입력하는 데이터를 즉석에서 확인하면 된다.

마치며

이번 글에서는 정규식의 유용함에 대해 이해하고 자바에서 정규식을 구현해주는 Jakarta 프로젝트의 Regexp 패키지의 사용방법에 대해서 알아 보았다.

Regexp 패키지는 자바의 빈약한 문서처리능력을 보충해 줌은 물론 웹, 또는 그 이외의 환경에서 아주 유용하게 쓸 수 있는 패키지이다. 지금까지 비효율적으로 처리하던 많은 form data 입력이나 문자처리를 Regexp 패키지를 사용하면 아주 효율적으로 유연하게 처리할 수 있다. 이제까지 항상 정해진 형태로의 데이터만 처리하시던 분들도 이제부터 Regexp 패키지를 이용하여 좀더 효과적이고 강력하며 더욱 세련된 프로그래밍을 할 수 있게 됐으면 한다.

퀴즈정답


이 정규식을 자세히 보자. 앞자리는 여섯자리의 숫자가 오도록 했다. 중간엔 - 이나 빈칸이 꼭 오도록 했고 뒷자리의 첫번째 숫자는 1이나 2여야 하며 그뒤로 여섯자리의 숫자가 딸려와야 한다.

관련자료

댓글 0
등록된 댓글이 없습니다.

공지사항


뉴스광장


  • 현재 회원수 :  60,043 명
  • 현재 강좌수 :  35,853 개
  • 현재 접속자 :  84 명