[Python] re 모듈, 정규 표현식(정규식) 개념과 완전 정복하기(regex cheat sheet) SQL, HIVE, PySpark에서의 regex
이번 포스트에서는 정규표현식의 개념과, python에서 정규표현식을 지원하는 re 모듈에 대해서 정리하겠습니다.
1. 정규 표현식이란?
정규 표현식(Regular Expressions)은 복잡한 문자열을 처리할 때 사용하는 기법으로, 파이썬만의 고유 문법이 아니라 문자열을 처리하는 모든 곳에서 사용, 정규식이라고도 부름
2. 정규 표현식의 기초, 메타 문자
※ 메타 문자란 원래 그 문자가 가진 뜻이 아닌 특별한 용도로 사용하는 문자
. ^ $ * + ? { } [ ] \ | ( )
1) 문자 클래스 [ ]
- 문자 클래스로 만들어진 정규식은 '[ ] 사이의 문자들과 매치'를 의미
예를 들어, 정규 표현식
[abc]
는 'a, b, c 중 한 개의 문자와 매치'를 뜻함
즉, 어떠한 문자열에("defg") 위의 정규식이 하나도 해당되지 않을 때, '[abc]정규식와 매치하지 않는다.' 라고함
- [ ] 안의 두 문자 사이에 하이픈(-)을 사용하면 두 문자 사이의 범위(From - To)를 의미
예를 들어, 정규 표현식
[a-z]
는 [abcdefghijklmnopqrstuvwxyz]와 같음.
- 문자 클래스를 사용한 대표적으로 많이 쓰이는 정규식은 다음과 같음
[a-zA-Z]#모든 영문자
[0-9]#모든 숫자
[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]#모든 한글
자주 사용하는 문자 클래스
- \d - 숫자와 매치, [0-9]와 동일한 표현식
- \D - 숫자가 아닌 것과 매치, [^0-9]와 동일한 표현식
- \s - whitespace 문자와 매치, [ \t\n\r\f\v]와 동일한 표현식, 맨 앞의 빈 칸은 공백문자(space)를 의미
- \S - whitespace 문자가 아닌 것과 매치, [^ \t\n\r\f\v]와 동일한 표현식
- \w - 문자+숫자(alphanumeric)와 매치, [a-zA-Z0-9_]와 동일한 표현식
- \W - 문자+숫자(alphanumeric)가 아닌 문자와 매치, [^a-zA-Z0-9_]와 동일한 표현식
대문자로 사용된 것은 소문자의 반대임을 추측할 수 있다.
2) Dot(.)
- 정규 표현식의 Dot(.) 메타 문자는 줄바꿈 문자인 \n을 제외한 모든 문자와 매치됨을 의미
a.b
는 'a + 모든문자 + b'를 뜻함
a와 b라는 문자 사이에 어떤 문자가 들어가도 모두 매치된다는 의미
*a와 b사이에 어떠한 문자가 반드시 하나는 있어야 된다.
- 메타 문자가아닌 점(dot)을 의미하는 문자 '.' 를 사용하려면
a[.]b
'a + Dot(.)문자 + b'를 뜻함
3) 반복 (*)
- 반복을 의미하는 *
ca*t
정규식 | 문자열 | Match 여부 | 설명 |
ca*t | ct | Yes | "a"가 0번 반복되어 매치 |
ca*t | cat | Yes | "a"가 0번 이상 반복되어 매치 (1번 반복) |
ca*t | caaat | Yes | "a"가 0번 이상 반복되어 매치 (3번 반복) |
4) 반복 (+)
- 반복을 나타내는 또 다른 메타 문자 +(최소 1번 이상 반복)
ca+t
'c + a(1번 이상 반복) + t'를 뜻함
정규식 | 문자열 | Match 여부 | 설명 |
ca+t | ct | No | "a"가 0번 반복되어 매치되지 않음 |
ca+t | cat | Yes | "a"가 1번 이상 반복되어 매치 (1번 반복) |
ca+t | caaat | Yes | "a"가 1번 이상 반복되어 매치 (3번 반복) |
5) 반복 ({m,n}, ?)
- 반복 횟수를 지정하는 메타 문자
ca{2}t
'c + a(반드시 2번 반복) + t'
정규식 | 문자열 | Match | 설명 |
ca{2}t | cat | No | "a"가 1번만 반복되어 매치되지 않음 |
ca{2}t | caat | Yes | "a"가 2번 반복되어 매치 |
{3,}#사용하면 반복 횟수가 3 이상
{,3}#사용하면 반복 횟수가 3 이하 의미 #생략된 m은 0과 동일)
{1,}은 +와 동일하고, {0,}은 *와 동일
ca{2,5}t
'c + a(2~5회 반복) + t'
정규식 | 문자열 | Match 여부 | 설명 |
ca{2,5}t | cat | No | "a"가 1번만 반복되어 매치되지 않음 |
ca{2,5}t | caat | Yes | "a"가 2번 반복되어 매치 |
ca{2,5}t | caaaaat | Yes | "a"가 5번 반복되어 매치 |
- {0, 1}을 의미하는 ? 메타 문자
ab?c
'a + b(있어도 되고 없어도 된다) + c'
정규식 | 문자열 | Match 여부 | 설명 |
ab?c | abc | Yes | "b"가 1번 사용되어 매치 |
ab?c | ac | Yes | "b"가 0번 사용되어 매치 |
b 문자가 있거나 없거나 둘 다 매치되는 경우
2. 파이썬에서 정규 표현식을 지원하는 re 모듈
파이썬은 정규 표현식을 지원하기 위해 re(regular expression의 약어) 모듈을 제공
re module은 파이썬 설치시 기본적으로 탑재되어 있는 기본 라이브러리
import re
p = re.compile('ab*')
1) 정규식을 이용한 문자열 검색
method | 목적 |
match() | 문자열의 처음부터 정규식과 매치되는지 조사한다. |
search() | 문자열 전체를 검색하여 정규식과 매치되는지 조사한다. |
findall() | 정규식과 매치되는 모든 문자열(substring)을 리스트로 돌려준다. |
finditer() | 정규식과 매치되는 모든 문자열(substring)을 반복 가능한 객체로 돌려준다. |
match, search는 정규식과 매치될 때는 match 객체를 돌려주고, 매치되지 않을 때는 None을 돌려줌
match
import re
text = "abc"
text2 = "3 python"
p = re.compile('[a-z]+')
m = p.match(text)
n = p.match(text2)
print(m)
#<re.Match object; span=(0, 3), match='abc'> 출력
print(n)
#None 출력
if m:
print('Match found: ', m.group())
else:
print('No match')
#Match found: abc 출력
if n:
print('Match found: ', m.group())
else:
print('No match')
#No match 출력
match 객체의 메서드
- match 객체의 어떤 문자열이 매치되었는지
- 매치된 문자열의 인덱스 시작과 끝
method | 목적 |
group() | 매치된 문자열을 돌려준다. |
start() | 매치된 문자열의 시작 위치를 돌려준다. |
end() | 매치된 문자열의 끝 위치를 돌려준다. |
span() | 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 돌려준다. |
#Match 객체의 메소드
print(m.group(),m.start(),m.end(),m.span())
#abc, 0, 3, (0,3) 출력
search
import re
text = "abc"
text2 = "3 python"
p = re.compile('[a-z]+')
a = p.search(text)
print(a)
#<re.Match object; span=(0, 3), match='abc'> 출력
b = p.search(text2)
print(b)
#<re.Match object; span=(2, 8), match='python'> 출력
findall
import re
text3 = "life is too short"
q = p.findall(text3)
print(q)
#['life', 'is', 'too', 'short'] 출력
finditer
import re
text3 = "life is too short"
w = p.finditer(text3)
print(w)
#<callable_iterator object at 0x000001A3A8388400> 출력
for i in w:
print(i)
#<re.Match object; span=(0, 4), match='life'>
#<re.Match object; span=(5, 7), match='is'>
#<re.Match object; span=(8, 11), match='too'>
#<re.Match object; span=(12, 17), match='short'>
컴파일 옵션
- DOTALL(S): . 이 줄바꿈 문자를 포함하여 모든 문자와 매치할 수 있도록 함
- IGNORECASE(I): 대소문자에 관계없이 매치할 수 있도록 함
- MULTILINE(M): 여러줄과 매치할 수 있도록 함 (^, $ 메타문자의 사용과 관계가 있는 옵션)
- VERBOSE(X): verbose 모드를 사용할 수 있도록 함 (정규식을 보기 편하게 만들수 있고 주석등을 사용할 수 있게됨)
옵션을 사용할 때는 re.DOTALL처럼 전체 옵션 이름을 써도 되고 re.S처럼 약어를 써도 됨
white space, 백슬래쉬(\) 문제와 Raw String 규칙
백슬래쉬로 인해 white space가 문자를 표현 할 수 있는데, 정규식에서 \자체를 표현하려면 2번을 사용해서 표현
#6. 백슬래쉬와 Raw String 규칙
#\t\n\r\f\v 와 같이 white space에 대해서는 정규식을 적용하고 싶다면 \를 2번 사용
p = re.compile('\\nowhitespace')
print(p.search('example\nowhitespace'))
#<re.Match object; span=(7, 19), match='\nowhitespace'> 출력
p = re.compile('\\\\nowhitespace')
print(p.search('example\\nowhitespace'))
#<re.Match object; span=(7, 20), match='\\nowhitespace'> 출력
백슬래쉬를 1번만 사용하면서 \문자를 있는 그대로 표현하고 싶다면 Raw String 규칙인 r문자를 사용
p = re.compile(r'\nowhitespace')
print(p.search('example\nowhitespace'))
#<re.Match object; span=(7, 19), match='\nowhitespace'> 출력
3. 메타문자(문자열을 소비시키지 않는 메타 문자)
+, *, [], {} 등의 메타문자는 매치가 진행될 때 현재 매치되고 있는 문자열의 위치가 변경된다(보통 소비된다고 표현
|
- 메타 문자는 or과 동일한 의미
>>> p = re.compile('Crow|Servo')
>>> m = p.match('CrowHello')
>>> print(m)
<re.Match object; span=(0, 4), match='Crow'>
^
- ^ 메타 문자는 문자열의 맨 처음과 일치함을 의미
- re.MULTILINE을 사용할 경우에는 여러 줄의 문자열일 때 각 줄의 처음과 일치
>>> print(re.search('^Life', 'Life is too short'))
<re.Match object; span=(0, 4), match='Life'>
>>> print(re.search('^Life', 'My Life'))
None
$
- $ 메타 문자는 ^ 메타 문자와 반대의 경우
- 문자열의 끝과 매치함을 의미
>>> print(re.search('short$', 'Life is too short'))
<re.Match object; span=(12, 17), match='short'>
>>> print(re.search('short$', 'Life is too short, you need python'))
None
\A
- \A는 문자열의 처음과 매치됨을 의미
- ^ 메타 문자와 동일한 의미이지만 re.MULTILINE 옵션을 사용할 경우에는 다르게 해석
- re.MULTILINE 옵션을 사용할 경우 ^은 각 줄의 문자열의 처음과 매치되지만 \A는 줄과 상관없이 전체 문자열의 처음하고만 매치
\Z
- \Z는 문자열의 끝과 매치됨을 의미
- \A와 동일하게 re.MULTILINE 옵션을 사용할 경우 $ 메타 문자와는 달리 전체 문자열의 끝과 매치
\b
- \b는 단어 구분자(Word boundary)
- \b는 파이썬 리터럴 규칙에 의하면 백스페이스(BackSpace)를 의미하므로 백스페이스가 아닌 단어 구분자임을 알려 주기 위해 r'\bclass\b'처럼 Raw string임을 알려주는 기호 r을 반드시 붙여주어야 함
>>> p = re.compile(r'\bclass\b')
>>> print(p.search('no class at all'))
<re.Match object; span=(3, 8), match='class'>
\B
- \B 메타 문자는 \b 메타 문자와 반대의 경우
- whitespace로 구분된 단어가 아닌 경우에만 매치
4. 그룹핑
문자열이 계속해서 반복되는지 조사하는 정규식을 그룹핑을 통해 해결
(ABC)+
위와 같이 문자를 괄호로 묶음으로써 그룹핑 시킴
import re
p = re.compile(r"\w+\s+\d+[-]\d+[-]\d+")#이름 + " " + 전화번호 형태의 문자열을 찾는 정규식
m = p.search("park 010-1234-1234")
#이름만 뽑아내는 정규식
p = re.compile(r"(\w+)\s+\d+[-]\d+[-]\d+")#0번 째 그룹은 park 010-1234-1234, 1번 째 그룹은 park이다.
m = p.search("park 010-1234-1234")
print(m.group(1))#park 출력
#이름과 전화번호를 그룹핑하는 정규식
p = re.compile(r"(\w+)\s+(\d+[-]\d+[-]\d+)")
m = p.search("park 010-1234-1234")
print(m.group(1),m.group(2))#park 010-1234-1234 출력
#이름과 전화번호와 국번만 뽑아내는 정규식
p = re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)")#2번 째 그룹 안에 중첩된 그룹을 만들 수 있다(3번 째 그룹)
m = p.search("park 010-1234-1234")
print(m.group(1), m.group(2), m.group(3))#park 010-1234-1234 010 출력
group(인덱스) | 설명 |
group(0) | 매치된 전체 문자열 |
group(1) | 첫 번째 그룹에 해당되는 문자열 |
group(2) | 두 번째 그룹에 해당되는 문자열 |
group(n) | n 번째 그룹에 해당되는 문자열 |
그룹핑 문자열 재참조
#그룹핑 문자열 재참조
p = re.compile(r'(\b\w+)\s+\1')#(\b\w+)\s+\1은 (그룹) + " " + 그룹과 동일한 단어, 2개의 동일한 단어를 연속적으로 사용해야만 매치
m = p.search('hello python python python bye')
print(m.group())#python python 출력
그루핑된 문자열에 이름 붙이기
#그룹핑 문자열 이름 붙여 재참조
p = re.compile(r'(?P<myfirstgroup>\b\w+)\s+(?P=myfirstgroup)')#그룹핑 명에 이름을 부여하면 (?P=그룹핑 명) 처럼 사용하여 재참조한다.
m = p.search("hello python python python bye")
print(m.group())#python python 출력
전방형 탐색
예를 들어, http:라는 검색 결과에서 :을 제외하고 출력하고자 할 때 사용
- 긍정형 전방 탐색((?=...)) - ... 에 해당되는 정규식과 매치되어야 하며 조건이 통과되어도 문자열이 소비되지 않음
- 부정형 전방 탐색((?!...)) - ...에 해당되는 정규식과 매치되지 않아야 하며 조건이 통과되어도 문자열이 소비되지 않음
.*[.](?!bat$).*$ #확장자가 bat가 아닌 경우에만 통과된다는 의미
.*[.](?!bat$|exe$).*$ #확장자가 bat이거나 exe이면 제외하라는 조건
문자열 바꾸기
sub 메소드를 이용하여 문자열을 바꿀 수 있음
#문자열 변환 sub 메소드
p = re.compile(r'(score|password|grade)')
m = p.sub('encoding target','your score, password, grade, name, telephone is here')#1번째 매개변수자리엔 변환할 문자열, 2번째 문자열엔 적용 대상 문자열
print(m)#your encoding target, encoding target, encoding target, name, telephone is here 출력
m = p.sub('encoding target','your score, password, grade, name, telephone is here',count=1)#맨 앞의 1개만 변환
print(m)#your encoding target, password, grade, name, telephone is here 출력
m = p.subn('encoding target','your score, password, grade, name, telephone is here',2)#맨 앞의 2개만 변환하여 튜플로 출력
print(m)#('your encoding target, encoding target, grade, name, telephone is here', 2) 출력
SQL, HIVE에서의 regex
ORACLE
SQL | Description |
REGEXP_LIKE(열 이름, 조건) | 정규 표현식을 사용하여 조건 검색을 실행 |
REGEXP_REPLACE(문자열 또는 열 이름, 조건, 치환 문자열) | 지정한 정규 표현식에 일치하는 부분을 지정한 다른 문자열로 치환함 |
REGEXP_INSTR(문자열 또는 열 이름, 조건) | 지정한 조건(정규 표현식)에 일치하는 부분의 최초의 위치를 반환함. |
REGEXP_SUBSTR(문자열 또는 열 이름, 조건) | 지정된 정규 표현식에 일치하는 부분 문자열을 찾아 결과로 리턴 |
MySQL
version 8이전 | version8이상 | 설명 |
REGEXP | REGEXP | WHERE 문에 사용하며, 기본적으로 정규식을 사용할 때 사용하는 함수 |
RLIKE | REGEXP_LIKE | WHERE 문에 사용하며, REGEXP랑 같은 용도로 사용된다. 정규식을 사용할 때 사용하는 함수 |
사용불가 | REGEXP_REPLACE | SELECT 문에 사용하며, SELECT REGEXP_REPLACE(대상 컬럼, 대상 문자열, 변경 문자열) 과 같이 사용한다. |
REGEXP
address 컬럼이 특수기호가 들어간 row만 출력
RLIKE
address 컬럼이 특수기호가 들어간 row만 출력
REGEXP_LIKE
address 컬럼이 특수기호가 들어간 row만 출력
REGEXP_REPLACE
actor 테이블의 last_update 컬럼이 숫자가 있으면 *로 변환
HIVE
hive에서의 정규식도 mysql과 사용하는 함수가 대부분이 같다.'
기본적으로 2개의 함수는 대표적으로 많이 쓰인다.
RLIKE
REGEXP_REPLACE
IF, RLIKE, REGEXP_REPLACE를 활용하여 다음과 같이 address컬럼에 특수기호가 들어가 있는 row에 숫자를 마스킹 처리 하도록 표현할 수도 있다.
다만 하이브는 특수기호 자체를 표현하는데에 있어서 \사용에 유의해야 한다.
REGEXP_REPLACE (CL, '\(\\d+\)\.\(\\d+\)\.\(\\d+\)', '')
위의 하이브 수식은 아래의 파이썬에서 의미하는 정규식의 의미를 나타낸다.
(정확히는 파이썬이 갖고 있는 함수가아닌 pyspark에서의 REGEXP_REPLACE)
REGEXP_REPLACE(CL,'(\d+)\.(\d+)\.(\d+)','')
PySpark에서의 regex
PySpark의 정규식은 특수기호 자체를 표현하는데 있어서, HIVE혹은 MYSQL과는 다르게 python언어를 따라가게 된다.
기본적으로 2개의 함수는 대표적으로 많이 쓰인다.
RLIKE
REGEXP_REPLACE
F.when(F.col('a.UI_VER').rlike('^[0-9]*$'),F.regexp_replace(F.col('a.CL'),'(\d+)\.(\d+)','')).otherwise(F.lit(0))
F.regexp_replace(F.col('a.CL'),'(\d+)\.(\d+)\.(\d+)','')
참조: