8 min read

정규표현식

오늘은 정규표현식에 대해 알아보고자 합니다. 정규표현식 (Regular Expression ; regex) 은 패턴(규칙)을 갖는 문자열의 집합을 표현하는 데에 사용합니다.

많은 프로그래밍 언어가 정규표현식 기능을 제공하고 있으나, 문법에 있어서는 프로그래밍 언어 간 차이가 있습니다. R에서 사용되는 정규표현식의 특징은 이스케이프 문자가 두번 (\\) 사용된다는 점입니다.

먼저 필요한 패키지들을 라이브러리 합니다.

library(dplyr)
library(tidyverse)
library(stringr)

다음으로 문자열을 생성하고 문자열의 글자 수를 확인해봅시다.

string <- "abCD가나123 \r\n\t-_,./?\\"
nchar(string)
## [1] 20

출력 결과를 보면 문자열의 글자 수를 20이라고 한 것을 볼 수 있는데, 이는 \\를 한글자로 인식하기 때문입니다.

1. 패턴

다음은 정규표현식의 기호와 의미를 나타낸 표입니다.

정규표현식 의미
. 개행을 제외한 모든 문자 한글자
\\w 영어 대문자, 영어 소문자, 숫자, _ 한글자
\\W 영어 대문자, 영어 소문자, 숫자, _ 제외하고 한글자 (\\w의 반대)
\\d 숫자 0-9 한 글자
\\D 숫자 0-9 제외하고 한글자 (\\d의 반대)
\\s 공백( ), 개행(\\n), 탭(\\t) 한글자
\\S 공백( ), 개행(\\n), 탭(\\t) 제외하고 한글자 (\\s의 반대)
\\p{Hangul} 한글
[a-z] 영어 소문자 한글자
[A-Z] 영어 대문자 한글자
[0-9] 숫자 한글자
[ㄱ-ㅎ] 한글 자음 한글자
[ㅏ-ㅣ] 한글 모음 한글자
[ㄱ-ㅣ] 한글 자음 및 모음 한글자
[가-힣] 한글 한글자 (\\p{Hangul}과 동일)
| 여러 문자열을 or 조건으로 지정
[] 여러 글자들을 or 조건으로 지정

이제 정규표현식에 대해 자세하게 살펴보도록 하겠습니다.

. : 개행을 제외한 모든 문자 한글자

.는 개행을 제외한 모든 문자 한글자를 의미합니다.

string %>% 
  str_extract_all(pattern = ".")
## [[1]]
##  [1] "a"  "b"  "C"  "D"  "가" "나" "1"  "2"  "3"  " "  "\t" "-"  "_"  ","  "." 
## [16] "/"  "?"  "\\"

\\w : 영어 대문자, 영어 소문자, 한글, 숫자, _ 한글자

string <- "abCD가나123 \r\n\t-_,./?\\"

string %>% 
  str_extract_all(pattern = "\\w")
## [[1]]
##  [1] "a"  "b"  "C"  "D"  "가" "나" "1"  "2"  "3"  "_"

\\W : 영어 대문자, 영어 소문자, 한글, 숫자, _ 제외한 한글자

문자열에서 필요없는 것들이 무엇이 있는지 보고싶을 때 사용합니다.

string %>% 
  str_extract_all(pattern = "\\W")
## [[1]]
##  [1] " "  "\r" "\n" "\t" "-"  ","  "."  "/"  "?"  "\\"

\\d : 숫자 0-9 한글자

\\d는 숫자 0-9 한글자를 의미합니다.

string %>% 
  str_extract_all(pattern = "\\d")
## [[1]]
## [1] "1" "2" "3"

\\D : 숫자 0-9 제외한 한글자

\\D는 숫자 0-9를 제외한 한글자를 의미합니다.

string %>% 
  str_extract_all(pattern = "\\D")
## [[1]]
##  [1] "a"  "b"  "C"  "D"  "가" "나" " "  "\r" "\n" "\t" "-"  "_"  ","  "."  "/" 
## [16] "?"  "\\"

\\s : 공백( ), 개행(\\n), 탭(\\t) 한글자

\\s는 공백( ), 개행(\\n), 탭(\\t) 한글자를 의미합니다.

string %>% 
  str_extract_all(pattern = "\\s")
## [[1]]
## [1] " "  "\r" "\n" "\t"

\\S : 공백( ), 개행(\\n), 탭(\\t) 제외한 한글자

