정규표현식(Regular Expression) 혹은 Regex란 특별한 의미를 갖는 문자, 숫자, 기호등을 조합해 만들어낸, 특정 문자열과 매칭되는 패턴을 의미한다.


Regex Grammar

정규식의 모든 문법을 외워야 할 필요는 없다. 아래 표의 기초 문법을 익히고, 필요할 때 찾아보도록 하자.

Java Regex 에서 모든 문법을 확인 할 수 있다.

Group, Range

Sign Meaning
| or
( ) group
(?: ) non capturing group
[ ] any character in char set
[^ ] except char set

Quantifiers

Sign Meaning
* 0 or more
+ 1 or more
? 0 or 1
{n} n times
{n, } at least n times
{n, m} n ~ m

Boundary

Sign Meaning
^ beginning of sentence
$ end of sentence
\b beginning or end of word
\B middle of word

Character classes

Sign Meaning
a-Z, 0-9, etc alphabet, digit and other non meta character itself
\ escape
. any character except \n
\d digit
\D not digit
\w alphanumeric and underscore
\W not alphanumeric and underscore
\s space
\S not space

Meta character ?

  정규식에서는 . * + ? { } ( ) [ ] | \ ^ $와 같은 문자들을 특별한 용도로 사용하고 있기 때문에, 문자로서 이런 특수문자 들을 사용하려면 앞에 ` \ ` 를 붙여야 한다. 예를 들어 \* 는 반복 횟수를 나타내지 않는 * 그 자체이다.

Easy regex example

  • 숫자 ^[0-9]*$

^$는 정규식의 시작과 끝을 의미한다. [0-9]0, 1, 2, ..., 9을 의미한다. *s는 앞의 문자가 없거나, 무한히 많은 경우를 의미한다. 따라서 ^[0-9]*$ 는 숫자로만 구성된 문자열을 의미한다.

  • 영문자 ^[a-zA-Z]*$
  • 한글 ^[가-힣]*$

Regex in Java

regex는 문자열이 정해진 형식으로 구성되어져 있는지 검증 하거나, 특정 형식(전화번호, 이메일)을 만족하는 문자를 찾기 위해 사용된다. Java는 정규식을 사용하기 위해 PatternMatcher 클래스를 지원해주고 있다.

주어진 문자열이 특정 형식을 준수하는지는 Pattern 클래스의 matches 메서드를 이용한다.

String regex = "^[0-9]*$"; // 숫자만
String input1 = "123456";
String input2 = "12345a";

boolean result1 = Pattern.matches(regex, input1); // true
boolean result2 = Pattern.matches(regex, input2); // false

텍스트로 부터 규칙을 만족하는 문자를 모두 찾으려면 정규식을 패턴화 시킨 후, Matcher 객체를 생성해 사용한다.

String regex = "[a-zA-Z]+"; // 알파벳만
String input = "..."
Pattern pattern = Pattern.complie(regex);
Matcher matcher = pattern.matcher(input);
List<String> result = matcher.results()
				.map(MatchResult::group)
				.collect(Collectors.toList());

위 코드는 input 안에 존재하는 알파벳으로만 구성된 단어들을 리스트 형태로 추출한다.


e.g. 이메일, 전화번호 추출하기

String input = "This is the sample text for regex practice. " +
  "it contains some email addresses and phone numbers. " +
  "Email addresses contain at sign(@), like hello@naver.com, someone@hello.co.kr. " +
  "The phone number could have a hyphen like 017-1234-5678, or not. " +
  "Some people use the dot instead of hyphen, like 02.123.4567 " +
  "or just use the space like 010 1234 4321." 

String emailRegex = "[a-zA-Z0-9.]+@[a-zA-Z0-9.]+";
String phoneNumRegex = "\\d{2,3}[- .]\\d{3,4}[- .]\\d{4}";

Pattern emailPattern = Pattern.compile(emailRegex);
Matcher emailMatcher = emailPattern.matcher(sample);
List<String> emailList = emailMatcher
			        .results()
			        .map(MatchResult::group)
			        .collect(Collectors.toList());

Pattern phoneNumberPattern = Pattern.compile(phoneNumRegex);
Matcher phoneNumberMather = phoneNumberPattern.matcher(sample);
List<String> phoneNumList = phoneNumberMather
			        .results()
			        .map(MatchResult::group)
			        .collect(Collectors.toList());

System.out.println("emailList : " + emailList);
System.out.println("phoneNumList : " + phoneNumList);

/*
  emailList : [hello@naver.com, someone@hello.co.kr]
  phoneNumList : [017-1234-5678, 02.123.4567, 010 1234 4321]
*/

Group

정규식과 매칭되는 항목의 일부분 만을 이용하고 싶을 때는 그룹을 이용한다.

