정규 표현식의 조건절

흔히 쓰이는 경우는 아니지만 정규 표현식에도 조건절을 사용할 수 있다. 참고로 모든 정규식 엔진이 조건절을 지원하는 것은 아니다. (javascript 정규식은 조건절을 지원하지 않는다.) 만약 현재 사용하는 편집기나 언어의 정규식 엔진에서 조건절을 지원한다면, 조건절을 사용해서 보다 정교한 매칭을 하는 것이 가능하다. 오늘은 정규표현식의 조건절 사용법에 대해 살펴보도록 하겠다.

기본 문법

정규 표현식의 조건절에서 조건은 특정한 캡쳐링 그룹의 여부이다. 앞에서 특정한 패턴으로 그룹을 정의하고, 해당 그룹이 매칭되었는지 여부에 따라서 A 혹은 B 패턴을 적용하는 방식으로 사용된다. 가장 간단한 형태는 다음과 같다.

(Z)?.*(?(1)A|B)

위 패턴에서 “Z”는 기준이 되는 조건 패턴이고, A는 Z그룹이 매치할 때 적용할 패턴, B는 Z그룹이 매치하지 않을 때 적용할 패턴이다. 이 때 ?(1) 의 1 대신에 다음과 같은 것들이 올 수 있다.

  • 앞에서 언급한 캡쳐링 그룹의 번호
  • 캡쳐링 그룹에 이름을 지정할 수 있는 경우, 해당 그룹 이름.
  • 숫자에 부호를 붙이는 경우에는 그룹의 상대 위치를 offset 방식으로 쓸 수 있다.
  • look-around 표현식
  • 서브루틴 콜
  • 재귀 매칭

서브루틴 콜이나 재귀 매칭은 어차피 이해하기도 어렵고 (나도 잘 모르겠…) 펄 정규식 외에는 거의 지원도 안돼서 딱히 알아보지 않으려고 한다. 또한 조건절을 쓰는 패턴은 OR 패턴으로도 대체가 거의 가능하다. (대신에 패턴이 매우 길어지거나 복잡해질 수는 있다.)

캡쳐링 그룹 번호를 조건으로 쓰는 경우

정규표현식에서 가장 일반적으로 사용하는 조건절은 특정한 그룹의 매치 여부에 따라 뒤쪽의 패턴이 달라지는 것이다. 다음 예는 연속된 숫자값이 있는 라인을 찾을 때 사용하는 패턴인데, 두 가지 경우가 있다고 가정한다.

  • “START”로 시작하고 숫자가 연속되며 다시 “END”로 끝나는 경우
  • “START”와 “END”가 모두 없고 라인 전체가 숫자로만 이루어지는 경우

이 규칙에 의하면 “START”로 시작했지만, 뒤에 “END”가 없거나, “START”가 없는데 뒤에 “END”가 붙은 라인은 모두 무효로 봐야하며, 결국 “START”의 매치 여부에 따라서 “END”를 체크할 것인지가 결정된다. 이 때 사용할 수 있는 패턴은 다음과 같다.

^(START)?(\d+)(?(1)END|\b)$
^
 (START)?                   -- START로 시작했는지 그룹 1에 캡쳐
         (\d+)              -- 연속된 숫자를 그룹2에 캡쳐
              (?(1)END|\b)  -- 그룹1이 있다면 END에 매치, 없다면 \b에 매치
                          $ -- 문자열의 끝
START012314352454END  -- 매치된다. 
0123134354556         -- 매치되지 않는다.
START0143452456E      -- 뒤에 END가 없으므로 매치되지 않는다.
12235345345END        -- START없이 END만 있으므로 매치되지 않는다.

사실 이 패턴은 아주 단순해서, 조건절을 지원하지 않는 정규식으로도 다음과 같이 바꿔 쓸 수 있다. ^(?:START(\d+)END|(\d+))$ 하지만 이 코드는 매치 여부는 동일하지만, 결과가 조금 다르다. 조건절을 사용한 패턴에서는 연속된 숫자 부분만 얻으려면 항상 GROUP 2를 확인하면 되지만, 조건절을 지원하지 않는 후자의 패턴에서는 START~END가 있으면 GROUP1에, 없으면 GROUP2를 확인해야 한다.

캡쳐링하는 그룹이 많다면 번호를 가지고 지정하는 그룹이 헷갈리기 쉽기 때문에, 캡쳐링 그룹에 이름을 지정할 수 있다. (?P<name> pattern) 과 같은 식으로 ?P<name> 을 사용해서 그룹에 이름을 지정할 수 있다. PCRE 정규식에서는 (?<name>)(?P<name>) 이 모두 지원되나, 파이썬에서는 후자의 패턴만 허용된다. 참고로 이 때 그룹이름은 영문자여야 하고 숫자로 시작될 수 없다.

선후방 검색에 의한 조건절

선후방 검색에 의한 조건절을 사용하는 방법도 있다. 그룹에 의해 조건절을 사용하는 방식은 조건으로 사용되는 그룹이 앞부분에 나와야 한다는 한계가 있다. 하지만 전방탐색을 사용하여 뒤쪽에 나올 패턴을 미리 확인해서 앞에서 조건에 따른 패턴을 적용하는 방식이다. 참고로 선후방 검색에 의한 조건절은 PCRE 정규식에서만 사용 가능하며, 파이썬에서는 사용할 수 없다.

이 예제에서는 “이름_종류”가 올바르게 연결된 문자열에서 이름에 매치하는 패턴을 살펴보겠다.

