Regular Expression is a sequence of characters that define a search pattern.

정규표현식이란 검색 패턴을 정의한 일련의 문자열을 말한다. 검색 패턴을 정의하기 위해 마치 프로그래밍 언어처럼, 패턴을 표현하기 위한 언어를 따로 정의하였다고 생각해도 괜찮다.

정규표현식을 배울 때 어려웠던 이유 중 하나는 길게 늘여뜨려 쓰면 이걸 알아먹기가 굉장히 힘들기 때문이다.

정규 표현식에 대해 더욱 자세히 알고 싶은 사람은 위키피디아를 참고하도록 하고 여기서는 간단한 정리 위주로 기록해 보자.

패턴

정규표현식의 정의를 다시 한 번 생각해 보자. 정규 표현식은 패턴을 말한다. 어떤 패턴이냐 하면, 어떤 문자열을 검색하여 선택할 수 있는 굉장히 “정규적인” 패턴을 말하는 것이다. 말이 조금 이상하지만 굉장히 일반적인 검색 패턴을 통해 어떠한 문자열이든 원하는 것을 선택할 수 있다는 것으로 대충 이해하자.

그렇다면 일반적인 검색 패턴이라는 것은 무엇일까? 만약 많은 텍스트 중 특정한 문자열 “asdf"를 찾아야 한다고 생각해 보자. 당연히 전체 문자열을 뒤져나가면서 “asdf"가 나오는지를 확인하면 된다. 그렇다면 “a로 시작하는 길이 4의 문자열” 을 찾으려면 어떻게 해야 할까?

  • a이외 다른 글자로는 시작할 수 없으며,
  • 그 길이는 4보다 크거나 작을 수 없다.

텍스트에는 a~z까지의 알파뱃 소문자만 포함되어 있었다고 가정할 경우에도, 알파뱃이 26자가 있으므로 검색해 보아야 할 문자열은 총 26 * 26 * 26, 전체 텍스트에 대해 17576개의 문자열을 검색해 보아야 그 결과를 알 수 있을 것이다.

for c in itertools.product(string.ascii_lowercase, repeat=3):
    print(', '.join(c))

a, a, a
a, a, b
a, a, c
a, a, d
a, a, e
a, a, f
a, a, g
a, a, h
a, a, i
a, a, j
...
z, z, t
z, z, u
z, z, v
z, z, w
z, z, x
z, z, y
z, z, z

물론 텍스트의 검색이라는 것이 꼭 메모장이나 어떤 그런 텍스트 에디터의 ‘검색’ 기능으로만 하는 것이 아니므로, 이 정도 조합이야 컴퓨터로 처리할 수 있다고 치자. 문제는 이 조합을 어떻게 다른 사람이 알아볼 수 있게 표현하느냐는 것이다. 그리고 그 표현은 컴퓨터에서도 쉽게 사용할 수 있어야 한다.

우리는 “a로 시작하는 길이 4의 문자열"이라는 패턴을 통해 위 조합을 만들 수 있었다. 따라서 어떤 표현을 통해 이 조합을 똑같이 만들 수 있다면, 그리고 그 표현을 일반적으로 만들 수 있다면 그것을 사용하여 어떤 문자열이든 찾을 수 있는 패턴을 만들 수 있을 것이다.

일단은 위 정규표현식을 통해 조건에 맞는 문자열을 찾을 수 있다. 이 조건을 만족하는 다른 정규표현식도 많으니 꼭 이게 답이라고는 할 수 없겠다.

아무튼, 정규표현식에서는 다양한 조건을 만족할 수 있도록 하기 위한 키워드들이 이미 존재하며 이를 특별히 Metacharacter, 메타캐릭터라고 부른다. 일반 문자와 메타캐릭터를 적절히 섞어 사용하여 원하는 문자열을 선택할 수 있는 패턴을 정의할 수 있다.

패턴의 기본 개념

