본문 바로가기
2_ 바삭바삭 프로그래밍/Java

Java - 겨울마다 만나지는 정규표현식

by 준환이형님_ 2011. 1. 20.

"안녕하세요" -> "안녕/n" -> "안녕" (in string type arr[0]) 

라이브러리를 통한 문자메세지의 자연어처리 이후 우리가 사용하는 특정한 형태로 자를 필요성을 느꼈으므로 일년 전, 이 맘 때 잠깐 맛본 적이 있던 정규표현식을 다시 찾아보게 되었습니다.수업이라도 들어서 기초를 좀 더 다져야 할텐데 말이죠..



(출처 : http://ispkorea.com/91 - 고에몽 님)

어플리케이션들을 만들다 보면 종종 단어 검색, 메일 주소 점검, XML 문서의 무결성 확인 등과 같은 복잡한 문자열 처리 기능이 필요한 경우가 있게 마련이다. 이런 때에 패턴 매칭(pattern matching)이 자주 사용된다. Perl과 sed, awk와 같은 언어들은 일치하는 텍스트를 검색하기 위해, 패턴을 정의하는 문자들과 정규표현식을 사용해 향상된 패턴 매칭 능력을 제공해 주고 있다. 자바에서 패턴 매칭을 사용하려면 StringTokenizer 클래스와 수많은 charAt, substring 메소드를 동원해야만 한다. 하지만 이런 방식은 종종 아주 복잡하고 지저분한 코드를 만들어내곤 한다.

하지만 모두 지금까지의 이야기일 Java 2 Platform, Standard Edition(J2SE) 1.4 넘어오면서 정규 표현식을 다룰 있는 java.util.regex라는 새로운 패키지가 추가되었다. 이제 자바에도 정규 표현식의 강력함을 맛볼 있는 메타 캐릭터들을 사용할 있게 되었다.

이번 글에서는 정규 표현식에 대해 간단히 설명하고, 아래의 과정에 따라 java.util.regex 패키지를 통해 정규 표현식을 활용하는 방법에 대해 자세히 설명하도록 하겠다.

l        단어 바꾸기

l        메일 주소 점검

l        파일에서 제어 문자 제거

l        파일 검색

 ( 글의 예제들을 실행시켜보려면 J2SE 1.4 호환 컴파일러와 가상머신이 필요함)

 정규 표현식 만들기

정규표현식이란 여러 문자열들의 공통적인 특성을 기술하는 문자들의 패턴이다. java.util.regex 패키지를 보면 입력 데이터로부터 패턴을 찾고 수정할 있는 다양한 방법들이 제공됨을 확인할 있을 것이다.

 정규표현식의 가장 단순한 형태는 ‘Java’, ‘programming’ 같은 일반적인 문자열이다. 또한 메일 주소와 같이 특수한 포맷을 갖는 문자열을 검증 등도 가능하다.

 정 표현식에는 일반 문자들과 특수 문자들이 혼용되어 사용된다.

 

\$

^

.

*

+

?

[

]

\.

 

 

 

 

‘\’ 시작하는 문자열을 제외한 모든 문자들은 보통 문자를 의미한다.

 

특수 문자들은 물론 고유한 기능을 갖고 있다. 예를 들어 ‘.’ 경우 라인 종결 문자를 제외한 어떤 문자와도 대응될 있다. 따라서 정규표현식s.n ‘sun’, ‘son’ 등과 같이 ‘s’ 시작하고 ‘n’ 으로 끝나는 자로 문자열 어떤 것이든 있다.

 

이와 같이 우리는 정규표현식에서 제공되는 여러 특수 문자들을 통해 줄의 시작 단어를 찾거나, 특정 범위의 문자 찾기, 대소문자 구별 없이 찾기, 정확히 일치하는 단어 찾기 등의 작업을 쉽게 처리할 있다.

 

java.util.regex 패키지에서 제공하는 정규표현식은 Perl 언어에서의 정규표현식 사용법과 같기 때문에 Perl 익숙한 사용자라면 같은 문법을 그대로 자바에도 적용할 있다. 만약 정규표현식에 익숙하지 않다면 아래 표가 많은 도움이 것이다.

Construct

Matches

Characters

 

X

The character x

\\

The backslash character

\0n

The character with octal value 0n (0 <= n <= 7)

\0nn

The character with octal value 0nn (0 <= n <= 7)

\0mnn

The character with octal value 0mnn (0 <= m <= 3, 0 <= n <= 7)

\xhh

The character with hexadecimal value 0xhh

\uhhhh

The character with hexadecimal value 0xhhhh

\t

The tab character ('\u0009')

\n

The newline (line feed) character ('\u000A')

\r

The carriage-return character ('\u000D')

\f

The form-feed character ('\u000C')

\a

The alert (bell) character ('\u0007')

\e

The escape character ('\u001B')

\cx

The control character corresponding to x

 

 

Character Classes

[abc]

a, b, or c (simple class)

[^abc]

Any character except a, b, or c (negation)

[a-zA-Z]

a through z or A through Z, inclusive (range)

[a-z-[bc]]

a through z, except for b and c: [ad-z] (subtraction)

[a-z-[m-p]]

a through z, except for m through p: [a-lq-z]

[a-z-[^def]]

d, e, or f

 

 

Predefined Character Classes

.

Any character (may or may not match line terminators)

\d

A digit: [0-9]

\D

A non-digit: [^0-9]

\s

A whitespace character: [ \t\n\x0B\f\r]

\S

A non-whitespace character: [^\s]

\w

A word character: [a-zA-Z_0-9]

\W

A non-word character: [^\w]

보다 자세한 설명과 예제는 J2SE 1.4 API 문서의 java.util.regex.Pattern 참조하도록 하자.

 

클래스와 메소드

 

Pattern 클래스

Pattern 객체는 Perl 문법과 비슷한 형태로 정의된 정규표현식을 나타낸다.

 

문자열로 정의한 정규표현식은 사용되기 전에 반드시 Pattern 클래스의 인스턴스로 컴파일되어야 한다. 컴파일된 패턴은 Matcher 객체를 만드는 사용되며, Matcher 객체는 임의의 입력 문자열이 패턴에 부합되는 여부를 판가름하는 기능을 담당한다. 또한 Pattern 객체들은 비상태유지 객체들이기 때문에 여러 개의 Matcher 객체들이 공유할 있다.

 

다음은 Pattern 클래스의 주요 메소드들에 대한 설명이다.

 

static Pattern compile(String regex) : 주어진 정규표현식으로부터 패턴을 만들어낸다(이를컴파일 한다 표현한다).
static Matcher matcher (CharSequence input) :
입력 캐릭터 시퀀스에서 패턴을 찾는 Matcher 객체를 만든다
.
String[] pattern() :
컴파일된 정규표현식을 String 형태로 반환한다
.
String[] split(CharSequence input) :
주어진 입력 캐릭터 시퀀스를 패턴에 따라 분리한다.

 

/*

 * split 메소드를 이용해 입력 시퀀스를 콤마(‘,’) 공백 문자를 기준으로

 * 나눈다.

 */

import java.util.regex.*;

 

public class Splitter {

    public static void main(String[] args) throws Exception {

        // 이상의 연속된 ‘,’ 공백문자를 의미하는 패턴을 만든다.

        Pattern p = Pattern.compile("[,{space}]+");

        // 패턴에 따라 입력 문자열을 쪼갠다.

        String[] result =

                 p.split("one,two, three   four ,  five");

        for (int i=0; i<result.length; i++)

            System.out.println(result[i]);

    }

}

 

Matcher 클래스

Matcher 객체는 특정한 문자열이 주어진 패턴과 일치하는가를 알아보는데 이용된다. Matcher 클래스의 입력값으로는 CharSequence라는 새로운 인터페이스가 사용되는데 이를 통해 다양한 형태의 입력 데이터로부터 문자 단위의 매칭 기능을 지원 받을 있다. 기본적으로 제공되는 CharSequence 객체들은 CharBuffer, String, StringBuffer 클래스가 있다.

 

Matcher 객체는 Pattern 객체의 matcher 메소드를 통해 얻어진다. Matcher 객체가 일단 만들어지면 주로 가지 목적으로 사용된다.

 

l        주어진 문자열 전체가 특정 패턴과 일치하는 가를 판단(matches).

l        주어진 문자열이 특정 패턴으로 시작하는가를 판단(lookingAt).

l        주어진 문자열에서 특정 패턴을 찾아낸다(find).

 

이들 메소드는 성공 true 실패 false 반환한다.

 

또한 특정 문자열을 찾아 새로운 문자열로 교체하는 기능도 제공된다.

 

appendRepalcement(StringBuffer sb, String replacement) 메소드는 일치하는 패턴이 나타날 때까지의 모든 문자들을 버퍼(sb) 옮기고 찾아진 문자열 대신 교체 문자열(replacement) 채워 넣는다. 또한 appendTail(StringBuffer sb) 메소드는 캐릭터 시퀀스의 현재 위치 이후의 문자들을 버퍼(sb) 복사해 넣는다. 다음 절에 나오는 예제 코드를 참고하도록 하자.

 

CharSequence 인터페이스

 

CharSequence 인터페이스는 다양한 형태의 캐릭터 시퀀스에 대해 일관적인 접근 방법을 제공하기 위해 새로 생겨났다. 기본적으로 String, StringBuffer, CharBuffer 클래스가 이를 구현하고 있으므로 적절한 것을 골라 사용하면 되며, 인터페이스가 간단하므로 필요하면 직접 이를 구현해 새로 하나 만들어도 된다.

 

Example Regex Scenarios

아래 코드는 J2SE 1.4 도큐먼트의 예제를 완성한 것이다.

 

/*

 * 코드는 "One dog, two dogs in the yard." 라는 문자열을

 * 표준 출력을 통해 출력한다.

 */

import java.util.regex.*;

 

public class Replacement {

    public static void main(String[] args)

                         throws Exception {

        // ‘cat’이라는 패턴 생성

        Pattern p = Pattern.compile("cat");

        // 입력 문자열과 함께 매쳐 클래스 생성

        Matcher m = p.matcher("one cat," +

                       " two cats in the yard");

        StringBuffer sb = new StringBuffer();

        boolean result = m.find();

        // 패턴과 일치하는 문자열을 ‘dog’으로 교체해가며

        // 새로운 문자열을 만든다.

        while(result) {

            m.appendReplacement(sb, "dog");

            result = m.find();

        }

        // 나머지 부분을 새로운 문자열 끝에 덫붙인다.

        m.appendTail(sb);

        System.out.println(sb.toString());

    }

}

 

메일 주소 포맷 확인

다음 코드는 주어진 입력 시퀀스가 메일 주소 포맷인가를 판단한다. 코드는 가능한 모든 형태의 메일 주소를 확인하는 것은 아니니 필요하면 코드를 추가해 사용하자.

 

/*

* 메일 주소에서 잘못된 문자 검사

*/

public class EmailValidation {

   public static void main(String[] args)

                                 throws Exception {

                                

      String input = "@sun.com";

      //메일 주소가 ‘.’이나 ‘@’ 같은 잘못된 문자로 시작하는 확인

      Pattern p = Pattern.compile("^\\.|^\\@");

      Matcher m = p.matcher(input);

      if (m.find())

         System.err.println("Email addresses don't start" +

                            " with dots or @ signs.");

      //’www.’으로 시작하는 주소를 찾는다.

      p = Pattern.compile("^www\\.");

      m = p.matcher(input);

      if (m.find()) {

        System.out.println("Email addresses don't start" +

                " with \"www.\", only web pages do.");

      }

      p = Pattern.compile("[^A-Za-z0-9\\.\\@_\\-~#]+");

      m = p.matcher(input);

      StringBuffer sb = new StringBuffer();

      boolean result = m.find();

      boolean deletedIllegalChars = false;

 

      while(result) {

         deletedIllegalChars = true;

         m.appendReplacement(sb, "");

         result = m.find();

      }

 

m.appendTail(sb);

 

      input = sb.toString();

 

      if (deletedIllegalChars) {

         System.out.println("It contained incorrect characters" +

                           " , such as spaces or commas.");

      }

   }

}

 

파일에서 제어 문자 제거

 

/*

* 지정된 파일에서 제어 문제를 찾아 제거한다.

*/

import java.util.regex.*;

import java.io.*;

 

public class Control {

    public static void main(String[] args)

                                 throws Exception {

                                

        //파일 객체 생성

        File fin = new File("fileName1");

        File fout = new File("fileName2");

        //입출력 스트림 생성

        FileInputStream fis =

                          new FileInputStream(fin);

        FileOutputStream fos =

                        new FileOutputStream(fout);

 

        BufferedReader in = new BufferedReader (

                       new InputStreamReader(fis));

        BufferedWriter out = new BufferedWriter (

                      new OutputStreamWriter(fos));

 

        // 제어문자를 의미하는 패턴 생성

        Pattern p = Pattern.compile("{cntrl}");

        Matcher m = p.matcher("");

        String aLine = null;

        while((aLine = in.readLine()) != null) {

            m.reset(aLine);

            //제어 문자들을 문자열로 대체

            String result = m.replaceAll("");

            out.write(result);

            out.newLine();

        }

        in.close();

        out.close();

    }

}

 

파일 검색

 

/*

 * .java 파일에서 주석을 찾아 출력한다.

 */

import java.util.regex.*;

import java.io.*;

import java.nio.*;

import java.nio.charset.*;

import java.nio.channels.*;

 

public class CharBufferExample {

    public static void main(String[] args) throws Exception {

        // 주석을 나타내는 패턴 생성

        Pattern p =

            Pattern.compile("//.*$", Pattern.MULTILINE);

       

        // 소스파일

        File f = new File("Replacement.java");

        FileInputStream fis = new FileInputStream(f);

        FileChannel fc = fis.getChannel();

       

        // 소스 파일로부터 CharBuffer 생성

        ByteBuffer bb =

            fc.map(FileChannel.MAP_RO, 0, (int)fc.size());

        Charset cs = Charset.forName("8859_1");

        CharsetDecoder cd = cs.newDecoder();

        CharBuffer cb = cd.decode(bb);

       

        // 매칭 작업 수행

        Matcher m = p.matcher(cb);

        while (m.find())

            System.out.println("Found comment: "+m.group());

    }

}

 결론

이제 자바의 패턴 매칭 능력은 다른 언어 부럽지 않은 수준까지 다다르게 되었다. 정규 표현식은 데이터베이스 또는 다른 어플리케이션에 데이터를 넘기기 전에 데이터가 정확한 포맷을 지키고 있는 검증할 , 또는 다양한 목적의 관리 작업에 사용될 있을 것이다. 간단히 말해 패턴 매칭이 필요한 모든 자바 프로그램에서 이제 정규표현식을 사용할 있게 되었다.