(?(?=.*_fruit)(?:apple|banana)|(?:potato|coffee))
(?(?=.*_fruit)            -- 나중에 "_fruit"가 나온다면 참이다.
       (?:apple|banana)   -- 참이라면 "apple", "banana" 중에서 매치한다.
      |(?:potato|coffee)) -- 거짓이라면 "potato", "coffee" 중에서 매치한다.
apple_fruit  - "apple"에 매치
apple_nuts   - 매치안함
potato_fruit - 매치안함
coffee_nuts  - "coffee"에 매치

파이썬에서는 다음과 같이 OR 패턴으로 위 식을 대체할 수 있기는 하다.

^(?:(?:apple|banana)(?=.*_fruits))|(?:(?:potato|coffee)(?!.*_fruits$))

예제 – 핸드폰 번호 매칭하기

핸드폰 번호를 찾는 패턴을 작성해보자. 핸드폰 번호의 규칙에는 다음과 같은 규칙이 있다.

  1. 세자리 – 세(네)자리 – 네자리 형태로 구성된다.
  2. 각 마디 사이에는 – 이나 . 이 들어갈 수 있다.
  3. 첫 마디에는 010, 011, 016, 017, 018, 019 가 올 수 있다.
  4. 첫 마디가 010인 경우에는 가운데 마디가 항상 4자리이다. 그외의 식별 번호는 가운데 마디가 3혹은 4자리이다.

첫번째 마디가 010인지 여부에 따라서 가운데 마디의 패턴이 달라진다. 이 경우는 정규 표현식의 조건절을 사용하면 비교적 간단? 하게 체크할 수 있다.

^(?:(?P<a>010)|01[16789])(?:(?P<b>[\-\.])?(?(a)\d{4}|\d{3,4}))(?:(?(b)(?P=b))\d{4})

^(?:(?P<a>010)|01[16789])  - 첫마디는 010 이거나, 01[16789]로 시작한다.
 (?:(?P<b>[\-\.])?          - 두번째 마디 앞에는 - 이나 . 이 올 수 있다. 
                            - 이 구분 문자는 b라는 이름의 그룹으로 캡쳐한다.
    (?(a)\d{4}|\d{3,4}))   - 첫마디가 010이었다면 둘째마디는 4자리, 
                           - 그외에는 3~4자리가 된다.
 (?:(?(b)(?P=b))           - 앞 마디에 구분문자(b)가 있었다면 똑같은 문자가 온다
     \d{4})                - 마지막 4자리가 온다.

이 패턴으로는 다음과 같은 번호 패턴들은 매치된다.

010-1234-1234
011-1234-1234
010.1234.5678
011.123.2134
01012345678
01112345678
0111235678

다음과 같은 패턴들은 매치되지 않는다.

010-123-1234  : 010인데 가운데가 3자리임
014-123-1235  : 식별번호가 틀림
010.1234-5678 : 마디 구분 문자가 일관되지 않음
0101234-5678  : 마디 구분 문자가 뒤에만 쓰임
010-12345678  : 마디 구분 문자가 앞에만 쓰임

Read more

워드프레스에서 고스트로 이전

워드프레스에서 고스트로 이전

이 글을 쓰면서도 믿기 힘든 사실인데, 블로그라는 걸 처음 시작한지가 20년이 되었습니다. 이글루스에서 처음 시작했다가, SK컴즈가 인수한다고 발표함과 동시에 워드프레스로 플랫폼을 옮겼죠. 워드프레스오 옮긴 이후에는 호스팅 환경을 이리 저리 옮기긴 했지만 거의 18년 가까이 워드프레스를 사용해온 것 같습니다. 그 동안 워드프레스는 블로깅 툴에서 명실상부한 범용CMS로 발전했습니다. 사실 웬만한 홈페이지들은 이제

By sooop
띄어쓰기에 대한 생각

띄어쓰기에 대한 생각

업무 메일을 쓸 때 가장 많이 쓰는 말 중에 하나가 메일 말미에 ‘업무에 참고 부탁 드립니다.‘인데요, 어느 날부터 아웃룩에서 이 ‘부탁 드립니다’가 틀렸다고 맞춤법 지적을 하기 시작했습니다. 맞는 말은 ‘부탁드립니다’라고 붙여 쓰는 거라고. 사실 아래아한글 시절부터 이전의 MS워드까지, 워드프로세서들의 한국어 맞춤법 검사 실력은 거의 있으나 마나 한

By sooop

구글 포토에서 아이클라우드로 탈출한 후기

한 때 구글 포토가 백업 용량을 무제한으로 제공해 주겠다고해서, 구글 포토를 사용해서 사진을 백업해왔습니다. 물론 이 이야기의 결말은 저나 이 글을 읽고 있는 여러분이나 모두 알고 있습니다. 사실 AI에게 학습 시킬 이미지 데이터를 모으기 위한 것일 뿐이라거나 하는 이야기는 그 당시에도 있었습니다만, 에이 그래도 구글인데 용량은 넉넉하게 주겠지…하는 순진한

By sooop

Julia의 함수 사용팁

연산자의 함수적 표기 Julia의 연산자는 기본적으로 함수이며, 함수 호출 표기와 같은 방식으로 호출하는 것이 가능합니다. 또한 그 자체로 함수이기 때문에 filter(), map() 과 같이 함수를 인자로 받는 함수에도 연산자를 그대로 적용하는 것이 가능합니다. 특히 + 연산자는 sum() 함수와 같이 여러 인자를 받아 인자들의 합을 구할 수 있습니다. 2 + 3 # = 5 +(2,

By sooop