자습시간

정규표현식 기초

happy-nut 2016. 3. 14. 20:55

이번에는 정규표현식(Regular Expression)에 대해 글을 써볼까 합니다. 

기초적인 부분이라 한번 훅 훑어 보는 것도 프로그래밍 지식을 환기시키는데 도움이 되리라 생각됩니다.


0. 정규표현식(Regular Expression) 이란?

정규표현식은 특정한 규칙을 가진 문자열의 집합을 표현하는 데 사용되는 (형식)언어 입니다. 

가령, 다음과 같은 데이터가 있다고 해봅시다.

안녕 ! 내 이름은 Poqw 고 내 전화번호는 010-1234-4321 이야. 앞으로 친하게 지내자.

만약 이 데이터에서 전화번호만 쏙 뽑아내고 싶다면 숫자를 검색하고 거기서부터 substr( )같은 함수로 14개 까지만 잘라내면 될 것입니다.

하지만 만약 데이터가 여러가지고, 다음과 같다면?

안녕 ! 내 이름은 Poqw 고 내 전화번호는 010-1234-4321 이야. 앞으로 친하게 지내자.

안녕 ! 내 이름은 Eric123 이고 내 전화번호는 02-123-4201 이야. 앞으로 친하게 지내자.

안녕 ! 내 이름은 Candy 고 내 전화번호는 011-1234-5678 이야. 앞으로 친하게 지내자.

안녕 ! 내 이름은 Sweet92 이고 내 전화번호는 922-1223-4567 이야. 앞으로 친하게 지내자.

안녕 ! 내 이름은 Daniel 이고 내 전화번호는 055-5555-5555 이야. 앞으로 친하게 지내자.

. . . 

전화번호의 숫자도 매번 달라지고, 이름에 숫자가 들어갈 수도 있어서 앞서 생각했던 방법으로는 힘들어 보입니다.

그러나 이런 경우. 정규표현식을 이용하면 데이터를 편리하게 뽑아올 수가 있습니다.


1. 정규표현식을 알아야 하는 이유

C언어, 파이썬(Python), Javascript, 파워쉘(PowerShell) 등 여러 프로그래밍 언어 종류를 막론하고 정규표현식은 굉장히 자주 쓰이는 부분이기 때문에, 이를 아느냐 모르느냐는 여러가지 부분에서 다소 차이가 납니다. 그 중 가장 큰 차이를 뽑으라면 당연히 시간의 절약이 있겠지요.

정규표현식을 쓰지 않으면,  파싱(Parsing)하거나 서칭(Searching)하는 함수를 일일이 만들어야 하고, 테스트 코드도 만들어 값이 누락되는 버그 등이 없는지 계속 확인을 해야 합니다. 다른 코어 함수를 짜기에도 바쁜데 이런 곳에 노력을 계속 투자하자니 시간이 아까움은 물론이거니와 집중력도 흐트러져 비효율적으로 작업을 할 수 밖에 없게 됩니다.

(+ 추가적으로, 그런 직접 짠 함수나 테스트코드가 빠지기 때문에 좀 더 코드가 깔끔해집니다)

그런 면에서, 정규표현식을 알아두는 것은 프로그래밍을 함에 있어 굉장히 든든한 무기를 가지게 된 셈입니다.


2. 정규표현식 표현

정규표현식에는 정규표현식에만 사용되는 특수한 표현들이 있습니다. 영어로 따지자면 be동사처럼 몰라서는 안되는 아주 중요한 부분이지만, 워낙 자주쓰이다 보니 정규표현식을 몇 개 쓰다보면 저절로 외워지게 되는 녀석들입니다.

.

새행문자(\n)를 제외한 모든 문자와 일치 

\

특별한 의미를 가진 문자를 이스케이프 시켜준다.  문자클래스가 시작되기 전에 사용되어야 한다

문자클래스 시작 

문자클래스 종료 

문자 그룹 시작 

문자 그룹 종료 

