정규 표현식 (좀 더) 깊이 알아보기

(A little) Deep Dive into Regular Expressions

Jitae Kim
Jitae Kim
Dec 9, 2016 · 11 min read
http://xkcd.net/208

글을 읽으실 분들은 대부분 정규 표현식을 사용하거나, 최소한 한 번이라도 들어 보셨을 것으로 생각하지만, 정의부터 알아보면 좋을 것 같습니다.

정규 표현식(正規表現式, 영어: regular expression, 간단히 regexp 또는 regex) 또는 정규식(正規式)은 특정한 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어이다.


개발하면서 문자를 다룰 때, 가장 유용한 것은 바로 정규 표현식이라고 할 수 있습니다. 복잡한 패턴의 문자열을 정규 표현식을 사용하지 않고 찾아내기는 정말 상상할 수 없을 만큼 힘겨울 것 같습니다.

저 역시 정규 표현식을 사용해 다양한 문제를 해결하고 있습니다. 페이지 내의 전화번호 혹은 이메일, 링크 등을 찾거나, 에디터에서 개발하다가 특정 부분을 원하는 형태로 재가공을 하는 것 처럼 말이죠. 정규 표현식은 대부분의 프로그래밍 언어에서는 물론이고, 에디터나 터미널 유틸리티 등 폭넓게 지원하고 있습니다.

하지만, 정규 표현식을 자주, 그리고 잘 사용하고 있다고 생각하지만, 좀 더 알아보면 활용도가 더 높아지지 않을까 생각을 하게 되었습니다. 책장에 고이 잠들어있던 정규 표현식 책과 인터넷의 문서 읽으며, 다시 한번 정규 표현식의 재미를 느끼게 되었습니다.


한 단계씩 정규 표현식을 알아가는 방식이 좋을 것 같네요.
(*참고: 정규 표현식은 일반적으로 슬래쉬(/)문자로 감싸진 형태로 표현됩니다. 검색된 결과는 굵은 글씨로 표시하는 방식으로 표현하겠습니다. 또한 바로 확인 및 적용할 수 있도록 결과 옆에 링크를 달았습니다)

# 예문
"안녕하세요, 만나서 반갑습니다."
"그래, 안녕?"
# 정규 표현식
/안녕/
# 결과 http://rubular.com/r/i6kXOgYO9f
"안녕하세요, 만나서 반갑습니다."
"그래, 안녕?"

먼저, 가장 간단한 형태의 정규 표현식입니다. 단순하게 입력한대로 일치하는 문자를 찾습니다.


다음으로는, 정규 표현식에는 다양한 메타 문자가 존재합니다. 그 중에서도 가장 기본적인 메타 문자들을 알아보도록 하겠습니다.

  • . : 모든 문자와 일치합니다.
  • [] : 대괄호 사이에 존재하는 문자들 중 하나에 일치합니다.
  • [^] : 대괄호 사이의 가장 첫 번째 문자로 ^ 문자가 있을 때, 그 문자 이후에 존재하는 문자들을 제외한 모든 문자와 일치합니다.
  • [a-z] : 대괄호 사이에서 특정문자1-특정문자2가 존재할 때, 특정문자1과 특정문자2사이의 모든 문자와 일치합니다. [a-z]의 경우, a 부터 z까지 모든 영문자 소문자와 일치합니다.
  • ^ : 대괄호 사이에 존재할 때는 부정을 나타내지만, 대괄호 밖에서는 문자열의 시작과 일치합니다.
  • $ : ^와 반대로, 문자열의 끝과 일치합니다.
  • * : 앞에 존재하는 문자가 0번 혹은 그 이상 반복되는 문자를 찾을 때 사용합니다.
  • + : 앞에 존재하는 문자가 1번 혹은 그 이상 반복되는 문자를 찾을 때 사용합니다.
  • ? : 앞에 존재하는 문자가 있을 수도, 없을 수도 있을 때 사용합니다.
  • \ : . 혹은 [] 등 특수한 목적으로 사용되는 메타 문자를 문자열에서 찾고 싶을 때, 메타 문자를 문자 그대로 사용할 수 있도록 변환해주는 기호입니다. .은 모든 문자와 일치하지만, \.의 경우, .문자와 일치합니다.