String phoneNumRegex = "(\\d{2,3})[- .](\\d{3,4})[- .](\\d{4})";
Pattern phoneNumberPattern = Pattern.compile(phoneNumRegex);
Matcher phoneNumberMather = phoneNumberPattern.matcher(sample);
List<String> phoneNumList = phoneNumberMather
			        .results()
			        .map(m -> m.group(1)) 
			        .collect(Collectors.toList());
System.out.println(phoneNumList)
// [017, 02, 010]

Replace

정규식와 매치되는 특정 항목들의 값을 변경하고 싶을 때는, replaceAll 메서드를 사용한다.

e.g. 이메일과 전화번호를 감춰보자

String input = "This is the sample text for regex practice. " +
  "it contains some email addresses and phone numbers. " +
  "Email addresses contain at sign(@), like hello@naver.com, someone@hello.co.kr. " +
  "The phone number could have a hyphen like 017-1234-5678, or not. " +
  "Some people use the dot instead of hyphen, like 02.123.4567 " +
  "or just use the space like 010 1234 4321."
  
String regex = "[\\w-.]+@[a-zA-Z0-9.]+|\\d{2,3}[- .]\\d{3,4}[- .]\\d{4}";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(sample);
String result = matcher.replaceAll(" **** ");

System.out.println(result);

/*
This is the sample text for regex practice. it contains some of email addresses and phone numbers.
Email addresses contain @, like  **** ,  **** The phone number could have hyphen like  **** , or not.
Some people use the dot like  ****  or use just space like  **** 
*/

Flag

플래그는 정규 표현식이 작동하는 방식을 바꾸기 위해 사용한다.

  • Pattern.CASE_INSENSITIVE or i: 대소 문자를 구분하지 않도록 변경한다.
  • Pattern.MULTILINE or m: ^ &가 전체 입력이 아닌, 각 줄의 시작과 끝에 일치하도록 변경한다.
  • Pattern.DOTALL or s: . 이 개행 문자 \n을 포함하도록 변경한다.
String regex = "aBc";
String input = "...";
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITVE); 
boolean isMatch = pattern.matcher(input).find();

위와 같이 CASE_INSENSITIVE 플래그를 적용하면, 정규식은 대소문자를 구분하지 않으므로 aBc, aBC, ABc, ABC, AbC등의 문자를 input 으로 사용해도 pattern.matcher(input).find()메서드는 true를 반환한다.

플래그를 아래와 같이 정규식에 포함시킬 수도 있다. 이때는 (?<flag>:<regex>) 와 같은 문법을 사용한다.

String regex = "(?i:aBc)";
String input = "...";
boolean isMatch = Pattern.matches(regex, input)

Exercise

e.g 2021 카카오 : 신규 아이디 추천 문제 풀어보기

public class Main {
    public static void main(String[] args) {
        String id = "...!@BaT#*..y.AdeFGhiJ.kLm";
        System.out.println("step 0 :" + id);

        // step 1. 모든 대분자를 대응되는 소문자로 치환한다.
        id = id.toLowerCase();
        System.out.println("step 1 :" + id);

        //step 2. 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자를 제거
        id = id.replaceAll("[^a-zA-Z0-9_.\\-]", "");
        System.out.println("step 2 :" + id);

        // step 3. 마침표(.)가 2번 이상 연속된 부분은 하나의 마침표로 치환한다.
        id = id.replaceAll("\\.+", ".");
        System.out.println("step 3 : " + id);

        // step 4. 마침표(.)가 처음이나 끝에 위치하면 제거한다.
        id = id.replaceAll("^\\.|\\.$", "");
        System.out.println("step 4 : " + id);

        // step 5. id가 빈 문자열 이라면, id 에 "a" 를 대입한다.
        if(id.isBlank()) id = "a";
        System.out.println("step 5 : " + id);

        // step 6-1. id의 길이가 16자 이상이면, 첫 15자를 제외한 나머지 문자를 모두 제거한다.
        if(id.length() >= 16) {
            id = id.substring(0, 15);
        }
        System.out.println("step 6-1 : " + id);

        // step 6-2. 15번째 문자에 마침표(.)가 오는 경우 삭제
        id = id.replaceAll("\\.$", "");
        System.out.println("step 6-2 : " + id);

        // step 7. id의 길이가 2자 이하라면, 3글자가 될 때까지 마지막 문자를 더해준다.
        while(id.length() < 3) {
            id = id + id.charAt(id.length()-1);
        }
        System.out.println("step 7 : " + id);
    }
/*
	step 0 : ...!@BaT#*..y.AdeFGhiJ.kLm
	step 1 : ...!@bat#*..y.adefghij.klm
	step 2 : ...bat..y.adefghij.klm
	step 3 : .bat.y.adefghij.klm
	step 4 : bat.y.adefghij.klm
	step 5 : bat.y.adefghij.klm
	step 6-1 : bat.y.adefghij.
	step 6-2 : bat.y.adefghij
	step 7 : bat.y.adefghij
*/
}

정규식 연습 사이트 에서 연습해보는 것을 적극 추천한다.

카테고리:

업데이트:

댓글남기기