\\S는 공백( ), 개행(\\n), 탭(\\t)를 제외한 한글자를 의미합니다.

string %>% 
  str_extract_all(pattern = "\\S")
## [[1]]
##  [1] "a"  "b"  "C"  "D"  "가" "나" "1"  "2"  "3"  "-"  "_"  ","  "."  "/"  "?" 
## [16] "\\"

\\p{Hangul} : 한글 한글자

\\p{Hangul}는 한글 한글자를 의미합니다.

string %>% 
  str_extract_all(pattern = "\\p{Hangul}")
## [[1]]
## [1] "가" "나"

[a-z] : 영어 소문자 한글자

[a-z]는 영어 소문자 한글자를 의미합니다.

string <- "abcdEFGH"

string %>% 
  str_extract(pattern = "[a-z]") 
## [1] "a"
string %>% 
  str_extract_all(pattern = "[a-z]")
## [[1]]
## [1] "a" "b" "c" "d"

[A-Z] : 영어 대문자 한글자

[A-Z]는 영어 대문자 한글자를 의미합니다.

string <- "abcdEFGH"

string %>% 
  str_extract(pattern = "[A-Z]")
## [1] "E"
string %>% 
  str_extract_all(pattern = "[A-Z]")
## [[1]]
## [1] "E" "F" "G" "H"

[0-9] : 숫자 한글자

[0-9]는 숫자 한글자를 의미합니다.

string <- "0123abcdEFGH"

string %>% 
  str_extract_all(pattern = "[0-9]")
## [[1]]
## [1] "0" "1" "2" "3"

[ㄱ-ㅎ] : 한글 자음 한글자

[ㄱ-ㅎ]는 한글 자음 한글자를 의미합니다.

string <- "0123abcdEFGHㄱㄴㅐㅑㅋㅋㅋㅎㅎㅎㅠㅠ가나다라"

string %>% 
  str_extract_all(pattern = "[ㄱ-ㅎ]")
## [[1]]
## [1] "ㄱ" "ㄴ" "ㅋ" "ㅋ" "ㅋ" "ㅎ" "ㅎ" "ㅎ"

[ㅏ-ㅣ] : 한글 모음 한글자

[ㅏ-ㅣ]는 한글 모음 한글자를 의미합니다. 텍스트 데이터를 다룰 때 자주 볼 수 있는 ㅠㅠㅠㅠ 와 같은 것들을 제거하는 데에 용이합니다.

string <- "0123abcdEFGHㄱㄴㅐㅑㅋㅋㅋㅎㅎㅎㅠㅠ가나다라"

string %>% 
  str_extract_all(pattern = "[ㅏ-ㅣ]")
## [[1]]
## [1] "ㅐ" "ㅑ" "ㅠ" "ㅠ"

[ㄱ-ㅣ] : 한글 자음 및 모음 한글자

[ㄱ-ㅣ]는 한글 자음 및 모음 한글자를 의미합니다.

string <- "0123abcdEFGHㄱㄴㅐㅑㅋㅋㅋㅎㅎㅎㅠㅠ가나다라"

string %>% 
  str_extract_all(pattern = "[ㄱ-ㅣ]")
## [[1]]
##  [1] "ㄱ" "ㄴ" "ㅐ" "ㅑ" "ㅋ" "ㅋ" "ㅋ" "ㅎ" "ㅎ" "ㅎ" "ㅠ" "ㅠ"

[가-힣] : 한글 한글자

[가-힣]는 한글 한글자를 의미합니다. 이때 한글 한글자라는 것은 한글 완성형 한글자를 의미하는 것으로, \\p{Hangul}과 같은 기능을 합니다.

string <- "0123abcdEFGHㄱㄴㅐㅑㅋㅋㅋㅎㅎㅎㅠㅠ가나다라"

string %>% 
  str_extract_all(pattern = "[가-힣]")
## [[1]]
## [1] "가" "나" "다" "라"
: 여러 문자열을 or 조건으로 지정
string <- c("abc", "bcd", "cde", "def")

string %>% 
  str_extract(pattern = "ab|cd")
## [1] "ab" "cd" "cd" NA

위 결과로부터 알 수 있듯이, ab|cd는 ab 또는 cd 패턴을 의미합니다.

[] : 여러 글자들을 or 조건으로 지정

string <- c("abc", "bcd", "cde", "def")

string %>% 
  str_extract(pattern = "[af]")
## [1] "a" NA  NA  "f"

[af]는 a 또는 f를 의미합니다.

string %>% 
  str_extract(pattern = "[abcd]")
## [1] "a" "b" "c" "d"

[abcd]는 a 또는 b 또는 c 또는 d를 의미합니다.

string %>% 
  str_extract_all(pattern = "[abcd]")
## [[1]]
## [1] "a" "b" "c"
## 
## [[2]]
## [1] "b" "c" "d"
## 
## [[3]]
## [1] "c" "d"
## 
## [[4]]
## [1] "d"

즉 대괄호 안의 글자들을 or 조건을 적용하여 탐색한다는 것입니다.

string <- "0123abcdEFGHㄱㄴㅐㅑㅋㅋㅋㅎㅎㅎㅠㅠ가나다라"

string %>% 
  str_extract_all(pattern = "[^ㄱ-ㅣ가-힣]")
## [[1]]
##  [1] "0" "1" "2" "3" "a" "b" "c" "d" "E" "F" "G" "H"

위와 같이 대괄호 안에 ^ 기호를 넣을 경우 ([^]) 대괄호 안의 패턴을 제외한 한글자를 의미합니다.

2. 탐욕적 수량자

기호 의미
+ 앞의 패턴이 1번~무한대번 일치
* 앞의 패턴이 0번~무한대번 일치
? 앞의 패턴이 0번~1번 일치
{} 앞의 패턴이 n번 일치

+ : 앞의 패턴이 1번 ~ 무한대번 일치

string <- c("12", "345", "가나", "다라마")

string %>% 
  str_extract(pattern = "\\d+")
## [1] "12"  "345" NA    NA

위 코드는 숫자가 1번 ~ 무한대번 일치하는 문자열을 추출함을 의미합니다.

* : 앞의 패턴이 0번 ~ 무한대번 일치

string %>% 
  str_extract(pattern = "\\d*")
## [1] "12"  "345" ""    ""

위 코드는 숫자가 0번 ~ 무한대번 일치하는 문자열을 추출함을 의미합니다. 따라서 \\\\d+의 경우 일치하지 않을 때 NA가 반환된 반면 \\\\d*의 경우 ""가 반환됩니다. 없어도 괜찮기 때문입니다.

? : 앞의 패턴이 0번 ~ 1번 일치

string <- c("12", "345", "가나", "다라마")

string %>% 
  str_detect(pattern = "\\d?")
## [1] TRUE TRUE TRUE TRUE
string %>% 
  str_extract(pattern = "\\d?")
## [1] "1" "3" ""  ""
string %>% 
  str_extract_all(pattern = "\\d?")
## [[1]]
## [1] "1" "2" "" 
## 
## [[2]]
## [1] "3" "4" "5" "" 
## 
## [[3]]
## [1] "" "" ""
## 
## [[4]]
## [1] "" "" "" ""

즉 있어도 괜찮고 없어도 괜찮다는 것입니다.

{n} : 앞의 패턴이 n번 일치

string %>% 
  str_extract(pattern = "\\d{3}")
## [1] NA    "345" NA    NA

{n,} : 앞의 패턴이 n번 이상 일치

string %>% 
  str_extract(pattern = "\\d{2,}") # 숫자 두개 이상
## [1] "12"  "345" NA    NA

위 코드는 숫자가 두개 이상인 문자열을 추출함을 의미합니다. 이때 주의해야 할 점은 \\d{2, }와 같이 콤마 뒤에 공백이 있어서는 안 된다는 것입니다.

{n,m} : 앞의 패턴이 n번 이상 m번 이하 일치

string %>% 
  str_extract(pattern = "\\d{2,3}") # 숫자 두개 이상 세개 이하
## [1] "12"  "345" NA    NA

위 코드는 숫자가 두개 이상 세개 이하인 문자열을 추출함을 의미합니다.

3. 게으른 수량자

게으른 수량자는 탐욕자 수량자 뒤에 ?를 붙인 것입니다. 탐욕적 수량자는 해당되는 패턴을 최대한 크게 선택하는 반면 게으른 수량자는 해당되는 패턴을 최소 단위로 선택합니다.

string <- "<p>이것은<br>HTML<br>입니다<강남역><맛집></p>"

string %>% 
  str_extract(pattern = "<.+>") # 탐욕적 수량자 : 최대한의 크기
## [1] "<p>이것은<br>HTML<br>입니다<강남역><맛집></p>"
string %>% 
  str_extract(pattern = "<.+?>") # 게으른 수량자 : 최소 단위