# 예문
"안녕하세요, 만나서 반갑습니다."
"그래, 안녕?"
# 정규 표현식
/[가-힣]+/
# 결과 http://rubular.com/r/iwUTQOIMuN
"안녕하세요, 만나서 반갑습니다."
"그래, 안녕?"

이것은, 한글 부터 , 즉, 모든 완성된 한글 문자와 한 개 혹은 그 이상 일치하는 문자를 찾는 정규 표현식입니다.


다음으로는, 정의되어 있는 패턴을 사용할 수 있는 방식을 알아보겠습니다.

  • \d : 숫자와 일치합니다.
  • \w : 영문자 및 _ 문자와 일치합니다.
  • \s : 여러 가지 공백 문자와 일치합니다. (* 스페이스, 탭, 기타… 공백 문자)

그리고 앞서 언급한 것들과 정반대의 기능을 하는 표현이 있습니다.

  • \D : 숫자를 제외한 문자와 일치합니다.
  • \S : 공백 문자를 제외한 문자와 일치합니다.
  • \W : 영문자 및 _ 문자를 제외한 문자와 일치합니다.
# 예문
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
# 정규 표현식
\w\w\w
# 결과 http://rubular.com/r/UfgMxPRDdi
Lorem ipsum dolor sit amet, consectetur adipiscing elit.

영문자(혹은 _) 세 개가 연달아 붙어있는 문자와 일치합니다.


앞서 본 예제에서 세 개가 연달아 붙어있는 문자를 표현했습니다. 더욱 쉬운 방법으로, 특정 문자수를 지정할 수 있습니다.

  • {n} : 앞에 존재하는 문자가 n번 반복되는 문자와 일치합니다.
  • {n, m} : 앞에 존재하는 문자가 n번 이상 m번 이하 반복되는 문자와 일치합니다.
  • {n,} : 앞에 존재하는 문자가 n번 이상 반복되는 문자와 일치합니다.
# 예문
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
# 정규 표현식
\w{3}
# 결과 http://rubular.com/r/x8IiwvgbG8
Lorem ipsum dolor sit amet, consectetur adipiscing elit.

바로 앞의 예제와 정확하게 일치하는 예제입니다. 더 가독성이 향상된 느낌입니다 :)


여러 문자와 반복하여 일치하는 표현식에 대해서 알아보았습니다. 특정 수가 지정되지 않고, n개 이상의 반복되는 문자와 일치하는 문자를 찾을 때, 의도치 않게 너무 많이 일치하는 경우가 발생할 수 있습니다. 기본적으로 탐욕적(Greedy) 방식으로 동작하기 때문입니다. ?문자를 해당 메타 문자 뒤에 붙이면 게으른(Lazy) 방식으로 동작합니다.

  • *?
  • +?
  • {n,}?

예를 들어보면 더 좋을 것 같네요. i로 시작하고 n으로 끝나는 모든 문자를 찾고 싶다고 가정합시다. 다음과 같이 작성할 수 있을 것 같습니다.

# 예문
internationalization
# 정규 표현식
i\w+n
# 결과 http://rubular.com/r/em8RaHlnq9
internationalization

결과에 나온 대로, internationalization 모두가 일치했습니다. i로 시작하고 n으로 끝나는 모든 문자를 찾고 싶었지만, 일치하는 건 internationalization 통째로 하나뿐입니다.

게으른 방식으로 바꿔보겠습니다.

# 예문
internationalization
# 정규 표현식
i\w+?n
# 결과 http://rubular.com/r/fr6Ejzal0N
internationalization

