▸JAVA/기본 문법

정규표현식 (작성 방법 및 전방탐색, 후방탐색) [2/2]

코데방 2019. 12. 10.
728x90

Email 주소를 검사하는 식을 예제로 들어보겠습니다. 알아보기 쉽게 생략할 수 있는 역슬레시는 생략했지만, 실제로 사용할 때는 특수문자에 모두 역슬레시를 붙여 일반 문자임을 정확히 명시해두는 것이 좋습니다.

 

※ Email 규칙검사 예제

1. [^-_][\\w-_]+ → ID 검사

- \\w == [a-z][A-Z][0-9], 하이픈(-), 언더바(_)만 허용함

- 시작문자에 하이픈(-)및 언더바(_)는 불허함

- 메타문자 +를 붙여 한글자 이상이 무조건 있어야 하도록 함

- 하이픈은 [ ] 에서 사용하는 범위지정 메타문자이지만, 앞뒤 범위가 없으면 일반 문자로 인식

 

2. [^-_]@[^-_]

- 위의 검사식을 만족했고, 만족하지 못하는 문자가 나오면 다음 식으로 넘어가서 검사함

- 아이디 다음은 골뱅이 문자 하나만 허용함

- 범위지정이 아니고 메타문자가 아닐 경우 괄호 없이 사용 가능

   ※ 하지만 꼭 주의할 것 (이전 글 주의사항 참조)

- @ 앞뒤로는 하이픈, 언더바를 불허함

 

3. [a-z0-9-.]+[^-.] → 도메인 검사

- 소문자와 숫자, 하이픈(-), 마침표(.)를 한글자 이상 허용함

- 마지막 글자도 하이픈 및 마침표를 불허함

- 마침표는 [ ] 안에 들어갈 경우 일반문자로 인식 (그래도 \\붙여주는게 가장 확실함)

 

package study.first;
import java.util.regex.Pattern;
public class Main {
	public static void main(String[] args) {

		// 정규표현식 작성
		String a = "[^-_][\\w-_]+[^-_]@[^-_][a-z0-9-.]+[^-.]";

		// 검사할 문자
		String b = "co-de_vanG@naver.com";		
		
		// 패턴 검사 결과
		System.out.println(Pattern.matches(a, b));  // true
	}
}

 


 

잘 작동하지만 문제가 하나 있습니다. 도메인은 다중 계층을 허용한다는 것입니다. 그리고 각 도메인 계층에서도 처음과 끝에 하이픈을 불허합니다.

→ CoDe_vang@naver-.com (틀림)

→ CoDe_vang@edu-24.-naver.com (틀림)

→ CoDe_vang@edu-24.naver.-co.kr (틀림)

→ CoDe_vang@edu-24.naver.co.-kr (틀림)

정확히 계층이 몇 개가 나올지 알 수 없기 때문에 형식이 완전히 고정돼 있지 않은 경우(불규칙한 횟수로 반복 등), 하나의 정규표현식으로 모든 규칙을 정확히 검증하기는 어렵습니다.

위와 같은 경우는 아래 코드와 같이 정규표현식 외 로직을 섞어서 해결할 수 있습니다. 정규표현식으로 해결할 수 있는 건 최대한 다 해결하고, 그 외의 것들은 메소드를 짜서 재검증을 할 수 있습니다. 너무 정규표현식으로만 검증하려고 하는 것은 정확하지 않을 수 있습니다. 더 정확하게 하려면 마침표나 하이픈이 연속으로 나오지 못하게 하는 등의 로직을 추가할 수 있습니다.

 

package study.first;

import java.util.regex.Pattern;

public class Main {
	public static void main(String[] args) {

		// 정규표현식 작성
		String a = "[^-_][\\w-_]+[^-_]@[^-_][a-z0-9-.]+[^-_.]";

		// 검사할 문자
		String b = "co-de_vanG@naver.com";
		String c = "CoDe_vang@naver-.com";
		String d = "CoDe_vang@edu-24.-naver.com";
		String e = "CoDe_vang@edu-24.naver.-co.kr";
		String f = "CoDe_vang@edu-24.naver.co.-kr";

		// 정규표현식 패턴검사 결과
		System.out.println(Pattern.matches(a, b)); // true
		System.out.println(Pattern.matches(a, c)); // true
		System.out.println(Pattern.matches(a, d)); // true
		System.out.println(Pattern.matches(a, e)); // true
		System.out.println(Pattern.matches(a, f)); // true

		System.out.println("// 패턴 검사 및 도메인 양 쪽 하이픈 검사 추가");
		System.out.println(domainCheck(a, b));	// true
		System.out.println(domainCheck(a, c));	// false
		System.out.println(domainCheck(a, d));	// false
		System.out.println(domainCheck(a, e));	// false
		System.out.println(domainCheck(a, f));	// false
	}

