정규 표현식 Regex

1. 정규 표현식 (Regular Expression)

정규 표현식은 특정한 규칙을 가진 문자열의 패턴을 표현하는 데 사용하는 표현식(Expression)으로 텍스트에서 특정 문자열을 검색하거나 치환할 때 흔히 사용된다. 예를 들어, 웹페이지에서 전화번호나 이메일 주소를 발췌한다거나 로그파일에서 특정 에러메시지가 들어간 라인들을 찾을 때 정규 표현식을 사용하면 쉽게 구현할 수 있다. 정규 표현식은 간단히 정규식, Regex 등으로 불리우곤 한다.

2. 정규 표현식 사용

정규식에서 가장 단순한 것은 특정 문자열을 직접 리터럴로 사용하여 해당 문자열을 검색하는 것이다. 예를 들어, 로그 파일에 "에러 1033" 이라는 문자열을 검색하여 이 문자열이 있으면 이를 출력하고 없으면 None을 리턴하는 경우이다. 이러한 간단한 검색을 파이썬에서 실행하는 방법은 아래와 같다.

먼저 파이썬에서 정규표현식을 사용하기 위해서는 Regex를 위한 모듈인 re 모듈을 사용한다.
re 모듈의 compile 함수는 정규식 패턴을 입력으로 받아들여 정규식 객체를 리턴하는데, 즉 re.compile(검색할문자열) 와 같이 함수를 호출하면 정규식 객체 (re.RegexObject 클래스 객체)를 리턴하게 된다.
re.RegexObject 클래스는 여러 메서드들을 가지고 있는데, 이 중 여기서는 특정 문자열을 검색하여 처음 맞는 문자열을 리턴하는 search() 메서드를 사용해 본다. 이 search() 는 처음 매칭되는 문자열만 리턴하는데, 매칭되는 모든 경우를 리턴하려면 findall() 을 사용한다. search()는 검색 대상이 있으면 결과를 갖는 MatchObject 객체를 리턴하고, 맞는 문자열이 없으면 None 을 리턴한다.
MatchObject 객체로부터 실제 결과 문자열을 얻기 위해서는 group() 메서드를 사용한다.

import re
text = "에러 1122 : 레퍼런스 오류\n 에러 1033: 아규먼트 오류"
regex = re.compile("에러 1033")
mo = regex.search(text)
if mo != None:
    print(mo.group()) 

3. 전화번호 발췌하기

정규 표현식은 단순한 리터럴 문자열을 검색하는 것보다 훨씬 많은 기능들을 제공하는데, 즉 특정 패턴의 문자열을 검색하는데 매우 유용하다. 그 한가지 예로 웹페이지나 텍스트에서 특정 패턴의 전화번호를 발췌하는 기능에 대해 알아보자. 전화번호의 패턴은 032-232-3245 와 같이 3자리-3자리-4자리로 구성되어 있다고 가정하자. 정규식에서 숫자를 의미하는 기호로 \d 를 사용한다. 여기서 d는 digit 을 의미하고 0 ~ 9 까지의 숫자 중 아무 숫자나 될 수 있다. 따라서, 위 전화번호 패턴을 정규식으로 표현하면 \d\d\d-\d\d\d-\d\d\d\d 와 같이 될 수 있다. 아래는 이러한 패턴을 사용하여 전화번호를 발췌하는 예이다.

import re

text = "문의사항이 있으면 032-232-3245 으로 연락주시기 바랍니다."

regex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
matchobj = regex.search(text)
phonenumber = matchobj.group()
print(phonenumber)    

위 예제에서 re.compile(전화번호패턴) 함수는 전화번호 패턴에 갖는 정규식 객체를 리턴하게 되고, search()를 사용하여 첫번째 전화번호 패턴에 매칭되는 번호를 리턴한다. 그리고 이로부터 실제 전화번호를 얻기 위해서는 group() 메서드를 사용하였다.

4. 다양한 정규식 패턴 표현

위의 전화번호 예제에서는 숫자를 표현하는 \d 만을 살펴보았는데, 정규 표현식에는 매우 다양한 문법과 기능들이 제공되고 있다. 아래는 이러한 다양한 정규식 표현 중 자주 사용되는 패턴들을 정리한 것이다.