문자 그룹이나 바로 앞 문자가 한 번 이상 반복될 수 있다는 것을 의미 

문자 그룹이나 바로 앞 문자가 0번 혹은 그 이상 반복될 수 있다는 것을 의미 

문자 그룹이나 바로 앞 문자가 0번 혹은 한 번 반복될 수 있다는 것을 의미 

앞 뒤의 문자그룹에 일치할 수 있다는 것을 의미 

^

문자열의 시작과 일치 

$ 

문자열의 끝과 일치 

- 

문자열의 범위를 나타냄 


새행문자란 Enter 키 처럼 한 줄 띄워주는 특수문자를 의미합니다. (\n)

문자클래스는 대괄호 [ 와 ] 로 감싸져 있는 표현을 의미합니다. [a] 는 문자클래스입니다. [a-z] 역시 마찬가지로 문자클래스 이며 소문자 알파벳과 일치합니다.

문자 그룹은 소괄호 ( 와 ) 로 감싸져 있는 표현을 의미합니다. (a) 는 문자 그룹입니다.

여기서 문자클래스와 문자 그룹에 대해 정리하고 들어가자면, 문자클래스는 괄호 안에 여러 문자를 묶어 이들 중 '하나'를 의미하지만, 문자 그룹은 괄호 안에 추출한 패턴표현을 그대로 지정합니다. 예를 들어, [a-z]를 하게 되면 소문자 알파벳 중 하나를 의미하게 되어 a, b, c 등이 일치하게 되지만, (a-z)를 하게 되면 a-z 만을 일치하게 됩니다. a, b, c 등은 일치하지 않습니다.

