[Python] RegEx 정규표현식

Poke·2024년 4월 15일
post-thumbnail

RegEx 정규표현식

정규표현식(RegEx, Regular Expressions)은 특정한 규칙을 가진 문자열의 집합을 설명하는 패턴. RegEx는 문자열에 특정 패턴이 포함되어 있는지 확인하는 데 사용할 수 있음.
Python에는 re라는 패키지가 내장.

import re

RegEx 함수

RegEx는 일치하는 문자열을 검색할 수 있는 기능을 제공함.

FunctionDescription
findall모든 일치하는 항목을 포함하는 리스트를 반환
search문자열 어디에나 일치하는 항목이 있으면 Match 객체를 반환
split각 일치하는 항목에서 문자열을 분할하여 리스트를 반환
sub하나 또는 여러 일치하는 항목을 문자열로 교체

findall()

findall() 함수는 모든 일치 항목을 포함하는 리스트를 반환. 목록에는 일치하는 항목이 검색된 순서대로 들어있음.

import re

txt = "The rain in Spain"
x = re.findall("ai", txt)           # re.findall(pattern, string, flags=0)
print(x)
['ai', 'ai']

만약 일치하는 항목이 없으면 빈 리스트가 반환됨.

# 일치하는 항목이 없는 경우
import re

txt = "The rain in Spain"
x = re.findall("Portugal", txt)      
print(x)
[]

search() 함수는 문자열에서 일치하는 항목을 검색하고 일치하는 항목이 있으면 Match 객체를 반환. 일치하는 항목이 2개 이상이여도, 첫번재 항목만 반환됨.

import re

txt = "The rain in Spain"
x = re.search("ai", txt)            # re.search(pattern, string, flags=0)

print(x) # 찾은 문자를 표시X. 일치하는 객체 x에 대한 정보를 표시. 일치하는 시작 및 끝 인덱스가 포함.
print("첫번째 'ai'의 위치: ", x.start())  # 일치하는 문자열의 시작 위치를 반환.
print("일치하는 부분: ", x.group())
<re.Match object; span=(5, 7), match='ai'>
첫번째 'ai'의 위치:  5
일치하는 부분: ai

일치하는 항목이 없는경우 None 반환됨.

# 일치하는 항목이 없는 경우
import re

txt = "The rain in Spain"
x = re.search("Portugal", txt)
print(x)
None

split()

split() 함수는 각 일치에서 문자열이 분할된 리스트를 반환.

import re

txt = "The rain in Spain"
x = re.split("ai", txt)          # re.split(pattern, string, maxsplit=0, flags=0)
print(x)
['The r', 'n in Sp', 'n']

만약 일치되는 문자열이 없으면 분할되지 않아서 기존 문자열을 그대로 반환.

# 일치하는 항목이 없는 경우
import re

txt = "The rain in Spain"
x = re.split("Portugal", txt)
print(x)
['The rain in Spain']

maxsplit 파라미터를 지정하여 분할 횟수를 제어할 수 있음.

import re

txt = "The rain in Spain"
x = re.split("ai", txt, 1)    
print(x)
['The r', 'n in Spain']       # 앞에 'ai'만 제외됨.

sub()

sub() 함수는 일치 항목을 선택한 텍스트로 대체할 수 있음.

import re

txt = "The rain in Spain"
x = re.sub("ai", "9", txt)         # re.sub(pattern, repl, string, count=0, flags=0)
print(x)
The r9n in Sp9n

만약 일치하는 항목이 없으면 바뀌지 않아서 긱존 문자열 그대로 출력.

import re

txt = "The rain in Spain"
x = re.sub("Portugal", "9", txt)
print(x)
The rain in Spain

count 파라미터를 지정하여 교체 횟수를 제어할 수 있다.

import re

txt = "The rain in Spain"
x = re.sub("ai", "9", txt, 1)
print(x)
The r9n in Spain

Match

위에서 serach()함수가 Match Object를 반환했다. Match Object는 검색 및 결과에 대한 정보를 포함하는 객체. 일치하는 값이 없으면 None이 반환됨.

.span()는 일치항목의 시작 위치와 끝 위치가 들어 있는 튜플을 반환.
.string는 함수에 전달된 문자열을 반환.
.group()는 일치하는 문자열의 부분을 반환.

# span()
import re

txt = "The rain in Spain"
x = re.search(r"ai", txt)
print(x.span())
(5, 7)
# string
import re

#The string property returns the search string:

txt = "The rain in Spain"
x = re.search(r"ai", txt)
print(x.string)
The rain in Spain
# group()
import re

txt = "The rain in Spain"
x = re.search(r"ai", txt)
print(x.group())
ai

Metacharacters

메타 문자란 원래 그 문자가 가진 의미가 아니라 특별한 의미로 사용되는 문자.
. ^ $ * + ? \ | ( ) { } [ ] 이 있음.

