정규표현식은 줄여서 정규식(영어로 Regular Expression이며, 줄여서 Regex
, Regexp
등으로 불린다.)이라고 한다.
컴퓨터 과학의 정규언어로부터 유래된 것으로 특정한 규칙을 가진 문자열의 집합을 표현하기 위해 쓰이는 형식언어이다.
참고 정규언어 - wiki
이 말을 풀어보자면 텍스트 내에서 특정한 형태나 규칙을 가진 문자열을 찾기 위해 그 형태나 규칙을 나타내는 패턴을 정의하는 것
을 정규 표현식이라고 한다.
정규 표현식은 검색 엔진, 워드 프로세서와 문서 편집기의 찾아 바꾸기 대화상자, 또 sed, AWK와 같은 문자 처리 유틸리티, 어휘분석 등 여러 분야에서 쓰였다. 각 분야의 정규식은 서로 영향을 주고 받으면서 발전해서 지금에 이르렀다. 정규식이 쓰이는 곳이 다양했지만 아름다운 역사 때문에 정규식에는 하나의 통일된 표준이 없다는 문제가 있다.
처음 유닉스 명령줄 도구들에서 사용하던 정규 표현식은 후에 POSIX
표준에 편입되었다. 이 시기의 표준으로 받아들여진 형식을 POSIX 정규식이라고 부른다. 이후 다시 POSIX 정규식은 POSIX BRE(POSIX 기본 정규식)와 POSIX ERE(POSIX 확장 정규식)으로 버전이 나뉘게 된다.
그 외에 BRE를 기본 골격으로 한 vim 정규식이 있다. 이 vim 정규식은 vim 편집기 내에서 찾기/바꾸기 등의 동작에서 범위를 지정하는데 사용된다. 하지만 시간이 흐르면서 개선과 확장을 거듭하면서 이 vim 정규식 역시 POSIX 표준과는 좀 다른 규격으로 취급이 되었다.
vim 내에서도 magic 모드, very magic 모드라는 것이 있는데, 이후의 확장 정규식의 일부 기능을 사용하거나, 일부 punctuation 문자를 매칭하는 방법이 다시 갈리게 되었다.
punctuation 문자 : one of ( ) [ ] { } * , : = ; ... #
문자열을 다루는데 특화된 스크립트 언어인 펄(perl)의 정규식 체계의 기본은 POSIX와 비슷한 골격에서 디자인이 되었다. 그런데 엄청나게 많은 확장이 들어가게 되면서, 펄의 정규식은 PCRE라는 규격으로 정리되었으며, 이후 많은 프로그래밍 언어들이 이 규격을 차용하거나 계승했다. 여기서 중요한 것은 일부 차용이라는 것이다. 이 규격은 방대해서 PCRE를 그대로 가져다 쓰지 않는 이상, 구현할게 너무 많기 때문이다.
정규식 기분 문법은 크게 세 가지로 나눌 수 있다.
- 패턴 그대로를 매칭하는 경우 : 편집기에서 '찾기' 기능을 통해서 특정 단어를 찾는 것과 동일하게, 단어 그대로를 패턴으로 사용하여 매치
ex)textbox, regExp
- 메타문자 및 수량 한정자를 적용하는 경우 : 정규식 패턴에 쓰이는 문자 중에는 특별한 의미를 가지는 메타 문자를 사용하여 보다 폭넓은 패턴에 매치
- Group 및 look around 기능을 사용하는 경우 : 고급 정규식이라 할 수 있는 부분으로, 패턴의 일부를 Group으로 묶거나, 특정 패턴의 앞 뒤로 다른 패턴이 오는 조건을 더하는 경우
메타 문자는 특정한 문자 혹은 문자 계열을 대신하여 표시하는 문자이다. 메타문자를 이용하면 특정한 규칙을 가진 여러 단어를 하나의 패턴으로 함축할 수 있다.
메타 문자 | 의미 | 예 |
---|---|---|
^ | 문자열의 시작을 의미. [...] 내에서 쓰이면 '일치하지 않는'이라는 의미 |
^http 는 문자열의 맨 처음에 http가 온 경우에 매치 |
$ | 문자열의 끝을 의미 | them$ 은 문자열이 them으로 끝난 경우에 them에 매치 |
\b | 단어의 경계. 공백(space), 탭(tab), 콤마(,), 대시(-) 등이 가능 | \bplay\b 는 play의 양 끝에 단어 경계가 오는 경우에만 play에 매치. 'playground'의 play에는 매치하지 않음 |
\B | \b 의 반대. |
\bplay\B 는 play뒤에 단어 경계가 아닌 것이 왔을 때 play에 매치. play에는 매치하지 않지만 playground, playball의 play에는 매치 |
\s | 공백문자 | 공백, 탭에 매치한다. |
\S | 공백문자가 아닌 것 | |
\d | 숫자. [0-9] 와 동일 |
|
\D | 숫자가 아닌 것. [^0-9] 와 동일 |
|
\w | 단어를 만들 수 있는 글자. 알파벳 대소문자, 숫자, 언더스코어가 포함 | |
\W | not \w 위의 것이 아닌 글자들이 해당 |
|
\n | 개행문자. 캐리지리턴(CR)은 \r 에 매치 |
그 외에 탭 문자는 \t 에 매치 |
\ | escape용 문자. 정규식 상의 특별한 의미가 있는 기호들을 문자 그대로 사용할 때 사용 | \. , \* , \$ , \( 등과 같이 메타 문자를 리터럴하게 매치할 때 사용. |
. | 임의의 문자 1개에 대응한다. |
라인피드(LF : Line Feed) => 현재 위치에서 바로 아래로 이동
캐리지리턴(CR: Carriage return) => 커서의 위치를 앞으로 이동
메타 문자의 패턴을 유심히 살펴보면, 소문자를 쓴 것과 대문자를 쓴 것이 서로 반대의 의미를 지닌다.
|
문자를 이용하면 A | B
의 패턴으로 A 혹은 B에 매칭할 수 있다. 예를 들어 tomato와 potato에 모두 매칭하고 싶다면 tomato|potato
라고 쓸 수 있다. 선택 패턴은 이후에 등장하는 그룹 패턴과 같이 사용하여 보다 강력하게 쓰일 수 있다.
그 외의 선택 패턴으로는 [ ... ]
이 있다. 대괄호속에 넣은 문자 중에서 하나에 매칭하는 것이다. [cfh]all
이라는 패턴은 call
, fall
, hall
에 모두 매치될 수 있다.
선택 패턴은 대시(A-B
)를 통해서 특정 범위를 표현할 수도 있는데, 숫자의 경우 [0-9]
, 알파벳 소문자의 경우 [a-z]
, 알파벳대문자의 경우 [A-Z]
와 같은 식으로 한 글자에 매칭하는 것이 가능하다. 유니코드를 지원하는 정규식에서는 [ㄱ-힣]
을 이용해서 한글 한 글자에 매칭하는 것도 가능하다.
위에서 언급 되었던 것처럼, 선택 패턴 내에서 ^
이 쓰이면 not
의 의미가 되며, 문자 뒤에 오는 문자들은 제외하게 된다. [^cfh]all
은 앞서 나온 call
, fall
, hall
에는 매치하지 않으며 mall
에는 매치하게 된다.
괄호로 둘러싼 단위는 Group을 나타낸다. Group은 전체 패턴 내에서 다시 하나로 묶여지는 패턴 조각을 나타낸다. 특히 |
나 뒤에 나오는 수량 한정자를 Group에 붙이는 형태로 많이 사용되며, 한 번 매치한 Group을 다시 반복하여 사용할 수 있다.
(tom|pot)ato
: tomato, potato에 모두 매치되는 패턴을 Group을 써서 좀 더 줄였다.(a|i){3}bc
: a 혹은 i가 3개 온 후에 bc가 오는 패턴.aaabc, iiibc, aiabc, aaibc, iiabc
등에 매치된다.
괄호를 써서 묶은 부분은 1번부터 시작하는 Group으로 참조할 수 있다. 앞서 매치한 Group을 패턴 내에서 재사용하려면 \1
과 같이 Group번호를 역슬래시로 escape하여 표현한다. tomato에서 to가 두 번 반복되는데 이는 다음과 같이 표현할 수 있다.
(to)ma\1 # 이 패턴을 각 절로 나누어서 살펴보면,
----
(to) # to 에 매치하는 첫번째 Group을 캡쳐한다.
ma # ma에 매치
\1 # 1번 Group인 to가 다시 나온다.
좀 더 응용하면 아래와 같은 패턴도 만들 수 있다.
(a|b|c){2}ma\1
이 패턴은 a 혹은 b 혹은 c 중에서 매치되는 두 글자를 Group으로 캡쳐하고 ma 뒤에 동일한 글자가 반복되는 패턴이다. 따라서 aamaaa, bcmabc, abmaab 등에 매치된다. 캡쳐된 Group을 재사용하는 패턴은 Group의 패턴이 아닌 캡쳐된 내용에 매치하므로 aamabb에는 매치되지 않는다.
(?: )
을 사용하면 Group으로 묶어는 주지만 캡쳐는 하지 않는 비캡쳐링 Group이 된다. 특정한 수량 한정자 등을 적용은 하려 하지만 최종 결과에서 따로 구분하여 사용할 필요가 없는 경우에 적용한다. (사실 캡쳐만 해놓고 사용하지 않아도 된다.)
동일한 글자 혹은 동일한 가족(family)이 n개 만큼 나오는 경우에 수량한정자를 뒤에 붙일 수 있다.
메타 문자 | 의미 | 예 |
---|---|---|
? | 앞의 표현식이 없을 수 있다. | apples? 에서 s? 는 있을 수도 없을 수도 있다는 의미이며, 이 패턴은 apple, apples 모두에 매치 |
* | 0개 이상 | n\d* 는 n 뒤에 숫자가 0개 이상이라는 의미. n, n1, n12 등이 모두 매치 |
+ | 1개 이상 | * 와 달리 적어도 1개는 있어야 매치 |
{n} | n 개 있다. | n\d{3} 은 n 뒤에 숫자가 3개 온다는 의미. n1, n23, n3464 등은 매치되지 않음 |
{n, m} | n개 이상, m 개 이하의 범위 | 숫자 두 개를 사용해서 범위를 지정 |
{n,} | n개 이상 | 우측 경계가 없는 범위로, 최소값 이상을 의미한다. |
수량 한정자와 관련하여 *
, +
는 기본적으로 greedy 하게 동작한다. 즉 가능한 많은 글자를 먹고 다음 패턴을 찾는다는 것이다. 예를 들어
i like apples and bananas
라는 문장에 대해서 ^.*s
를 매치하면 .
문자(아무 글자)는 욕심을 부려서 다 먹어치우기 때문에 bananas의 s까지, 전체 문장이 다 매치된다.
이 때, *
와 ?
를 조합하는 경우에는 반대로 동작하면서 가장 처음에 나오는 패턴까지 매치한다. 즉 ^.*?s
로 패턴을 주면 i like apples
까지만 매치한다.(lazy하게 동작한다고 한다.)
메타 문자 | 의미 |
---|---|
(?=) | 긍정형 전방탐색 |
(?!) | 부정형 전방탐색 |
(?<=) | 긍정형 후방탐색 |
(?<!) | 부정형 후방탐색 |
일부 정규 표현식 문서에 일치하는 영역을 반환하는 동작을 표현할 때 소비한다(consume)라는 용어를 사용하는데, 전방탐색은 소비하지 않는다(not consume)라고 한다.
http://www.forta.com
https://mail.forta.com
ftp://ftp.forta.com
.+(?=:)
와 같은 전방탐색을 하게 되면 아래와 같은 결과값이 나온다.
http
https
ftp
위에서 언급한 내용과 동일하게 전방탐색은 소비하지 않는다. 일반적인 일치하는 영역을 반환하도록 하는 소비하게 하는 패턴을 작성하게 되면 .+:
와 같이 작성하면 된다.
ABC01: $23.45
HGG42: $5.31
CFMX1: $899.00
XTC99: $69.96
Total items found: 4
(?<=\$)[0-9.]+
와 같은 후방탐색을 하게 되면 아래와 같은 결과값이 나온다.
23.45
5.31
899.00
69.96
$
기호와 일치하지만 소비하지 않고 뒤에 숫자만 반환한다.
<HEAD>
<Title>Ben Forta`s Homepage</Title>
</HEAD>
(?<=\<[tT][iI][tT][lL][eE]\>).*(?=\<\/[tT][iI][tT][lL][eE]\>)
와 같은 패턴을 적용하게 되면 아래와 같은 결과값이 나온다.
Ben Forta`s Homepage
(?<=\<[tT][iI][tT][lL][eE]\>)
는 후방탐색 작업으로 <TITLE>
과 일치하며 소비하지 않으며,
(?=\<\/[tT][iI][tT][lL][eE]\>)
도 같은 방식으로 </Title>
과 일치하며, 소비하지 않는다.
따라서 제목 텍스트만 반환하는 형태가 나오게 된다.
부정형 전방탐색은 앞쪽에서 지정한 패턴과 일치하지 않는 텍스트를 찾고 부정형 후방탐색도 마찬가지로 뒤쪽에서 지정한 패턴과 일치하지 않는 텍스트를 찾는다.
I paid $30 for 100 apples,
50 oranges, and 60 pears.
I saved $5 on this order.
긍정형 패턴인 (?<=\$)\d+
으로 테스트를 하게 되면 30 5
이 나오게 되며, 부정형 패턴인 \b(?<!\$)\d+\b
으로 테스트를 하게 되면, 100 60
이 나오게 된다.
Flag | Description |
---|---|
g | 전역 검색 |
i | 대소문자 구분 없는 검색 |
m | 다중행(multi-line) 검색 |
u | 유니코드; 패턴을 유니코드 코드 포인트의 나열로 취급한다. |