정규표현식
1. 개요
- 정규표현식(regular expression)은 컴퓨터 과학에서 특정한 문자열 규칙을 갖는 문자열 집합을 표현하는 데 흔히 쓰이는 일종의 문자열 표현 방식으로, 10여개의 메타문자를 사용한 간결한 문법으로 굉장히 다양하고 크기가 큰 문자열 집합을 표현할 수 있다.
- 많은 프로그래밍 언어에서 정규표현식을 사용할 수 있으며, 프로그래밍 언어에서 문자열 검색, 문자열 치환 등을 수행할 때 정규표현식을 사용하면 아무리 길고 복잡한 문자열이 입력으로 들어오더라도 이를 직접 구현하는 것보다 더 간결하게 구현할 수 있는 경우가 많다.
2. 메타문자들
그냥 알파벳이나 숫자로만 이루어진 평범한 문자열을 쓰면 그냥 그 문자열을 표현하는 것에 불과하며 그 이상 다른 문자열이 집합에 포함되지는 않는다. 그러나 메타문자가 포함된 문자열을 쓰면 이로써 그 표현식이 다양한 문자열을 포함하는 표현식이 된다.
1) -
- 하이픈은 양 옆의 문자들과 함께 쓰며 ‘그 양 옆 사이에 있는 모든 문자들의 나열’을 뜻한다. 예를 들어, /a-d/는 문자열 abcd를 뜻한다.
2) .
- 마침표는 개행문자(\n)를 제외한 모든 문자를 뜻하며, 자리 하나를 차지한다. 예를 들면, /d.t/는 dat, dbt, …, dzt뿐 아니라 d0t, d1t, …, d9t 등 모든 문자를 모두 포함하는 정규표현식이 된다.
3) {n, m}, *, +, ?
- 앞에 어떤 문자가 있고 그 뒤에 바로 중괄호쌍이 붙어있어 그 중괄호쌍 안에 숫자를 쓰면 중괄호쌍 앞의 문자가 그 숫자만큼 반복된 문자열로 취급된다. 예를 들어, /ba{5}/은 문자열 baaaaa와 같다.
- 중괄호쌍 안의 숫자는 하나 또는 두 개를 쓸 수 있으며, 쉼표는 생략할 수도 생략하지 않을 수도 있다. 또, 어떤 숫자의 경우에는 이를 축약한 메타문자가 존재한다.
| 의미 | |
|---|---|
{n} |
n회 반복 |
{n, m} |
n회 이상 m회 이하 반복 |
{n,} |
n회 이상 반복 |
{0, } |
0회 이상 반복(=그 문자를 사용하지 않은 경우를 포함). *로 대체할 수 있다. |
{1, } |
1회 이상 반복. +로 대체할 수 있다. |
{0, 1} |
그 문자를 사용했거나 사용하지 않은 경우를 모두 포함. ?로 대체할 수 있다. |
- 반복문자의 경우 그 정규표현식이 허용하는 가장 긴 문자열을 모두 다 포함하게 하기 때문에, 예를 들어 정규식 /\<.\*>/는 <ul><li></li></ul> 문자열 전체를 포함시킨다. 만약 반복문자의 반복 횟수를 반복문자 바로 뒤에 나오는 문자열과 일치하는 범위 내 최소로 제한하고 싶다면, 반복문자 뒤에 ?를 붙여 해결할 수 있다. 예를 들어 위 정규식을 /\<.\*?>/로 고친다면 위 문자열 범위에서 <ul>, <li>, </li>, </ul>만을 포함하게 된다.
4) [], [^]
- 대괄호쌍은 character class라고도 하며 ‘그 안에 들어있는 문자 중 어느 하나’를 뜻한다. 예를 들어, [a-z]는 ‘a, b, …, z 중 어느 한 문자’를 뜻한다.
- 대괄호쌍 안에 문자열의 맨 앞에 ^가 오면 ‘그 뒤에 등장하는 모든 문자를 제외한 문자’가 된다. 예를 들어, [^0-9]는 ‘숫자를 제외한 모든 문자 중 어느 하나’를 뜻한다.
- []를 쓰지 않더라도 그 자체가 어떤 집합을 뜻하는 \를 붙여 쓴 문자들이 있다.
| 의미 | |
|---|---|
\d |
숫자. [0-9] |
\D |
숫자가 아닌 모든 문자. [^0-9] |
\s |
모든 whitespace 문자 |
\S |
whitespace가 아닌 모든 문자 |
\w |
알파벳, 숫자, 언더바처럼 단어 하나를 구성하는 문자. [a-zA-Z0-9_] |
\W |
[^a-zA-Z0-9_] |
- [] 안의 메타문자들은 ^와 -를 제외하면 모두 원래 지닌 뜻이 아니라 그냥 문자 ., {, }, *, +, …로 취급된다. ^와 -의 경우에도, ^의 경우 맨앞이 아니라면, -의 경우 양 옆에 모두 문자가 있는 경우가 아니라면(=대괄호쌍 안에서 맨앞이나 맨뒤에 있다면) 원래 지닌 뜻이 아니라 그냥 문자 ^, -로 취급된다.
5) ^, $
- ^를 문자열의 맨 앞에 쓰거나 $를 문자열의 맨 뒤에 쓰는 경우는, 그 문자열이 전체 단락의 맨앞 또는 맨뒤에 오는 경우만을 포함한다. 예를 들어 /^P/, /[.]$/는 각각 단락 맨 앞에 P가 있거나 단락 맨 뒤에 .이 있는 경우에만 그 표현식이 그 단락의 그 부분에 매치된다.
- 그 정규표현식에 ‘멀티라인 플래그’를 설정하면, 전체 단락이 아니라 각 줄을 기준으로 맨앞 또는 맨뒤의 문자열 매칭 여부를 검사한다.
6) \b, \B
- 문자열의 앞 또는 뒤에 \b를 쓰는 경우, 그 문자열이 boundary 문자(=whitespace 문자) 바로 다음에 시작하는 경우거나 끝나고 바로 뒤에 boudary 문자가 오는 경우만을 포함한다. 예를 들어 /\bin/로 쓰면 단어 시작이 in으로 시작하는 경우에만 그 표현식이 그 단어의 그 부분에 매치된다.
- 문자열의 앞 또는 뒤에 \B를 쓰는 경우, 반대로 그 문자열의 앞 또는 뒤에 boundary 문자가 없는 경우만을 포함한다. 예를 들어 /\Bin/로 쓰면 단어 중간에 in이 있는 경우에만 그 표현식이 그 단어의 그 부분에 매치된다.
7) (), P<>, (?P=)
- 정규표현식 안의 문자열 일부를 괄호쌍으로 감싸면, 괄호쌍으로 감싼 부분을 마치 하나의 문자처럼 다룰 수 있다. (예를 들어, /(abc){3}/은 문자열 abcabcabc를 뜻한다.) 이처럼 문자열을 괄호로 감싸는 것을 grouping이라 한다.
- 그룹이 지정된 문자열이 여럿일 경우 각각은 왼쪽부터 숫자 1부터 인덱스가 붙으며, \+숫자를 써서 인덱스가 그 숫자와 일치하는 그룹의 문자열을 재참조할 수 있다. (예를 들어, /(ab){2}\1/은 문자열 ababab를 뜻한다.)
- 그룹 안에 그룹을 지정할 수도 있다. 이 경우 바깥쪽 그룹이 그보다 안쪽 그룹보다 인덱스가 더 앞 번호가 매겨진다.
- 그룹에 이름을 지정할 수도 있다. 괄호쌍 앞에 P<>라고 쓰고 꺽쇠 안에 이름을 쓰면 그 이름이 그 그룹의 이름으로 지정된다. 이 경우, 만약 문자열 내에서 (?P=)의 등호 뒤에 그룹 이름을 쓴 표현을 쓰면 그 위치에서 그 그룹을 재참조할 수 있다.
8) |
- 두 문자열 사이에 |를 쓰면, ‘두 문자열 중 어느 하나’라는 뜻이 된다. 예를 들어, /apple|pear/는 문자열 apple과 pear를 모두 포함한다.
9) \ (이스케이프)
3. 전방/후방탐색
1) 전방/후방탐색 ((?=), (?<=))
- 예를 들어 >를 그 뒤에 반드시 포함하는 문자열과 매치되는 정규표현식은 /.*>/이다. 그런데 ‘그 뒤에 >가 반드시 있는 문자열이되 그것이 >를 포함하지는 않는 문자열’을 매치해야 하는 경우가 있을 수 있다.
-
예를 들어
abcde>라는 문자열에서abcde만을 매치하고 싶은 경우가 있을 수 있다. -
이 경우를 ‘전방탐색(lookahead)’라 하며,
(?=)라는 표현의 등호 뒤에 그 문자를 적음으로써 이를 정규표현식으로 표현할 수 있다.- 이 경우에는
/.*(?=>)/로 표현하면 된다.
- 이 경우에는
- 반대로, 예를 들어 그 앞에 <가 반드시 있는 문자열이되 그것이 <를 포함하지는 않는 문자열을 정규표현식으로 표현하고자 할 때(이를 후방탐색 또는 lookbehind라 한다)는 (?<=)라는 표현을 사용하면 된다.
- 예를 들어 꺽쇠로 둘러싸인 내부 문자들을 표현하는 정규표현식은
/(?<=<).*?(?=>)/이다.
- 다음은 주어진 문자열에서 <>로 둘러싸인 태그의 이름들을 리스트형 객체에 담는 파이썬 코드다.
1
2
p = re.compile('(?<=<).*?(?=>)')
list1 = p.findall("<ul><li></li></ul>")
2) 부정 전방/후방탐색 ((?!), (?<!))
- (?=), (?<=)가 ‘어떤 문자/기호가 문자열의 앞/뒤에 있는 문자열이되 그것이 그 문자/기호를 포함하지는 않는 문자열’을 표현하는 기호라면, (?!), (?<!)는 반대로 ‘어떤 문자/기호가 문자열의 앞/뒤에 오지 않는 문자열’을 표현하는 문자열이다.
- 예를 들어, 다음은 주어진 문자열에서 <>로 둘러싸인 태그가 아닌 문자열들을 리스트형 객체에 담는 파이썬 코드다.
1
2
p = re.compile('(?<=>)(?!<).*?(?=<)')
list1 = p.findall("<ul><li>i love you</li></ul>")
* 부정 전방탐색 관련 정규식 사례
1
/^((?!abc).)*$/
-
((?!abc).)- ‘abc가 앞에 오지 않는 어떤 문자 하나’ 를 뜻한다.
-
/^((?!abc).)*$/- ‘abc를 포함하지 않는 문자열’ 을 뜻한다.