CharacterDescriptionExample
[] (Character Set)지정된 문자 집합중 하나와 일치"[a-m]"
\ (Backslash)특수 시퀀스를 나타내며 특수 문자를 이스케이프할 때 사용"\d"
. (Dot)개행 문자를 제외한 모든 단일 문자와 일치"he..o"
^ (Caret)문자열의 시작과 일치"^hello"
$ (Dollar)문자열의 끝과 일치"planet$"
* (Asterisk)앞의 문자가 0회 이상 반복"he.*o"
+ (Plus)앞의 문자가 1회 이상 반복"he.+o"
? (Question Mark)앞의 문자가 0회 또는 1회 반복"he.?o"
{} (Braces)앞의 문자가 지정된 횟수만큼 정확히 반복"he.{2}o"
| (Pipe)둘 중 하나. 또는."falls\|stays"
() (Group)패턴내에서 하위 패턴을 그룹화"(text)"

. (Dot)

import re

pattern = r"a..d"                    # a로 시작하고 d로 끝나며 사이에 2개의 문자가 들어가는 경우 
text = "abcd aabb ab d aabcddabcd"

matches = re.findall(pattern, text)
print(matches)
['abcd', 'ab d', 'abcd', 'abcd']     # 공백도 문자열. a로 시작하고 d로 끝나는 4개의 문자 찾기.

^ (Caret)

문자열이 시작과 일치하는지.

import re

pattern = r"^The"
text = "The cat sat on the mat."

match = re.search(pattern, text)
print(match)
<re.Match object; span=(0, 3), match='The'>

$ (Dollar)

문자열이 끝과 일치하는지.

import re

pattern = r"mat\.$"
text = "The cat sat on the mat."

match = re.search(pattern, text)
print(match)
<re.Match object; span=(19, 23), match='mat.'>

* (Asterisk)

앞의 문자가 0번이상 반복되는지.

import re

pattern = r"bo*"                    # b로 시작하고 *앞의 o가 0회이상 반복되는 패턴 찾기.
text = "A ghost booooed at me."

matches = re.findall(pattern, text)
print(matches)
['boooo']
import re

pattern = r"o*"                    # 0회 이상이기 때문에 o가 없어도됨. o가 연속해서 나타나는 부분 및 없는 부분 모두 찾음.
text = "A ghost booooed at me."

matches = re.findall(pattern, text)
print(matches)
['', '', '', '', 'o', '', '', '', '', 'oooo', '', '', '', '', '', '', '', '', '', '']

+ (Plus)

앞의 문자가 1회이상 반복되는 패턴찾기.

import re

pattern = r"bo+"
text = "A ghost booooed at me."

matches = re.findall(pattern, text)
print(matches)
['boooo']
import re

pattern = r"o+"
text = "A ghost booooed at me."

matches = re.findall(pattern, text)
print(matches) 
['o', 'oooo']

? (Question Mark)

import re

pattern = r"bo?"
text = "A ghost booooed at me."

matches = re.findall(pattern, text)
print(matches) 
['bo']
import re

pattern = r"o?"
text = "A ghost booooed at me."            # o를 'oooo'로 인식하지않고 o,o,o,o로 인식

matches = re.findall(pattern, text)
print(matches) 
['', '', '', '', 'o', '', '', '', '', 'o', 'o', 'o', 'o', '', '', '', '', '', '', '', '', '', '']

{} (Braces)

앞의 문자가 지정된 횟수만큼 반복되는 패턴.

import re

pattern = r"a{3}"
text = "aaaaa aa a aaaa aaa a"

matches = re.findall(pattern, text)
print(matches)
['aaa', 'aaa', 'aaa']
# 궁금해서 해봄
import re

pattern = r"a{0}"
text = "aa"

matches = re.findall(pattern, text)
print(matches)
['', '', '']                       # 왜일까?

| (Pipe)

import re

pattern = r"cat|dog"
text = "I have a cat and a hamster."

matches = re.findall(pattern, text)
print(matches)
['cat']

() (Group)

패턴 내에서 하위 패턴을 그룹화함.

import re

pattern = r"(oo)+"
text = "A ghost booooed at me."      

matches = re.findall(pattern, text)
print(matches) 
['oo']                   # 왜 ['oooo']가 아니고 ['oo']일까? 위에서 "o+"할때는 ['oooo']였는데...

Character Set

집합은 다음과 같은 특별한 의미를 가진 한 쌍의 대괄호 [] 안에 있는 문자 집합.

SetDescription
[arn]지정된 문자 중 하나(a, r, n)가 있는 경우 일치
[a-n]a부터 n까지의 소문자 중 하나에 일치
[^arn]a, r, n을 제외한 모든 문자에 일치
[0123]지정된 숫자 중 하나(0, 1, 2, 3)가 있는 경우 일치
[0-9]0부터 9까지의 숫자 중 하나에 일치
[0-5][0-9]00부터 59까지의 두 자리 숫자에 일치
[a-zA-Z]a부터 z까지, 그리고 A부터 Z까지의 대소문자에 일치
[+]문자열에서 + 문자에 일치 (세트 내에서 +, *, ., |, (), $, {}는 특별한 의미를 가지지 않음)

\ Sequences

\기호 뒤에 문자는 특별한 의미를 가지고 있다.

CharacterDescriptionExample
\A문자열의 시작 부분에 지정된 문자들이 있는 경우 일치"\\AThe"
\b (Word Boundary)지정된 문자들이 단어의 시작이나 끝에 있을 때 일치 (문자열 앞의 'r'은 문자열이 'raw string'으로 처리되는 것을 확실하게 함)r"\\bain", r"ain\\b"
\B지정된 문자들이 존재하지만 단어의 시작이나 끝이 아닐 때 일치 (문자열 앞의 'r'은 문자열이 'raw string'으로 처리되는 것을 확실하게 함)r"\\Bain", r"ain\\B"
\d (Digit)문자열이 숫자(0-9)를 포함하는 경우 일치"\\d"
\D문자열이 숫자를 포함하지 않는 경우 일치"\\D"
\s (Space)문자열이 공백 문자를 포함하는 경우 일치"\\s"
\S문자열이 공백 문자를 포함하지 않는 경우 일치"\\S"
\w (Word Character)문자열이 단어 문자(a부터 Z, 0-9까지의 숫자, 밑줄(_) 문자)를 포함하는 경우 일치"\\w"
\W문자열이 단어 문자를 포함하지 않는 경우 일치"\\W"
\Z지정된 문자들이 문자열의 끝에 있는 경우 일치"Spain\\Z"
import re

pattern = r"\d+"          # 숫자가 1회이상 반복
text = "There are 15 apples and 10 oranges."

matches = re.findall(pattern, text)
print(matches)
['15', '10']
import re

pattern = r"\d"          # 숫자찾기
text = "There are 15 apples and 10 oranges."

matches = re.findall(pattern, text)
print(matches)
['1', '5', '1', '0']
import re

pattern = r"\D+"         # 숫자가 아닌 문자가 1회이상 반복
text = "There are 15 apples and 10 oranges."

matches = re.findall(pattern, text)
print(matches) 
['There are ', ' apples and ', ' oranges.']

자주 쓰이는 예제

HTML 태그 찾기 및 제거

import re

html = "<html><head><title>Title</title></head><body>Body content</body></html>"

# HTML 태그 찾기
tags = re.findall(r"<[^>]+>", html)
print(tags)
['<html>', '<head>', '<title>', '</title>', '</head>', '<body>', '</body>', '</html>']

패턴 : < [^>]+ >
< : 태그 시작
[^>]+ : >가 아닌 모든 문자가 1회이상 반복
> : 태그 종료

비밀번호 강도 검사

import re

password = "Password123"

# 최소 8자, 숫자와 대소문자 포함
pattern = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$"

패턴 : ^ (?=.*[a-z]) (?=.*[A-Z]) (?=.*\d) .{8,} $
^ : 문자열이 시작. [^abc]와 다름.
() : 그룹지정
? : 전방탐색.
= : 전방탐색을 사용하는데 특정 패턴이 있는지 확인하기위해.
. : 임의의 문자열.
* : 바로앞의 패턴이 0회이상 반복
[a-z] : 소문자 알파벳
[A-Z] : 대문자 알파벳
\d : 숫자.
. : 임의의 문자.
{8,} : 바로 앞의 패턴이 8회이상 반복. {8}: 8회, {8,10}: 8회이상 10회이하
$ : 문자열의 끝.

이메일 주소 추출

import re

text = "Contact us at support@example.com or sales@example.com."
emails = re.findall(r"[\w\.-]+@[\w\.-]+\.\w+", text)

print(emails)
['support@example.com', 'sales@example.com']

패턴 : [\w\.-]+ @ [\w\.-]+ \. \w+
[] : 대괄호는 문자 클래스를 나타냄.
\w : word character. 문자열이 단어 문자(a부터 Z, 0-9까지의 숫자, 밑줄(_) 문자)를 포함.
.- : 점(.), 하이픈(-) 그대로 해석.
+ : 앞에 있는 패턴(\w.-)이 1회이상 반복됨.
@ : 이메일 주소에서 로컬파트와 도메인파트를 구분.
. : 점(.). 도메인파트와 최상위 도메인을 구분하는 점.

로그 파일에서 특정 패턴 찾기

import re

log = "2024-04-01 12:00:00, ERROR, The application encountered an unexpected error"

# 날짜와 시간 패턴 찾기
pattern = r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}"
date_time = re.search(pattern, log)

if date_time:
    print("날짜 및 시간:", date_time.group())
날짜 및 시간: 2024-04-01 12:00:00

패턴: \d{4} - \d{2} - \d{2} \d{2} : \d{2} : \d{2}
\d : 숫자.
{4} : 4회 반복.

추가로 공부할것
Flags
re.IGNORECASE, re.MULTILINE, re.DOTALL, re.ASCII, re.DEBUG

0개의 댓글