정규식 입문하기

개요top

정규표현식은 문자열을 조작하거나 검사하는데 편리한 도구입니다.
정규표현식의 도움없이 복잡한 문자열 관련 작업을 수행하기 위해서는 방대한 양의 코드를 필요로 하게 됩니다.

하지만 편리한 정규식에 입문하는 것을 어려워하는 경우가 많습니다.
개인적으로 정규표현식에 대한 기초 개념을 제대로 설명하지 않고 기능만 나열한 것을 흡수하게 하려는 형태의 설명이 많아서라고 생각합니다.

정규표현식 입문을 위한 기초 개념을 익혀보도록 하죠.

3대 기본 연산top

정규식을 이루는 기본 연산이 있습니다. 사실 이 기본 연산만 잘 익혀도 대부분의 문제를 해결할 수 있을 정도로 중요합니다.

접합

접합이란 연속으로 나오는 것을 하나로 모아서 검색하는 기능입니다. 정규식의 최소단위는 하나의 문자인데 이 문자를 접합하여 단어를 검색할 수 있습니다.
예를 들어 ABC라는 문자열을 찾는 정규식은 그저 /ABC/로 표현할 수 있습니다. 이는 사실 A B C 라는 하나하나의 문자를 접합한 것입니다.

선택

접합과 달리 여러 개 중에 하나만 찾아 검색하는 것을 선택이라고 합니다.
예를들어 A, B, C 중에 하나만 찾는 것을 정규식으로 표현하면 /A|B|C/ 라 표현할 수 있습니다. | 연산자는 선택을 나타내고 여러 대안 중 왼쪽부터 차례로 찾아가며 처음으로 일치하는 것을 찾게 됩니다.

반복

반복은 사실 편의 기능입니다. 접합을 통해 표현할 수 있으나 반복적인 표현을 줄이고 간편하게 사용할 수 있는 기능입니다.
예를 들어 AAA라는 문자열을 찾는 정규식은 /A{3}/ 으로 표현할 수 있습니다. {…} 연산은 반복을 나타냅니다. 하지만 반복이 지원되지 않아도 그냥 그 숫자만큼 반복한 접합을 통해 표현할 수 있습니다. 즉 위의 예는 /AAA/ 로 갈음할 수 있죠.

하지만 반복은 범위나 무한대 같은 것을 손쉽게 표현할 수 있으므로 대단히 유용하고 전체적으로 정규식의 길이를 줄여줍니다.

3대 기본 연산의 우선 순위top

이 다음 단계를 이 3대 기본연산의 연산자 우선 순위를 이해하는 것입니다. 간단한 접합과 선택을 한꺼번에 사용해보죠.

예를 들어 ABC 또는 DEF를 찾는 정규식은 /ABC|DEF/ 로 표현할 수 있습니다. 이를 자세히 살펴보면 ABC, DEF는 각각 접합을 통해 만들어진 식입니다. 이 두 개의 접합식을 |를 통해 선택연산을 적용한 것이죠.

우리는 여기서 3개 기본연산의 우선순위가 존재한다는 사실을 알 수 있습니다. 접합연산은 선택연산 보다 우선 순위가 높아 우선 접합이 계산되고 그 다음에야 선택연산이 계산됩니다. 마치 곱셈(접합)과 덧셈(선택) 같은 연산자 우선순위를 생각해보시면 쉽습니다.

이번엔 접합과 반복을 같이 쓰는 경우를 생각해볼 수 있죠.

만약 /AB{3}/ 정규식이 ABBB로 해석된다면 반복이 접합보다 연산자 순위가 높다고 할 수 있습니다. 만약 접합이 우선 순위가 높았다면 ABABAB로 해석될 것입니다.

실제로 ABBB로 해석되기 때문에 접합보다 반복이 더 우선 순위가 높습니다.

따라서 기본 연산 3개는

반복 >> 접합 >> 선택

순으로 연산자 우선 순위를 갖고 있습니다.

모두 합쳐 BAAA 또는 ABBB를 찾는 정규식은 /BA{3}|AB{3}/ 으로 표현할 수 있습니다. 접합, 반복, 선택을 전부 사용하고 있습니다.

그룹

그룹은 () 괄호를 이용해 표현하는데 일반적인 산술연산처럼 모든 연산자 우선 순위를 이겨 먼저 실행되게 할 뿐만 아니라 그룹자체를 하나의 문자처럼 3개 기본연산의 대상으로 만들 수 있습니다.
(그룹의 캡쳐기능은 일단 생각하지 마세요 ^^)
예를 들어 ABC, BBC, CBC 중에 하나를 찾는 정규식에서 뒤에 BC는 반복적으로 나타나고 있습니다.

  1. 이는 반대로 생각하면 A, B, C중에 하나로 시작하고 뒤에 BC를 결합한 식으로 생각할 수 있습니다.
  2. 그렇다면 A, B, C 중에 하나라는 것을 마치 하나의 문자처럼 표현할 수 있다면 BC와 결합할 수 있을 것입니다.
  3. 이때 괄호를 사용하게 됩니다. A, B, C 중에 하나를 선택하는 연산을 표현하여 괄호로 감싸면 (A|B|C) 가 될 것입니다.
  4. 이제 괄호를 감쌌으므로 하나의 문자처럼 다루게 됩니다. 이제 뒤에 B, C와 결합할 수 있습니다.
  5. 최종적으로 완성된 정규식은 /(A|B|C)BC/ 입니다.