패턴에는 3가지 기본 개념이 존재한다. 아래 내용을 보면 알 수 있겠지만, 뭔가 이런 기본 개념을 사용하지 않더라도 특정한 문자열을 찾는 것이 불가능하지는 않다는 것을 알 수 있다. 예를 들어 gray를 찾는 것에 대해 gray를 정규 표현식으로 사용해도 문제가 없다는 것이다. 다만 이런 패턴의 기본 개념을 알고, 또 나아가서 메타캐릭터를 사용할 수 있게 되면 더 명확한 의도를 가지고 조금 더 다양한 문자열을 선택할 수 있게 되는 것이다.

BOOLEAN OR

| 를 통해 여러 항목을 한 번에 선택할 수 있다. 예를 들어 패턴 gray|grey는 gray와 grey 두 패턴을 모두 선택할 수 있다.

Grouping

(, )는 범위와 우선권을 지정할 수 있는 연산자다. 예를 들어 gray|greygr(a|e)y와 같이 다시 표현할 수 있다.

Quantification

Quantification은 수량화라고 부르는데 그냥 어떤 것의 양을 지정한다고 생각하자. 양을 지정하는 방법은 굉장히 다양하며, 이를 통해 여러 조건을 만들어낼 수 있다.

  • ?: 0개 또는 1개일 때를 선택한다.
  • *: 0개 또는 여러 개일 때를 선택한다.
  • +: 1개 또는 여러 개일 때를 선택한다.
  • {m}: 정확히 m개일 경우를 선택한다.
  • {m, }: m개 이상인 경우를 선택한다.
  • {m, n}: m개 이상 n개 이하인 경우를 선택한다.

Metacharacter

일단 기본적인 정규표현식을 하려면 당연히 Metacharacter, 메타캐릭터를 알아야 한다.

MetacharacterDescription
^한 줄의 시작 위치를 의미한다.
$한 줄의 끝 위치를 의미한다.
[][와 ]안에 들어있는 문자 한 개를 찾는다. 하이픈을 사용하여 찾는 문자의 범위를 입력할 수도 있다.
[^][ ^ 와 ]안에 들어있는 문자가 아닌 문자를 찾는다. 쉽게 말하면 바로 위의 부정과 같다.
\n일치하는 n번째 패턴을 선택한다. 순서대로 번호가 매겨진 정규표현식의 그룹 결과를 선택한다.
.아무 문자 ‘하나’를 의미한다. 보통 개행 문자는 제외된다.
*선행 문자 하나가 ‘없거나 여러 개’인 경우를 찾는다.
?선행 문자 하나가 ‘없거나 한 개’인 경우를 찾는다.
+선행 문자 하나가 ‘한 개 이상’인 경우를 찾는다.
{m}선행 문자 하나가 ‘정확히 m개’인 경우를 찾는다.
{m,}선행 문자 하나의 수가 ‘m개 이상’인 경우를 찾는다.
{m,n}선행 문자 하나의 수가 ‘m개 이상 n개 이하’인 경우를 찾는다.

메타캐릭터가 어떤 대단한 것은 아니지만, 그냥 문자에 정규표현식에서 사용할 수 있는 특별한 의미를 담은 것이다. 메타캐릭터에 대한 자세한 설명을 보자.

시작과 끝

문자열의 시작과 끝을 나타내기 위해 ^$ 문자를 사용한다. 예를 들어

a
ab
abc

abcd

abcdabcd

와 같은 텍스트가 있을 경우, a만 있는 라인을 선택하기 위해서는 ^a&의 정규표현식으로 선택하면 될 것이다.

극단적인 예로, 만약 텍스트 중 빈 라인만 찾아야 하는 경우라면 ^$로 찾을 수도 있다.

아무 문자 하나 선택하기

문자 하나 선택하기

[], 대괄호를 통해, 대괄호 안에 있는 글자 한 개를 선택할 수 있다. 예를 들어 .을 사용하는 경우 선택되는 글자에 제한을 따로 줄 수는 없지만 대괄호를 사용하면 이것이 가능하다.

예를 들어 a와 b사이에 a, b, c, d, e, f, g가 들어간 것만을 찾으려는 경우를 생각해 보자. 우리는 위에서 패턴의 기본 개념을 통해 여러 항목을 선택하는 OR를 확인했으므로 가장 간단하게는 다음과 같이 패턴을 지정할 수 있을 것이다.

a(a|b|c|d|e|f|g)b

