✏️개요
이번에 작성할 게시글의 주제는 정규 표현식이다. 정규 표현식은 학부생 시절부터 여러 언어를 학습할 때마다 등장하는 챕터였는데, 필자는 개인적으로 Thread와 마찬가지로 기피했던 챕터였다. Thread는 내용 자체가 이해하기 어려웠다면, 정규 표현식은 이해하고 싶지가 않았다.
^\\d {3}-\\d {3,4}-\\d {4}$ <= 인터넷에서 발견할 수 있는 자주 쓰이는 휴대전화 정규 표현식이다.
한눈에 보더라도 이해하기 위해 노력하고 싶지 않게 생겼다.
이런 필자가 정규 표현식에 대해서 글을 쓰고자 하는 이유는 역시 실무에서의 코드를 분석하는 과정에 있었다. 취업 준비 과정에서의 개인 프로젝트는 기능 구현에 몰두했기 때문에 이와 같은 정규 표현식에 대한 필요성을 못 느껴 사용할 엄두도 내지 않았지만, 실무 코드의 로그인, IP, MAC 검증, 이메일 등등 수많은 곳에서 사용되고 있는 정규 표현식을 보고 이에 대한 이해와 필요할 때 참고할 수 있는 레퍼런스의 필요성을 느끼게 되어 작성하고자 마음먹었다.
❗ 본 게시글은 필자 개인적인 의견이므로 틀린 부분이 있을 수 있습니다. 댓글을 통해 지적해주시면 감사하겠습니다.
✏️Regular Expression, 정규 표현식
정규 표현식이란, 문자열에서 특정한 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어로서 흔히 약어로 regex로 표현하기도 한다. 정규 표현식을 사용한다면, 일반적인 조건문을 굉장히 많이 사용해야 하는 경우보다 더욱 간단하게 우리가 원하는 문자열을 뽑아낼 수 있다고 하며 주로 문자열의 검색과 치환을 위한 용도로 사용된다고 한다.
x로 시작하는 문자열을 고른다고 할 때, 조건문을 사용하면 아마 if(str.charAt(0) == 'x')이 될 텐데, 정규 표현식을 이용하면 ^x가 된다. 훨씬 간단하다.
필자와 같이 웹 애플리케이션을 개발할 때 (아직 입사한 지 2개월밖에 안됐지만), 이와 같은 정규 표현식이 사용자가 입력하는 정보의 유효성을 검사할 때 많이 사용된다고 한다. 웹뿐만 아니라 사용자에게 데이터를 입력받는 어떠한 애플리케이션이든 이와 같은 유효성 검사가 필수적이라고 생각된다.
그 이유는 개발 시 사용자 입력 데이터에 대한 포맷이 존재할 텐데 이 포맷을 다양한 방식으로 사용자에게 아무리 전달하더라도, 기상천외한 방법으로 데이터를 입력하는 경우가 실제로 많이 발생한다고 한다. 이런 경우가 발생하면, 내부적으로 어떠한 오류를 일으켜도 이상하지 않을 것이다.
정규 표현식의 문법은 어차피 다 외우지도 못하니 정리된 표를 기반으로 여러 예제를 한 번 살펴봐야겠다.
정규 표현식 문법
표현식 | 설명 |
^ | 문자열의 시작 |
$ | 문자열의 종료 |
. | 임의의 한 문자 (문자의 종류 가리지 않음, 단, \ 는 넣을 수 없음) |
* | 앞 문자가 없을 수도 무한정 많을 수도 있음 |
+ | 앞 문자가 하나 이상 |
? | 앞 문자가 없거나 하나있음 |
[] | 문자의 집합이나 범위를 나타내며 두 문자 사이는 - 기호로 범위를 나타낸다. []내에서 ^가 선행하여 존재하면 not 을 나타낸다. |
{} | 횟수 또는 범위를 나타낸다. |
() | 소괄호 안의 문자를 하나의 문자로 인식 |
| | 패턴 안에서 or 연산을 수행할 때 사용 |
\s | 공백 문자 |
\S | 공백 문자가 아닌 나머지 문자 |
\w | 알파벳이나 숫자 |
\W | 알파벳이나 숫자를 제외한 문자 |
\d | 숫자 [0-9]와 동일 |
\D | 숫자를 제외한 모든 문자 |
\ | 정규표현식 역슬래시(\)는 확장 문자. 역슬래시 다음에 일반 문자가 오면 특수문자로 취급하고 역슬래시 다음에 특수문자가 오면 그 문자 자체를 의미 |
(?i) | 앞 부분에 (?i) 라는 옵션을 넣어주면 대소문자를 구분하지 않음 |
자주 쓰이는 정규 표현식
분류 | 정규식 패턴 |
숫자 | ^[0-9]*$ |
영문자 | ^[a-zA-Z]*$ |
한글 | ^[가-힣]*$ |
영어&숫자 | ^[a-zA-Z0-9]*$ |
비밀번호 (숫자, 문자 포함의 6~12자리 이내) | ^[A-Za-z0-9]{6,12}$ |
비밀번호 (숫자, 문자, 특수문자 포함 8~15자리 이내) | ^.*(?=^.{8,15}$)(?=.*\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&+=]).*$ |
이메일 | ^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$ |
휴대전화 | ^\\d{3}-\\d{3,4}-\\d{4}$ |
일반전화 | ^\\d{2,3}-\\d{3,4}-\\d{4}$ |
주민등록번호 | \d{6} \- [1-4]\d{6} |
파일확장자 | ^\\S+.(?i)(txt|pdf|hwp|xls)$ |
이중 파일확장자 | (.+?)((\\.tar)?\\.gz)$ |
표 조차도 복잡하다. 이제 예제들을 한 번 살펴보자.
✏️ 예제
예제 1(휴대전화) : ^\\d {3}-\\d {3,4}-\\d {4}$
- ^ : 패턴 문자열의 시작.
- \ : 뒤의 \d를 사용하기 위해서 사용, Java에서 \를 사용하기 위한 escape 문자.
- \d : [0-9]와 동일, 숫자.
- {3} : 숫자가 3개 온다.
- - : 그냥 표현되는 문자열.
- \, \d : 위와 동일.
- {3,4} : 숫자가 3~4개가 온다.
- -, \, \d : 위와 동일.
- {4} : 숫자가 4개 온다.
- $ : 패턴 문자열의 종료.
예제 2 (이메일) : ^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$
- ^ : 패턴 문자열의 시작.
- [0-9a-zA-Z] : 문자의 범위, 숫자와 알파벳.
- () : 소괄호 안의 문자는 1개의 문자로 인식.
- [-_.] : - 또는 _ 또는 . 중 1개.
- ? : [-_.]가 없거나 한 개 있음.
- [0-9a-zA-Z] : 숫자 또는 알파벳.
- * : 위의 소괄호 안의 패턴을 지킨 문자 1개가 (1개인 이유는 소괄호 때문) 없거나 무수히 많을 수 있다.
- @ : 그냥 표현되는 문자열.
- [0-9a-zA-Z]([-_.]?[0-9a-zA-Z])* : 위와 동일.
- . : \를 제외한 임의의 한 문자. ( 이건 의문이다.)
- [a-zA-Z]{2,3} : 알파벳이 2개 또는 3개.
- $ : 패턴 문자열의 종료.
이메일 예제를 보면, . 부분이 \를 제외한 임의의 한 문자로 인식되는데, 그 결과 아래와 같은 경우가 나오게 된다.
이것을 일반 . 문자로 인식시키기 위해서는 \. 과 같이 써야 한다.
✏️ Java에서의 정규 표현식
여기까지가 일반적으로 대부분의 언어에서 공통적으로 사용될 수 있는 정규 표현식의 문법 자체를 알아보았다. 이 단락부터는 Java에서 정규 표현식을 이용하여 어떻게 유효성 검증을 할 수 있는지 알아보았다.
Java에서 정규 표현식을 이용해 유효성 검증을 하기 위해서는 java.util.regex 패키지의 API를 사용해야 한다. 그리고 그중 Pattern 클래스와 Matcher 클래스를 주로 이용한다고 한다.
✏️ Pattern과 Matcher 클래스
앞서 살펴보았듯이, 정규 표현식은 문자열 형태로 작성되므로, 이것만 가지고는 정규 표현식으로서의 기능을 수행할 수 없을 것이다. 작성된 문자열을 정규 표현식으로서 기능하게 하기 위해선 Pattern 객체로의 컴파일 과정이 필요하다.
Pattern 클래스에는 다양한 메서드가 있지만, 문자열로 된 정규 표현식을 Pattern 객체로 컴파일하는 메서드는 compile()을 사용한다.
물론, 이 과정에는 다양한 방법이 있지만, 필자는 실무에서 작성되어 있었던 기본적인 포맷을 따르고자 한다.
Pattern 객체를 만든 뒤, 할 일은 사용자가 입력한 정보와 비교하기 위해 사용될 Matcher 객체를 얻어야 한다. 이 Matcher 객체는 앞서 얻은 Pattern 객체의 matcher() 메서드를 이용해 얻을 수 있다. 이후, 사용자에게 입력받은 데이터를 Matcher 객체의 matches() 메서드를 통해 boolean 값을 얻어 판단할 수 있다.
위의 내용을 기준으로 사용자에게 입력받은 이메일의 유효성을 검증한다고 가정해보면,
- Pattern email = Pattern.compile("^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$");
- Matcher matcherEmail = email.matcher(사용자 입력 이메일);
- boolean result = matcherEmail.matches();
여기서 compile() 메서드는 클래스 메서드이며, Matcher 객체를 만들 때 사용자에게 입력받은 데이터를 인자로 넘겨주어야 한다는 것에 주의해야 한다.
✏️ 마치며
정규 표현식에 대한 막연한 두려움이 있었는데, 이제 좀 볼 줄 아는 것 같다. 그래도 지속적으로 참고해야겠다.