^(꺽쇠) 같은 경우 문자열의 시작과 일치하지만, 조금 특수한 경우에도 쓰입니다. 바로 문자클래스( [ ] )의 시작지점에 쓰이게 되는 경우인데, 이 경우 해당 표현의 역(반대 의미)을 의미합니다. 예를 들어 [^a-zA-Z] 는 [a-zA-Z]의 여집합을 의미하고, 결과적으로 알파벳이 아닌 문자(1234, #%@$, 23#% 등)와 일치되는 표현을 찾습니다.

+, *, ? 는 반복과 관련된 특수표현입니다.

만약 (a)+ 를 하게되면 "aa", "aaaaaa"와 일치하지만 "b", "c" 와는 일치하지 않습니다. a라는 표현이 한 번 이상 반복되는지만 보게 됩니다.

만약 (a)* 를 하게되면 "aa", "aaaaaa"와 일치하고 심지어 공백과도 일치하지만 "b", "c" 와는 일치하지 않습니다. a라는 표현이 0번 이상 반복되는지만 검사합니다.

만약 (a)? 를 하게 되면 "a"와 공백과 일치하고 "aaa" , "b", "c" 와는 일치하지 않습니다. a라는 표현이 딱 한 번 쓰였는지, 안 쓰였는지를 검사합니다.

소괄호로 감싸서 굳이 문자 그룹으로 만들지 않더라도, a+, a*, a? 이런 식으로 사용하게 되면 +,*,? 바로 앞에 쓰인 문자는 자동으로 문자 그룹으로 취급합니다.

만약 위 표에 있는 표현들을 실제 문자열로 걸러내고 싶다면 문자 앞에 이스케이프 (\)를 사용하면 됩니다. 즉 \+ 를 하게 되면 \가 한 번 이상 쓰였는지를 판단하는 게 아니라 문자 그대로 +와 일치하는 지 검사하게 됩니다.

(만약 \가 한 번 이상 쓰인 것과 일치하고 싶다면 \\+를 하면 되겠지요)


3. 간단한 예

위 특수표현들은 아래의 예시를 차근 차근 보다보면 어느 정도 감이 잡힐 겁니다.

 ^.

 문자열의 첫 문자가 어느 문자든 상관없이 일치 (결과적으로 빈 문자열 외에 모두가 해당됩니다)

 .+

 한 번 이상 반복된 그룹 문자열 (.은 문자 하나를 의미하므로, 이게 한 번 이상 반복되는지를 검사하게 되어 결과적으로 빈 문자열 외에 모두가 해당됩니다)

 .*

 문자열의 모든 문자와 일치 (아무것도 걸러지지 않습니다)

 [abc]

 a , b, c 와 일치. 한 글자만 해당되므로 "ab"나 "bc"와는 일치하지 않습니다.

ab|bc 

 "ab" 혹은 "bc" 와 일치. "abc"와는 일치하지 않습니다.

fox(es)? 

 "fox"나 "foxes" 와 일치.  "foxeses"와는 일치하지 않습니다.

 <.+>

 <>사이에 적어도 문자 하나를 포함하면 일치. "<>"는 일치하지 않습니다. 

^[0-9]*\.[0-9]+$

 "2.53", "3.141519"등 과 일치. "1.2e"는 일치하지 않습니다.


4. 추가적인 기호

정규표현식에는 위에서 말씀드린 것 외에도 정해놓은 규칙이 있습니다.

\w

 영 숫자, 문자, 밑줄 기호와 일치. [a-zA-Z0-9]와 같습니다.

\W 

 비 영순자 문자와 일치. [^a-zA-Z0-9]와 같습니다.

\d 

 숫자와 일치. [0-9]와 같습니다.

\D 

 비 숫자와 일치. [^0-9]와 같습니다.

\s 

 Tab과 개행문자를 포함하여 공백 문자와 일치. 

\S 

 비공백문자와 일치. 

\n 

 Line Feed 문자와 일치(0x0A) 

\r 

 Carriage Return 문자와 일치(0x0D)

\t 

 Tab 문자와 일치(0x09) 


5. 간단한 예시 2

추가적인 기호에 대한 이해는 다음 예시로 대신하겠습니다.

 \W$

알파벳 문자로 끝나지 않는 문자열과 일치 

^\t+ 

문자열의 시작에서 하나 이상의 Tab문자와 일치 


6. 최대패턴일치(Greedy)와 최소패턴일치(Non-Greedy)

정규표현식에는 최대패턴일치와 최소패턴일치가 존재하며, 기본적으로 최대패턴일치를 적용합니다.
최대 패턴일치를 설명하기 앞서 다음 문자열이 있다고 가정해 봅시다.

Some<b>HTML</b> markup

<b>, </b>와 같은 HTML TAG만 걸러내고 싶어서 <.*> 라는 정규표현식을 사용하게 되면 애당초 뽑고 싶었던 <b> 나 </b>가 아닌 <b>HTML</b>가 일치됩니다. 분명 <b>, </b>, <b>HTML</b> 모두 <.*>와 일치하는 문자열입니다. 그런데도 <b>HTML</b>만 일치되는 것은 최대패턴일치가 적용되었기 때문입니다. 

정리하자면, 정규표현식으로 걸러지는 데이터 중 원시 문자열과 최대한 곂치는 데이터를 뽑아내는 게 최대패턴일치고, 반대로 최소로 곂치는 데이터를 뽑아내는 게 최소패턴일치 입니다. 

<b>나 </b>를 일치시키기 위해서는 최소패턴일치로 바꾸어야 하는데, 어렵지 않습니다. 반복을 의미하는 특수 표현( *, +, ? )뒤에 ? 만 붙이면 됩니다.

즉, <.*?>를 하게 되면 최소패턴일치가 적용되며 <b>와 </b>가 걸러집니다.


정규표현식은 몇 번 사용하다 보면 익숙하지 않으셨던 분들도 '아, 생각보다 어렵지 않네?' 하는 생각이 들 만큼 금방 감을 잡을 수 있습니다. (정규표현식이 불편하고 어려워보였던 저 또한 그랬으니까요)

그럼, 이상으로 좋은 하루 되시길 바라며, 정규표현식에 대한 포스팅을 마치도록 하겠습니다.