완성된 /(A|B|C)BC/ 는 크게 보면 (…)BC 라고 볼 수 있죠.
즉 괄호부분을 하나의 문자라고 보면 (…), B, C 라는 세 개의 문자를 접합연산한 것입니다.
그룹에서 가장 중요한 개념은 이 그룹이 하나의 문자로 취급된다는 점입니다.

더 나아가 정규식은 문자를 연산하는 것입니다.

그룹이 문자를 대체할 수 있으므로 이제 모든 연산은 재귀적으로 해석될 수 있습니다.

기초적인 정규식 사고방법

여기까지 배운 것을 통해 간단히 문자열 파서를 만들어보죠.
대부분의 프로그래밍 언어에서 문자열은 “…” 형태로 표현됩니다. 하지만 그 문자열 안에 “를 넣기 위해서는 \” 형태로 이스케이프 문자를 넣게 됩니다.
이를 인식할 수 있는 정규식을 짜보는 것이죠.

그럼 처음에 할 일은 모든 사고를 3대 기본연산으로 그 현상을 바라보는 것입니다.

위에서 묘사한 문자열은 “로 시작해서 “로 끝나는 것입니다.
따라서 우선

“….”

라고 표현할 수 있죠. 그러면 그 안에 뭐가 올까요?

……..

난감하시죵?

이 때 알고 있는 3대 기본연산만 떠올려 봅니다.

알고 있는 건 접합, 선택, 반복 뿐입니다.

저 안에 들어갈 문자를 표현할 연산자는?

접합이나 반복으로 문자열을 표현하는 것은 말이 안되므로 “선택”입니다.

선택이라는 연산자 관점에서 “…” 안에 들어갈 문자열을 바라보면 딱 두 가지로 보입니다.
바로 “가 아닌 문자열이거나 \”인 거죠.

바로 이 부분입니다. 정규식 사고에 성공하냐 안하냐의 분기점이죠.
일단 이 사고에 도달하면 즉시 문제를 풀 수 있습니다. 아직 배우지 않은 문법이 등장하지만 간단히 풀어보면 다음과 같습니다.

/”([^”]|\\”)*”/

차근차근 살펴보죠.

  1. 우선 양쪽을 감싸는 “는 당연한 것입니다. 우리는 큰 따옴표로 감싼 것을 찾고 있습니다.
  2. 양쪽 큰 따옴표를 빼고 생각해보면 (…)* 입니다. *는 {0,} 의 단축 표현으로 0개이상을 의미하는 반복연산입니다. 빈 문자열일 수도 있으니까 0개 이상이죠.
  3. 이제 괄호 안쪽만 보면 되는데 괄호 안은 크게 보면 | 선택 연산자를 기준으로 두 개의 문자식이 있습니다.
  4. 왼쪽은 [^”] 인데 이는 “가 아닌 문자를 의미합니다.
  5. 오른쪽은 \\” 인데 정규식도 이스케이프 문자를 사용하기 때문에 \를 표현하려면 \\로 표현해야합니다. 즉 \\”는 \”라는 문자에 일치하는 결합식입니다.

전체적으로 해석해보면 큰따옴표가 아닌 문자이거나 \”인게 0개이상 반복되는 것이 양쪽 따옴표 안에 들어있다라고 할 수 있습니다.
*는 {0,}의 간단한 표현으로 반복연산입니다. 하지만 앞이 괄호로 된 그룹이라 연산자 우선순위에 상관없이 내부가 먼저 계산되고 그 이후에 반복이 연산되는 것이죠.

이렇듯 3대 기본연산을 잘 알고 현실의 문제를 이 연산으로 바꿔 생각하는 것이 정규식을 짜는 핵심 요령입니다.

결론top

이 포스팅이 끝일지 시리즈가 될지는 아직 정하지 않았습니다만 만약 다음편이 진행된다면 이 다음에 배워야할 주제는 다음과 같습니다.

  • 메타문자의 개념
  • 문자클래스의 개념
  • 일치의 종류와 이해

여기까지 마치고 나면 상당히 많은 것이 가능해집니다. 더 나아가 그 다음 주제는 다음과 같습니다.

  • 탐색의 종류와 이해
  • 재귀의 해소
  • 기본 3연산으로 처리할 수 없는 경우

마지막으로 보다 깊이 파고 드실 분들은

  • 탐색과 백트래킹의 관계
  • DFA형과 VM형의 차이점
  • ABNF를 통한 정규식 자동생성

같은 주제를 더 들어가시면 좋습니다.

정규식과 관련되어 추천하는 책은 제이펍에서 역서로 출판된 다양한 언어로 배우는 정규표현식이라는 책으로 개인적으로 이 책으로 온라인 스터디도 진행한 적이 있었는데 상당히 개념적으로 잘 가르쳐줍니다.