패턴 설명 예제
^ 이 패턴으로 시작해야 함 ^abc : abc로 시작해야 함 (abcd, abc12 등)
$ 이 패턴으로 종료되어야 함 xyz$ : xyz로 종료되어야 함 (123xyz, strxyz 등)
[문자들] 문자들 중에 하나이어야 함. 가능한 문자들의 집합을 정의함. [Pp]ython : "Python" 혹은 "python"
[^문자들] [문자들]의 반대로 피해야할 문자들의 집합을 정의함. [^aeiou] : 소문자 모음이 아닌 문자들
| 두 패턴 중 하나이어야 함 (OR 기능) a | b : a 또는 b 이어야 함
? 앞 패턴이 없거나 하나이어야 함 (Optional 패턴을 정의할 때 사용) \d? : 숫자가 하나 있거나 없어야 함
+ 앞 패턴이 하나 이상이어야 함 \d+ : 숫자가 하나 이상이어야 함
* 앞 패턴이 0개 이상이어야 함 \d* : 숫자가 없거나 하나 이상이어야 함
패턴{n} 앞 패턴이 n번 반복해서 나타나는 경우 \d{3} : 숫자가 3개 있어야 함
패턴{n, m} 앞 패턴이 최소 n번, 최대 m 번 반복해서 나타나는 경우 (n 또는 m 은 생략 가능) \d{3,5} : 숫자가 3개, 4개 혹은 5개 있어야 함
\d 숫자 0 ~ 9 \d\d\d : 0 ~ 9 범위의 숫자가 3개를 의미 (123, 000 등)
\w 문자를 의미 \w\w\w : 문자가 3개를 의미 (xyz, ABC 등)
\s 화이트 스페이스를 의미하는데, [\t\n\r\f] 와 동일 \s\s : 화이트 스페이스 문자 2개 의미 (\r\n, \t\t 등)
. 뉴라인(\n) 을 제외한 모든 문자를 의미 .{3} : 문자 3개 (F15, 0x0 등)

정규식 패턴의 한 예로 "에러 {에러번호}"와 같은 형식을 띄는 부분을 발췌해 내는 예제를 살펴보자. 여기서 에러 패턴은 "에러" 라는 리터럴 문자열과 공백 하나, 그 뒤에 1개 이상의 숫자이다. 이를 표현하면 아래와 같다.

import re
text = "에러 1122 : 레퍼런스 오류\n 에러 1033: 아규먼트 오류"
regex = re.compile("에러\s\d+")
mc = regex.findall(text)
print(mc)
# 출력: ['에러 1122', '에러 1033']

위 예제는 첫번째 패턴 매칭값을 리턴하는 search() 메서드 대신 패턴에 매칭되는 모든 결과를 리턴하는 findall()을 사용하였다. findall()는 결과 문자열들의 리스트(list)를 리턴한다.

5. 정규식 그룹(Group)

정규 표현식에서 ( ) 괄호는 그룹을 의미한다. 예를 들어, 전화번호의 패턴을 \d{3}-\d{3}-\d{4} 와 같이 표현하였을 때, 지역번호 3자를 그룹1으로 하고 나머지 7자리를 그룹2로 분리하고 싶을 때, (\d{3})-(\d{3}-\d{4}) 와 같이 둥근 괄호로 묶어 두 그룹으로 분리할 수 있다.
이렇게 분리된 그룹들은 MatchObject의 group() 메서드에서 그룹 번호를 파라미터로 넣어 값을 가져올 수 있는데, 첫번째 그룹 지역번호는 group(1) 으로, 두번째 그룹은 group(2) 와 같이 사용한다. 그리고 전체 전화번호를 가져올 때는 group() 혹은 group(0) 을 사용한다.

import re

text = "문의사항이 있으면 032-232-3245 으로 연락주시기 바랍니다."

regex = re.compile(r'(\d{3})-(\d{3}-\d{4})')
matchobj = regex.search(text)
areaCode = matchobj.group(1)
num = matchobj.group(2)
fullNum = matchobj.group()
print(areaCode, num) # 032 232-3245

그룹을 위와 같이 숫자로 인덱싱하는 대신 그룹이름을 지정할 수도 있는데 이를 정규식에서 Named Capturing Group 이라 한다. 파이썬에서 Named Capturing Group을 사용하는 방법은 (?P<그룹명>정규식) 와 같이 정규식 표현 앞에 ?P<그룹명>을 쓰면 된다. 그리고 이후 MatchObject에서 group('그룹명') 을 호출하면 캡쳐된 그룹 값을 얻을 수 있다.

import re

text = "문의사항이 있으면 032-232-3245 으로 연락주시기 바랍니다."

regex = re.compile(r'(?P\d{3})-(?P\d{3}-\d{4})')
matchobj = regex.search(text)
areaCode = matchobj.group("area")
num = matchobj.group("num")
print(areaCode, num)  # 032 232-3245
본 웹사이트는 광고를 포함하고 있습니다. 광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.