## [1] "<p>"
string %>% 
  str_extract_all(pattern = "<.+?>") # 게으른 수량자 즉 최소 단위로 모두 추출
## [[1]]
## [1] "<p>"      "<br>"     "<br>"     "<강남역>" "<맛집>"   "</p>"
string %>% 
  str_extract(pattern = "<.+>?")
## [1] "<p>이것은<br>HTML<br>입니다<강남역><맛집></p>"

4. 이스케이프 문자

\\는 메타 문자의 기능을 제거합니다. 이때 메타 문자는 \, [], |, ., {}, () 을 의미합니다.

string <- "우리집 강아지는 (복슬강아지)입니다."

string %>% 
  str_extract(pattern = "(.+)")
## [1] "우리집 강아지는 (복슬강아지)입니다."
string %>% 
  str_extract(pattern = "\\(.+\\)")
## [1] "(복슬강아지)"
string %>% 
  str_extract(pattern = "\\([가-힣]+\\)")
## [1] "(복슬강아지)"

5. 문자열에서의 위치 지정

기호 의미
^ 문자열 맨 앞에 있을 것
$ 문자열 맨 뒤에 있을 것

단 대괄호 안에서의 ^ 즉 [^]는 대괄호 안 패턴 제외를 의미한다는 것입니다.

string <- c("가나다", "나다라", "가다라", "라가나", "다라가")

string %>% 
  str_extract(pattern = "^가") # 문자열 맨 앞에 있는 가 선택
## [1] "가" NA   "가" NA   NA
string %>% 
  str_extract(pattern = "라$")
## [1] NA   "라" "라" NA   NA
money <- c("1000원", "20달러", "300위안", "4엔")

money %>% 
  str_extract(pattern = "[가-힣]+$")
## [1] "원"   "달러" "위안" "엔"
money <- c("1000원", "20달러", "300위안", "4엔 ")

money %>% 
  str_extract(pattern = "[가-힣]+$") # 마지막 원소는 공백으로 끝나기 때문에 NA 반환
## [1] "원"   "달러" "위안" NA

6. 문자열에서의 그룹 지정

기호 의미
() 패턴을 grouping
\숫자 역참조
string <- "매경부동산과 한경부동산보다 KB부동산이 더 자세합니다."

string %>% 
  str_extract_all(pattern = "(부동산)")
## [[1]]
## [1] "부동산" "부동산" "부동산"
string %>% 
  str_extract_all(pattern = "부동산")
## [[1]]
## [1] "부동산" "부동산" "부동산"
string %>% 
  str_extract(pattern = "(부동산).+\\1") 
## [1] "부동산과 한경부동산보다 KB부동산"

위 코드에서 \\1은 grouping 된 것 중 첫번재 그룹을 재사용한다는 것을 의미합니다. 또한 +는 탐욕적 수량자이므로 문자열을 최대 단위로 선택합니다.

string %>% 
  str_extract(pattern = "(부동산).+?\\1")
## [1] "부동산과 한경부동산"

위 코드에서는 탐욕적 수량자인 + 뒤에 ?를 붙여 게으른 수량자로 만들었으므로 문자열을 최소 단위로 선택합니다.

7. 전방탐색 & 후방탐색

전방탐색

전방탐색은 A(?=B)와 같은 형태를 사용하는 것을 말하는데, AB라는 문자열을 감지한 뒤 뒤에 있는 B를 제외하고 앞에 있는 A만을 선택합니다. 전방탐색에서는 B의 글자 수를 정확하게 지정해주어야 합니다.

string <- c("100원", "300원", "450원", "800원")

string %>% 
  str_extract(pattern = "\\d+(?=원)")
## [1] "100" "300" "450" "800"
string <- c("100원", "300원", "450원", "800원", "90엔")

string %>% 
  str_extract(pattern = "\\d+(?=원)")
## [1] "100" "300" "450" "800" NA

후방탐색

후방탐색은 (?<=A)B와 같은 형태를 사용하는 것을 말하는데, AB라는 문자열을 감지한 뒤 앞에 있는 A를 제외하고 뒤에 있는 B만을 선택합니다. 후방탐색에서는 A의 글자 수를 정확하게 지정해주어야 합니다.

string <- c("100원", "300달러", "450엔", "800위안")

string %>% 
  str_extract(pattern = "(?<=\\d)[가-힣]+")
## [1] "원"   "달러" "엔"   "위안"