위와 같은 표현식을 사용하면 원하는 문자열을 선택할 수 있다. 하지만 괄호 안에 OR가 너무나 많아서 정규표현식 자체가 너무 길어졌다. 조금 더 간략하게 이를 표현하기 위해 우리는 대괄호를 사용할 수 있다.

a[abcdefg]b

이렇게 줄여서 쓸 수 있다.

대괄호에서의 범위 지정

바로 위의 경우에서 a와 b사이에 alphanumeric, 즉 모든 알파벳과 숫자가 들어가는 경우를 생각해 보자. 그러면 OR를 사용하는 경우에는 표현식 자체가 어마어마하게 길어질 것이고, 대괄호를 사용하더라도 그 길이가 매우 길 것이다.

이런 반복을 피하기 위해 -, 하이픈을 사용한다. 하이픈은 from부터 to 사이의 어떤 값을 생략한 채로 표기하게 하는 것이 가능하다. 즉 [a-z]는 달리말하면 [abcdefghijklmnopqrstuvwxyz]가 된다.

a[a-zA-Z0-9]b

하이픈은 대괄호 안에서만 사용 가능한 메타캐릭터이다. 즉 대괄호 밖에서 사용한다면 메타캐릭터가 아니라는 뜻이다. 즉 [a-z][abcdefghijklmnopqrstuvwxyz]이지만 a-z는 그냥 a-z일 뿐이다.

하이픈이 지정하는 범위는 아스키 코드의 순서로 지정된다. 따라서 이런 것이 가능하다.

  • [a-z]: [abcdefghijklmnopqrstuvwxyz]
  • [0-9]: [0123456789]
  • [가-힣]: [가나다라....힣]

하이픈을 사용하여 지정하는 범위는 꼭 특정 문자가 아니어도 상관없다. 예를 들어 [\x00-\x7F]의 경우 아스키 코드 내 모든 문자를 선택하게 된다.

그러면 [A-z]는 어떻게 될까? 아스키 코드 순서상으로는 대문자와 소문자 사이에는 특수기호가 존재하기 때문에 알파벳 대소문자 이외에도 특수기호 [, ], \, ^, _, ' 또한 같이 선택된다는 것이다. [ㄱ-힣]의 경우도 마찬가지인데, 은 유니코드 셋의 한글 자모 부분이고 0x3131 ~ 0x318F까지의 범위를 가진다. 또한 는 한글 음절 부분인데 0xAC00 ~ 0xD7A3이므로 0x318F ~ 0xAC00까지의 범위가 추가 선택된다는 문제를 가진다.

그러면 [a-Z]는 어떻게 될까? 이는 순서가 문제가 된다. 아스키 코드 순서가 역행하므로 보통 에러 처리되거나 제대로 된 문자열을 주지 않는다.

대괄호에서의 범위 지정 키워드

위에서는 -, 하이픈을 사용하여 대괄호 안에서 특정 문자셋을 표기하는 법을 보았다. POSIX에서는 이런 일련의 범위를 지정하는 데에 키워드를 예약해 놓았는데 그 내용은 아래와 같다.

KeywordDescriptionhyphenShorthand
[:alnum:]Alphanumeric characters[a-zA-Z0-9]
[:alpha:]Alphabetic characters[a-zA-Z]
[:ascii:]ASCII characters[\x00-\x7F]
[:blank:]Space and tab[ \t]\h
[:cntrl:]Control characters[\x00-\x1F\x7F]
[:digit:]Digits[0-9]\d
[:graph:]Visible characters (anything except spaces and control characters)[\x21-\x7E]
[:lower:]Lowercase letters[a-z]\l
[:print:]Visible characters and spaces (anything except control characters)[\x20-\x7E]
[:punct:]Punctuation (and symbols).``
[:space:]All whitespace characters, including line breaks[ \t\r\n\v\f]\s
[:upper:]Uppercase letters[A-Z]\u
[:word:]Word characters (letters, numbers and underscores)[A-Za-z0-9_]\w
[:xdigit:]Hexadecimal digits[A-Fa-f0-9]

따라서 Alphanumeric을 표현하기 위해서 [a-zA-Z0-9]와 같이 표현해도 되지만 [:alnum:] 이라고도 표현할 수 있다.