	// 도메인 양 쪽 하이픈 검사
	static boolean domainCheck(String str1, String str2) {

		// 정규표현식 검사
		if (!Pattern.matches(str1, str2))
			return false;

		// 하이픈 검사
		int strLength = str2.length();
		for (int i = 0; i < strLength; i++) {

			if (str2.charAt(i) == '@') {
				for (int j = i + 1; j < strLength; j++) {
					if (str2.charAt(j) == '.' && 
							(str2.charAt(j - 1) == '-' 
							|| str2.charAt(j + 1) == '-'))
						return false;
				}
				
				break;
			}
		}
		return true;
	}
}

 


 

[ 전방 탐색 ]

  • 지정한 문자(열) 직전의 문자(열)을 탐색
  • 작성 방법 : 검사할문자(?=지정할문자)
  • 작성 방법 : (?=(검사할문자)(지정할문자))

위와 같이 사용합니다. 그냥 (?=:) 없이 [:]를 사용할 수 있지만 이 경우 :까지 검색결과로 포함됩니다. (?=:)는 :를 검색결과에 포함하지 않고 그 직전 문자들만 검색해줍니다. 아래는 이전글에서 소개했던 정규표현식 검색 사이트(https://regex101.com/)에서의 결과입니다.

첫 번째와 두 번째는 같은 의미를 가지고, 세 번째는 'h','t','p','s'에 속한 문자가 1개 이상 있는 것을 검색하라는 의미가 되어 "https"까지 검색이 된 모습입니다.

 

 


 

간단한 예제를 하나 살펴보겠습니다. 아래 정규표현식은 "com"앞에 마침표(.)가 한 개 있어야 한다는 의미입니다. 일단 전방 탐색을 수행해서 true가 나오면 뒤의 구문을 다시 처음부터 검사합니다. 전방 탐색 하나만 단독으로는 사용할 수는 없고 문자 검사식과 함께 사용해야 합니다.

그리고 검색과는 다르게, 지정한 문자 앞이 모두 일치해야 true가 됩니다. 아래와 같이 "com 앞에 마침표가 1개 있다" 라는 의미로 써주면 정확한 검사가 되지 않습니다.

package study.first;
import java.util.regex.Pattern;
public class Main {
	public static void main(String[] args) {

		// 정규표현식 작성
		String a = "(?=[\\.](com))[\\S]+";

		// 검사할 문자
		String b = "http://naver.com";  // false (앞에 마침표 하나만 있는게 아니라 false)		
		String c = ".com";  // true (앞에 마침표가 하나만 있으므로 true)
		
		// 패턴 검사 결과
		System.out.println(Pattern.matches(a, b));
		System.out.println(Pattern.matches(a, c));
	}
}

 

 

이 경우 아래와 같이 써주면 됩니다. 메타문자로서의 마침표(.)는 역슬레시를 제외한 모든 문자를 의미합니다. 따라서 ( .* ) 이란 표현은 "아무 문자가 아예 없거나 있으면 true"라는 뜻으로 역슬레시를 제외하면 없어도 그만, 있어도 그만이 됩니다. 그 앞의 내용도 제한하고 싶은 범위가 있다면 .* 대신 다른 조건으로 사용해도 됩니다.

아래 식의 의미는 "com 앞에 마침표가 1개 있어야 하는데 그 앞에는 뭐가 있건 없건 상관안함, 전방탐색이 true라면 전체 문자열은 공백이 아닌 숫자가 1개 이상 있으면 됨" 이라는 의미가 되어 모두 true가 됩니다. 뒤에 [\\S]+ 와 같은 문자 검사식이 하나라도 없이 전방 탐색(및 후방 탐색)을 사용하면 모두 false가 반환됩니다. com 앞에 마침표가 있어서 전방 탐색 자체는 true인데 다시 .com을 허용하는 조건이 없기 때문입니다.

package study.first;
import java.util.regex.Pattern;
public class Main {
	public static void main(String[] args) {

		// 정규표현식 작성
		String a = "(?=.*[\\.](com))[\\S]+";

		// 검사할 문자
		String b = "http://naver.com";	// true		
		String c = ".com";		// true
		
		// 패턴 검사 결과
		System.out.println(Pattern.matches(a, b));
		System.out.println(Pattern.matches(a, c));
	}
}

 

※ 부정형 전방 탐색

- 지정한 문자 직전에는 패턴에 맞아도 인정하지 않겠다는 의미
- 검색할 때 주로 사용됨
- 작성 방법 : 검사할문자(?![지정한문자])

 


 

[ 후방 탐색 ]

  • 지정한 문자(열) 직후에 있는 문자(열)을 탐색
  • 작성 방법 : (?<=지정할문자)검사할문자

전방 탐색과 약간 문법만 다를 뿐 같은 개념입니다. 아래는 검색 패턴이고 코드는 간단한 검사식 예제 입니다. "naver 뒤에는 무조건 마침표 1개가 와야하고, 앞쪽은 공백이 아닌 문자가 1개 이상, 마침표 뒤쪽은 아무 문자나 1개 이상 와야 한다" 라는 의미가 됩니다. 역시 검색할 때 사용하면 지정할 문자 자체는 포함되지 않습니다.

 

package study.first;
import java.util.regex.Pattern;
public class Main {
	public static void main(String[] args) {

		// 정규표현식 작성
		String a = "[\\S]+(?<=(naver))[\\.].+";

		// 검사할 문자
		String b = "http://naver.com";	// true		
		
		// 패턴 검사 결과
		System.out.println(Pattern.matches(a, b));
	}
}

 

※ 부정형 후방 탐색

- 지정한 문자 직후에는 패턴에 맞아도 인정하지 않겠다는 의미
- 검색할 때 주로 사용됨
- 작성 방법 : (?<![지정문자])검사할문자

 


 

전후방 탐색은 문자열을 검색할 때 검색 기준값을 포함하지 않기 위한 장치입니다. 하지만 이를 이용해서 문자열 검증에 사용할 수 있습니다. 기준값의 앞뒤를 살핀다는 말은 일단 "기준값이 있어야 한다"가 전제돼야 합니다. 따라서 반대로 생각하면 "기준값 자체가 하나도 없으면 false"라는 말입니다.

이런 성질을 패스워드 규칙 지정에 활용할 수 있습니다. "8자리 이상, 대소문자 각 1개 이상 포함, 특수문자 1개 이상 포함, 숫자 1개 이상 포함" 이라는 규칙을 만들 경우 어느 위치에서 각 종류의 문자가 나올지 알 수 없습니다. 사실 이 규칙은 그냥 반복문 로직으로 해결해도 별로 어렵진 않지만 전후방 탐색 기법을 이용하면 정규표현식으로도 검증이 가능합니다.

 

※ 패스워드 규칙검사 예제

1. (?=.*[A-Z]) → 대문자 검사

   - 대문자 1개 앞에 아무거나 있거나 없거나 (.*)

   - 앞에 뭐가 있던 상관없고 대문자가 1개라도 있어야 true가 되기 때문에 대문자 검사식이 됨

2. ​(?=.*[a-z]) → 소문자 검사

3. (?=.*[0-9]) → 숫자 검사

4. (?=.*[\\W^\\s]) → 공백이 아닌 (문자,숫자가 아닌)문자(특수문자) 검사

5. [\\S^\\\\]{8,} → 허용 문자 및 최소 횟수 설정

   - 공백이 아니면서 역슬레쉬가 아닌 문자가 8개 이상 연속돼야 함

 

package study.first;

import java.util.regex.Pattern;

public class Main {
	public static void main(String[] args) {

		// 정규표현식 작성
		String a = "(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[\\W^\\s])[\\S^\\\\]{8,}";

		// 검사할 문자
		String b = "password!!";	// false
		String c = "p@ss1Word";		// true


		// 정규표현식 패턴검사 결과
		System.out.println(Pattern.matches(a, b)); // false
		System.out.println(Pattern.matches(a, c)); // true
	}
}

 

728x90

댓글

💲 추천 글