8 분 소요

아래의 코드는 “1900-01-01” 과 같은 생년월일을 검색할 수 있는 정규식이다.

/^(19[0-9][0-9]|20\d{2})-(0[0-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/



…??????

정규식이란?

“텍스트에서 주어진 패턴과 일치하는 문자를 찾아내는 표현식”

Regular Expression, regex, regexp, rational expression…. 등으로 표현하는 정규식은 수학적 표기법에서부터 ed, grep, LIKE 등과 같이 개발자에게 기능으로까지 발전해 왔다.

정규식은 패턴에 기반하여 일치하는 문자를 찾아내는 기술인만큼 패턴이 전부라 할 수 있다.

패턴은 사전에 정의한 기호들로 규칙을 설정하는데, 예를 들어 ^는 시작을 의미하기도 하며, 그룹 문자 [' '] 안에서는 not을 의미하기도 한다.

이처럼 의미를 알지 못하면 사용하기 난해하다.

가독성이 떨어지면서도, 사용할 수 있는 사람이라면 굳이 길게 코드를 짜지 않고 정규식만으로 원하는 값을 추출할 수 있어서 익혀두는 것이 좋겠다.

기본 컨셉

“찾으려는 문자의 조건을 정의한다.”

정규식은 기본적으로

  1. Boolean(or)
  2. Grouping
  3. Quantification
  4. Wildcard

의 구조를 활용하여 조건을 정의해야 한다..

이는 정규식에서 특정한 의미로 사용 되는 문자인 메타문자 |, (), {}, . 등을 활용하여 작성하게 된다.

기본 컨셉에 활용되는 메타문자는 아래와 같다.

표현식 의미 설명
| OR, 혹은 gray\|gery는 “gray” 혹은 “grey”를 인식해 낼 것이다.
( ) Grouping, 그룹 범위(Scope)를 정한다.

gray\|gerygr(a\|e)y로 표현할 수 있다.
? 0개 혹은 1개 colou?r은 ‘u’가 없거나, 1개가 있는지를 확인.

“color”과 “colour”를 인식할 수 있다.
* 0개 혹은 여러 개 ab*c ‘b’가 없거나, 여러 개 있는 것을 확인.

“ac”, “abc”, “abbc”, “abbbc” 혹은 b가 그 이상 있어도 인식할 수 있다.
{n} 정확히 n개 ab{1}c는 “abc”만 인식할 수 있다.
{min, } 최소한 min개(min~) ab{2,}c는 “abbc”, “abbbc” 등과 같이 “b”가 2번 이상 포함된 문자열만 인식한다.
{, max} 최대 max개(~max) ab{,2}c는 “ac”, “abc”, “abbc”를 인식한다.
{min, max} min~max ab{1,3}c는 “abc”, “abbc”, “abbbc”를 인식한다.
. 아무 문자 a.b는 ‘a’와 ‘b’ 사이에 아무 문자 1개가 포함되어 있는지 확인한다.

a.*b는 아무 문자가 없거나 여러 개 있을 때를 의미한다.

좀 더 복잡한 구성은 IEEE POSIX 표준에서 설명하는 메타 문자들로 작성할 수 있으며, 일단은 간단하게 예를 들어보겠다.

여기 “Handel”, “Händel”, “Haendel” 3개의 문자열이 있다.

우리는 문자와 연산자 등을 사용하여 이와 매치 되는 패턴을 작성해야 한다.

이를 모두 만족시키는 정규식 패턴은

/H(ä|ae?)ndel/

혹은

/(Hän|Han|Haen)del/

와 같이 여러 가지 방법으로 패턴을 작성할 수 있다.

기본적으로 가독성이 떨어지니 이왕이면 필요한 기능을 명확히 보여줄 수 있는 정규식을 작성하자.

구문(Syntax)

구분자

정규식이 표현될 수 있는 곳을 구분, 지정 해준다.

/[정규식 표현]/ 와 같이 / /사이에 표기하는 것이 기본이며, C, Java, Python과 같은 프로그래밍 언어에서는 정규식을 사용할 때 / 로 구분 짓지 않고 "" 사이에 정규식을 그대로 입력하는 것도 인용된다.

즉, 정규식은 / / 사이에서 작동한다.

IEEE POSIX 표준

“정규식의 공통된 사용법”

BRE(Basic Regular Expressions), ERE(Extended Regular Expressions), SRE(Simple Regular Expressions)의 3종류가 기준으로 정해졌으며, 현재 SRE는 더 이상 사용하지 않는다.

ERE는 기본 BRE에서 확장된 정규식이며 기본 정규식에 ?, +, | 메타문자 추가와 BRE에서 필요했던 (), {} 특수 문자 이스케이프가 필요 없어졌다.

등등….표준에 준하는 정규식 설명이 있지만 가장 중요한 건 “표준” 수립으로 공통된 사용법을 가지게 된다는 내용이다.

기본 컨셉에서 설명하지 못한 다른 메타 문자들을 소개하겠다.

POSIX 표준

표현식 의미 설명
^ 문자열 시작 문자열의 시작 위치를 지정한다.

^a는 ‘a’로 시작하는 문자 혹은 문자열에만 대응한다.
$ 문자열 끝 문자열의 끝을 지정한다.
. 문자 하나 어떤 문자든 하나 인식한다.
구동 환경에 따라 다르지만, 개행 또한 웬만해서는 인식한다고 알아두는 게 안전하다.

[] 안에서는 .은 그대로 문자로 사용되지만 a.b에서는 아무 문자로 인식된다.
즉, [a.c]는 “a”, “.”, “c”를 인식할 것이고, a.c는 “abc”, “aac” 등 . 위치에 어떠한 문자가 들어가도 이를 인식할 것이다.
[ ] 해당 범위에 해당되는 문자 [ ] 안에 표현한 문자 혹은 - 로 문자의 범위를 지정하여 해당 되는 문자 하나를 인식한다.

일종의 \| 라고 생각해도 된다.

[abc]는  ”a”, “b”, “c” 와 같이 매치 되는 한 문자를 인식할 수 있고,
[a-z]는 “a” ~ “z”의 영어 소문자를 인식할 수 있다.

-[abc-][-abc]와 같이 처음과 끝에 위치해 범위를 지정하지 못하는 경우 단순히 -의 문자로 인식된다.

이를 응용한다면 [abcx-z]([a-cx-z]) 로 “a”, “b”, “c”, “x”, “y” “z”를 인식할 수 있다.
[^ ] 해당 범위에 해당되지 않는 문자 ^[ ] 시작 부분에 추가하여 [ ] 의 반대 역할을 수행한다.

[abc]는 “a”, “b”, “c”가 아닌 문자들을 인식하고,
[^a-z] 는 영어 소문자가 아닌 문자를 문자를 인식할 수 있다.
( ) 하위 표현(묶음) 정규식 내부에서 또 다른 정규식을 나누는 묶음(그룹)역할을 한다.

위에서도 보았듯이
/H(ä\|ae?)ndel/은 “Handel”, “Händel”, “Haendel” 에 대응한다.

이는
1. H와 일치.
2. ä혹은 a, e는 있어도 되고 없어도 되고.
3. ndel과 일치.

와 같이 단계를 나눌 수 있는데 1,3은 정확히 일치해야 하고 2는 \|의 or를 처리하였다.

()로 or 역할을 “묶었다” 라고 생각하면 될 것 같다.
\n n번째 패턴 n은 1~9의 숫자가 대응되며 이는 n번째 패턴에 대응하는 패턴을 의미한다.

back-reference라고도 한다.

BRE 모드에서 지원된다.
* 0개 혹은 여러 개 *앞의 요소가 없거나, 여러개 있을 때를 대응 한다.

위에서도 설명했듯이ab*c 는 “ac”, “abc”, “abbc”, “abbbc” 혹은 b가 그 이상 있어도 인식할 수 있으며,
[xyz]*는 “”, “x”, “y”, “z”, “zx”, “zyx”, “xyzzy” 를,
(ab)* 는 “”, “ab”, “abab”, “ababab”… 등을 인식한다.
{m,n} min~max 최소 m개에서 최대 n개까지.

a{3,5}는 “aaa”, “aaaa”, “aaaaa”에 대응한다.
? 0개 혹은 1개 있을 수도 없을 수도의 의미로,
ab?c 는 “ac” 혹은 “abc”만 인식한다.
+ 1개 혹은 여러 개 1개 이상 혹은 그 이상에 대응한다.

ab+c 는 “abc”, “abbc”, “abbbc”만 인식하며 “ac”는 b+에 대응하지 못한다.
\| or abc\|def 는 “abc” 혹은 “def” 를 인식한다.

Character Classes

자연어를 사용하는 인간은 숫자, 영어 대/소문자 등을 쉽게 구분할 수 있지만, 정규식에서는 이를 [0-9], [A-Z], [a-z] 와 같이 직접 명시를해야 한다다. 여기서 Characterclasses는[[0-9]의정수만이라는 의미를를\dd와 같이 작은 단위로 표현할 수 있다.

대표적으로 몇 가지만 보고 넘어가자.

\w [A-Za-z0-9_] _를 포함한 알파벳과 숫자
\W [^A-Za-z0-9_] _를 포함한 알파벳과 숫자가 아닌.
\a [A-Za-z] 알파벳(Alphabetic)
\l [a-z] 영어 소문자(Lowercase)
\u [A-Z] 영어 대문자(Uppercase)
\d [0~9] 숫자(Digits)
\D [^0~9] 숫자가 아닌.
\s [ \t\r\n\v\f] 공백문자.
\S [^ \t\r\n\v\f] 공백이 아닌.

이외의 Greedy 탐색을 방지하는 Lazy matching 등과 같은 자세한 사항은 wiki 문서에서 확인하길 바란다.

응용

이제 실제로 많이 사용하는 패턴 몇 가지를 하나씩 뜯어가면서 어떤 의미를 가지는지 확인해 보자.

1. 날짜 (YYYY-MM-dd 형식)

/^\d{2,4}[-./]\d{1,2}[-./]\d{1,2}$/

“1729-02-11”, “1999.09.09”, “9999/1/3”…

  1. 숫자 2~4자(2024 or 24 가능)
  2. 숫자 사이에 -, ., / 중 하나가 포함
  3. 숫자 1~2자(01, 1)

2. 생년월일 (YYYY-MM-dd 형식)

/^(19[0-9][0-9]|20\d{2})-(0[0-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/`

“1900-01-01”, “2024-04-16”…

  1. (19[0-9][0-9]|20\d{2})
    1. 총 네 자릿수의 숫자.
    2. 19로 시작하여 숫자 2자 혹은 20으로 시작하여 숫자 2자.
  2. 하위 정규식 사이에 -.
  3. (0[0-9]|1[0-2])
    1. 총 두 자릿수의 숫자.
    2. 0으로 시작하여 숫자 1자 혹은 1로 시작하여 0~2의 숫자 1자
  4. (0[1-9]|[1-2][0-9]|3[0-1])
    1. 0으로 시작하여 1~9 숫자 1자 혹은 1~2로 시작하여 숫자 1자 혹은 3으로 시작하여 0~1 숫자 1자

3. 이메일

“abc@google.com”, “wido1593@gmail.com”, “email@main.co.kr”

case 1

/^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*(\.([a-zA-Z]{2,3})){1,2}$/i;
  1. [0-9a-zA-Z]
    1. 숫자나 알파벳으로 시작한다.
  2. ([-_.]?[0-9a-zA-Z])*
    1. -, _, . 이 있을 수도 없을 수도 있다
    2. 숫자나 알파벳이 0개 혹은 여러 개 있을 수 있다.
  3. @
    1. @는 필수로 포함된다.
  4. [0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*
    1. 1,2 번과 동일
  5. (\.([a-zA-Z]{2,3})){1,2}
    1. .는 필수로 포함된다.(escape 처리로 literal)
    2. .이후로는 알파벳 2~3개로 끝난다.
    3. .([a-zA-Z]{2,3})(){1,2}으로 wrap하여 도메인 패턴을 최소 1번 최대 2번까지만 허용한다.

case 2

/^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]+$/;
  1. [\w-]+
    1. 1개 이상의 알파벳, 숫자, _, -으로 시작.
  2. (\.[\w-]+)*
    1. .으로 시작하여 1개 이상의 알파벳, 숫자, _, -으로 이루어진 문자 조합이 여러 개 있을 수 있다.
  3. @
    1. @는 필수로 포함된다.
  4. ([\w-]+\.)+
    1. 1개 이상의 알파벳, 숫자, _, -으로 시작하여 .으로 끝나는 조합이 여러 개 있을 수 있다.
  5. [a-zA-Z]+
    1. 1개 이상의 알파벳으로 끝난다.

4. 핸드폰 번호

/^(01[01346-9])-?([0-9]{3,4})-?([0-9]{4})$/
  1. (01[01346-9])
    1. 01로 시작하여 0,1,3,4,6~9 의 숫자 1자.
  2. -?
    1. 하위 정규식 사이에 -가 있을 수도 없을 수도.
  3. ([0-9]{3,4})
    1. 숫자가 최소 3자에서 4자.
  4. ([0-9]{4})
    1. 숫자가 4자.

여기까지 정규식의 기본 개념과 사용법을 알아봤다.

정규식을 익혀두면 패턴 매칭으로 쉽게 문제를 해결할 수 있으니 익혀두는 것을 추천한다.

추가로 직접 머신을 돌리면서 테스트하지 않아도 https://regexr.com/ 와 같은 사이트에서 테스트가 가능하다.

Ref.
https://en.wikipedia.org/wiki/Regular_expression
https://velog.io/@haru/frequently-used-regexp
https://ioerror.tistory.com/entry/%EC%9E%90%EC%A3%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D-%EB%AA%A8%EC%9D%8C