앞의 예제와의 차이점은 단 하나 ?문자입니다. ?를 붙임으로 인해서 게으른 방식으로 동작하고 있습니다. internationalization은 i로 시작하고 n으로 끝나는 문자긴 하지만, 그 사이 사이에도 i로 시작하고 n으로 끝나는 문자가 여럿 존재합니다. 이렇게 요구에 따라 탐욕적 혹은 게으른 방식의 찾기를 할 수 있습니다.


다음으로는, 빼놓을 수 없는 그룹을 설명하도록 하겠습니다. 그룹은 () 문자로 지정할 수 있으며, 괄호 사이에 존재하는 표현식을 통해 찾은 결과를 묶음으로 처리할 수 있도록 해줍니다. 그룹을 통해 하나의 결과에서도 여러 가지 그룹으로 나눌 수 있고, 같은 문자가 반복되는 것을 찾거나, 원하는 방식으로 사용할 수 있습니다.

그룹은 일반적으로 \1과 같이 \ 문자와 그룹의 번호로 구성됩니다. 일반적으로 전체 결과가 \0이며, 앞에서 부터 나타나는 그룹의 순서에 따라 숫자가 하나씩 증가하는 방식입니다.

# 예문
<h1>이것은 첫 번째 제목</h1>
<h2>이것은 두 번째 제목</h2>
<h3>이것은 세 번째 제목</h3>
<h4>It's also right heading</h4>
<h5>이것은 올바르지 않은 제목</h6>
# 정규 표현식
<(h[1-6])>[가-힣\w\s']+<\/\1>
# 결과 http://rubular.com/r/ZF58XxDYVw
<h1>이것은 첫 번째 제목</h1>
<h2>이것은 두 번째 제목</h2>
<h3>이것은 세 번째 제목</h3>
<h4>It's also right heading</h4>

<h5>이것은 올바르지 않은 제목</h6>

HTML에서 제목을 나타내는 h 태그를 찾는 정규 표현식입니다. h 태그는 h1부터 h6까지 있으므로, h[1-6]로 표현하였습니다. 태그의 경우, 여는 태그 <h1> 와 닫는 태그 </h1>가 일치하므로, (h[1-6])로 그룹을 만들고, 만들어진 그룹을 이용해 닫는 태그를 지정해놓고, 그 사이에 한글 및 영문자, 공백, ' 문자 한 개 이상 존재하는 문자를 찾도록 하였습니다. 따라서, h1에서 h4까지는 제대로 열고 닫혀서 정상적으로 찾아졌습니다. 하지만 마지막 문자열의 경우, 여는 태그는 <h5>로 시작했지만, 닫는 태그의 경우 </h6>로, 제대로 여닫아지지 않았기 때문에 찾아지지 않았습니다. 이처럼, 그룹을 잘 활용하면 다양한 문제를 해결할 수 있습니다.


앞서 설명한 부분은 정규 표현식을 어느 정도 접해보신 분들이라면, 이미 아는 부분이어서 지루하셨거나, 훑어보며 내려오셨을 것 같습니다. 저도 주로 사용하는 부분은 이 정도였습니다. 제목처럼 (좀 더) 깊이 들어가 보도록 하겠습니다. (“좀 더”입니다. 역시나 싱거울 수 있습니다)

특정한 문자 앞에 일치하는 문자만 찾는 전방탐색과 특정한 문자 뒤에 일치하는 문자만 찾는 후방탐색이 있습니다. 전방탐색과 후방탐색으로 일치된 문자는 그룹처럼 보이지만 결과에는 포함되지 않습니다.

  • (?=) : 전방탐색. 찾고자 하는 표현식 뒤에 전방탐색 표현식을 넣으며(?=) 사이에 표현식을 넣습니다. 전방탐색 표현식을 통해 문자가 존재하고, 그 앞에 찾고자 하는 문자가 존재할 때 일치합니다. 특정 문자가 포함된 문자를 찾고 싶지만 결과에 포함하고 싶지는 않을 때 사용합니다.
  • (?<=) : 후방탐색. 후방탐색 표현식 (?<=) 사이에 표현식을 넣고, 찾고자 하는 표현식을 작성합니다. 후방탐색 표현식을 통해 문자가 존재하고, 그 뒤에 찾고자 하는 문자가 존재할 때 일치합니다. 전방탐색과 유사하죠.

이와 반대인 탐색이 있습니다.

  • (?!) : 부정형 전방탐색. 전방탐색과 반대로, 부정형 전방탐색 내의 표현식이 일치하지 않고, 찾고자 하는 문자가 존재할 때 일치합니다.
  • (?<!) : 부정형 후방탐색. 후방탐색과 반대로, 부정형 후방탐색 내의 표현식이 일치하지 않고, 찾고자 하는 문자가 존재할 때 일치합니다.

설명이 굉장히 어렵네요. 예제를 통해서 살펴보면 더 이해하기 쉬울 것 같습니다.

# 예문
2400원
3600원
28392830원
238493엔
원2839283
# 정규 표현식
\d+(?=원)
# 결과 http://rubular.com/r/XrZGoLjUbm
2400
3600
28392830
238493엔
원2839283

원화 표기로 작성된 문자를 찾는 예제입니다. 전방탐색을 사용했고, 숫자와 이라는 문자가 들어가야 일치하지만, 결과에는 이 포함되지 않은 걸 확인할 수 있습니다.


깊이 들어가려면 끝이 없는 게 정규 표현식인 것 같습니다. 이 글에서의 마지막은 역참조(Backreference)입니다. ‘어떤 그룹이 있으면, 이런 문자를 찾아라.’ 같은 개념이라고 볼 수 있습니다. 수를 찾는데, 괄호로 묶여있는 수의 경우 괄호까지 찾고 싶을 때 사용할 수 있습니다. 일종의 분기문이라고 할 수 있습니다.

  • (?(n)) : ?(n)의 n에 그룹의 번호를 넣습니다. 이 뒤에 나타나는 것은 n번 그룹이 존재할 때 일치해야 하는 표현식입니다.
# 예문
12345
12.3456
(120.293)
(18729.28
2839283)
# 정규 표현식
(\()?\d+(\.\d+)?(?(1)\))
# 결과 http://rubular.com/r/H2pt3TCCqF
12345
12.3456
(120.293)

(18729.28
2839283
)

위의 설명에서 언급한 내용을 예제로 만들어 보았습니다. (\()?( 문자를 그룹으로 만들었고, ? 메타 문자를 통해 있을 수도 없을 수도 있다는 것을 표현한 것입니다. 그리고, 숫자(소수점 포함)를 표현하였고, (?(1)\))를 통해 앞서 설명한 역참조를 사용하였습니다. (1)의 경우, 가장 첫 그룹인 ( 문자 유무를 판단하고, 이 그룹이 있으면 ) 문자가 있는지 찾습니다. 따라서, 결과에 나온 것처럼, 숫자들 혹은 괄호로 둘러싸인 숫자를 찾습니다. 괄호가 제대로 여닫아지지 않는 숫자들은 그냥 숫자에만 일치하는 것을 확인할 수 있습니다.


새로 알게된 내용만 적을까 하다가, 정리하는 느낌으로 쓰다보니 글이 길어졌네요. 이만큼만 알아도 문자를 다루는 데 훨씬 수월할 것이라 생각합니다. 이 글이 조금이라도 도움이 되었으면 좋겠습니다. 하지만, 여기에 나오지 않은 여러 정규 표현식들이 존재합니다. 흥미가 생겼다면, 더 많은 정규 표현식을 익히고 공유해주시면 좋을 것 같네요 :)


간단하게 짜놓은 예제를 버리기 아쉬워서… 덧붙입니다. 개미 수열이라고 불리는 읽고 말하기 수열을 정규 표현식을 이용해 구현해보았습니다.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade