최근 수정 시각 : 2019-08-10 20:57:01

정규 표현식

정규표현식에서 넘어옴
1. 개요2. 설명3. 문법
3.1. 패턴구분자3.2. 메타 문자3.3. 패턴변경자3.4. 예제3.5. 탐욕적 및 게으른 수량자
4. 공부하는 방법

1. 개요

正規表現式 / Regular Expression

정규식이라고도 부른다. 보통 regex 혹은 regexp 라 많이 쓴다. 때문에 규식이(...)라고 부르는 사람도 몇 존재한다.

2. 설명

프로그래밍에서 사용하는 일종의 형식 언어. 주로 문자열(string) 관련 프로그래밍에 많이 사용된다. 특히 컴파일러파서 부분은 이 정규표현식이 반드시 들어간다. 프로그램을 작성할 때는 특성상 일정한 규칙을 가진 텍스트 문자열을 사용하는 경우가 많은데, 이럴 때 정규 표현식을 사용한다. 유닉스 계열 운영체제에서 CLI 환경을 주로 사용하는 경우 grep, sed, awk 등으로 인해 거의 필수적으로 알게 될 수밖에 없는 언어.(모르고 CLI 환경에서 컴퓨터를 사용할수도 있긴 하지만, 그럴 경우 그냥 GUI 환경을 사용하는 것에 비해 노가다가 현저하게 늘어나고 생산성이 매우 떨어진다.)

잘 알아두면 문자열을 다루는 데 대단히 유용하기 때문에 C를 비롯한 여러 프로그래밍 언어에서 이 정규 표현식을 지원한다. 특히 웹 프로그래밍은 문자열을 다루는 빈도가 특히 높기 때문에 사용하는게 거의 필수적. 예를 들면 위키위키만 해도 DB에 저장된 텍스트에 있는 위키 문법을 엔진에서 해석해서 출력해주는 작업이 필요한데, 위키 문법도 일정한 규칙이 있는 문자열이니만큼 이 작업에서 정규표현식은 반드시 들어간다.

그냥 읽기에는 상당히 난해한데, 문법이 일반적인 프로그래밍 언어의 형식과는 판이하게 다른데다 한 눈에 알아보기 어렵게 되어 있다. 정규표현식 중간에는 강제개행이 허용되지 않기 때문에 한 줄에 주르르륵 쓰게 되는데, 조금만 복잡해지면 각종 특수문자가 마구마구 튀어나오기 때문에 무슨 외계어처럼 느껴진다. 이 때문에 초보 프로그래머는 가급적 이 물건에 손을 안 대려고 하는 경향이 많은데, 정규식을 안 쓸 경우 문자열 처리 코드 자체가 대단히 복잡해진다. 그렇게 복잡하게 문자열을 처리할 바에, 정규식 배우는게 여러모로 낫다. 사실 정규식이 복잡한 것은 확실한 단점이기 때문에 문자열 처리 함수를 다양하게 제공하는 언어도 있지만, 문자열이라는 것이 항상 고정된 형식으로 나오는 것이 아니기 때문에 정규식을 안 쓸래야 안 쓸수가 없다.

정규 표현식은 언어마다 문법이 조금씩 다른데, 쉘에서 주로 정규표현식을 사용하는 사용자 입장에서는 이것이 문제가 되는경우도 종종 있다. 크게 나누면 표준으로 인정된 POSIX의 정규 표현식과 그것에서 문법을 매우 확장한 Perl 방식의 PCRE[1] 이 둘로 나뉘어지며, POSIX 표준의 경우 다시 Basic 과 Extended 로 나뉜다. 이외에도 Emacs 와 Vim 모두 자체적인 정규표현식을 지원한다. 문제는, 이 정규표현식들이 완전히 다르면 모르겠지만, 비슷하면서도 살짝살짝 다르기 때문에 그 차이들을 다 외우고 있을 수도 없는 노릇이며, 정규표현식 자체가 다량의 텍스트를 다루는 명령이니만큼 작은 실수가 커다란 차이를 불러올 수 있다는 점이다. 즉, 셸에서 쓰기 작업에 정규표현식을 동반할 경우 매우 주의를 요할 필요가 있다. 또 다른 문제는, 자신이 뭘 원한다고 해서 그것만 배우고 쓰면 되는 게 아니라 프로그램마다 지원하는 정규표현식이 다르기 때문에 그에 맞춰 배우고 써줘야 한다.[2] PCRE의 경우 이미 정규표현식이라고 부를 수 없을 정도로 기능이 확장되어 있는데[3], 덕분에 이런 이상한 짓도 가능하다. (...)

3. 문법

문자열에서 URL을 찾는 정규표현식의 예제는 다음과 같다.

/(http|https|ftp|telnet|news|mms)://[^\"'\s()]+/i

위 정규식은 아래와 같이 구분이 된다.
/ (http|https|ftp|telnet|news|mms)://[^\"'\s()]+ / i
패턴구분자 시작 찾을 문자열의 패턴 패턴구분자 끝 패턴변경자

3.1. 패턴구분자

정규식의 패턴이 달라질 경우 그것을 구분하는 문자로, 정규식 패턴이 하나만 있을 경우에는 굳이 쓸 필요가 없지만 대부분 붙인다. 난해하게 만드는 주범이다. 왜냐하면, 이 패턴구분자는 특수문자 중 역슬래쉬(\\)를 제외하고 아무거나 쓸 수 있기 때문이다. 심지어 아래 등장하는 의미를 가진 문자(메타 문자라고 부른다)도 패턴구분자로 쓸 수 있다. 보통 슬래쉬(/)를 많이 사용하지만 규칙이 있는 것이 아니라서 정말로 프로그래밍 하는 사람 마음대로다. 이래저래 헷갈리는 주범 중 하나.

3.2. 메타 문자

정규표현식에서 일정한 의미를 가지고 쓰는 특수문자를 메타 문자라고 부른다. 여기에 쓰이는 글자가 포함된 패턴을 넣으려면 해당 글자 앞에 \\를 넣어줘야 한다.
  • ^ : 문자열의 시작
  • $ : 문자열의 종료. 옵션에 따라 문장의 끝 또는 문서의 끝에 매치된다.
  • . : 임의의 한 문자
  • []: 문자 클래스. 문자 클래스 안에 들어가 있는 문자는 그 바깥에서 하나의 문자로 취급된다.
    • ^ : 문자 클래스 내에서 ^는 not
    • - : ex) a-z는 a에서 z까지의 문자
  • | : or를 나타냄
  • ? : 앞 문자가 없거나 하나 있음
  • + : 앞 문자가 하나 이상임
  • * : 앞 문자가 0개 이상임
  • {n,m} : 앞 문자가 n개 이상 m개 이하. {0,1} ?와 같은 의미다.
  • {n,} : 앞 문자가 n개 이상. 위의 형태에서 m이 생략된 형태이다. {0,} 이면 *와 같고 {1,} 이면 +와 같은 의미이다.
  • {n} : 앞 문자가 정확히 n개. {n,n} 과 같은 의미이다.
  • () : 하나의 패턴구분자 안에 서브 패턴을 지정해서 사용할 경우 괄호로 묶어주는 방식을 사용한다.
  • \s : 공백문자
  • \b : 문자와 공백 사이를 의미한다.
  • \d : 숫자 [0-9]와 같다.
  • \t : 탭문자
  • \w : 단어 영문자+숫자+_(밑줄) [0-9a-zA-Z_]
    문자 이스케이프는 대문자로 적으면 반대를 의미한다.

3.3. 패턴변경자

패턴구분자가 끝나면 그 뒤에 쓰는 것으로, 패턴에 일괄적으로 변경을 가할 때 사용한다. 정규식 엔진에 따라 변경자의 적용 방식이 상이하므로 해당 구현의 매뉴얼을 읽어야 한다. 예를 들어 대소문자 무시 플래그의 경우 자바스크립트 /패턴/i 로 쓰지만 파이썬에서는 re.compile(패턴, flags=re.I) Go에서는 (?i)패턴으로 쓴다.
  • i : 패턴을 대소문자 구분 없이 검사한다. 이 변경자를 사용할 경우 [a-z]로만 검사해도 자동으로 [a-zA-Z]와 같은 기능을 하게 된다. 영어가 아닌 언어(독일어, 프랑스어 등)를 다룰 때에는 버그 가능성이 있으므로 쓰지 않는 게 좋다. 대소문자라는 개념이 없는 한글, 한자, 가나문자는 이 패턴 변경자가 아무 역할도 하지 않는다.
  • s : 임의의 한 문자를 가리키는 . 메타 문자에 개행 문자(\\n)도 포함시키도록 한다. 이 변경자를 사용하면 .이 줄바꿈도 임의의 한 문자로 취급하여 찾는다.
  • g : ^문자가 문장이 아닌 문서의 처음에, $ 문자가 문장의 끝(라인 피드 \\n)이 아닌 주어진 문자열의 끝에 매치되게 변경한다.
  • m : 주어진 문자열에 줄바꿈이 있을 경우, 여러 줄로 취급하여 검사한다. (줄바꿈이 없다면 써도 의미가 없다.) 원래 정규표현식을 쓸 때 줄바꿈은 무시되는데, 이걸 사용하면 줄바꿈을 적용해서 검사한다. 그리고 ^은 한 줄의 시작, $는 한 줄의 끝으로 의미가 달라진다.
  • x : 공백 문자를 무시한다. 단, 이스케이프(역슬래쉬하고 같이 쓸 경우)하거나 문자 클래스 안에 있을 경우에는 예외. 정규식을 조금 더 읽기 편하게 만들어준다. 그러나 이 변경자를 지원하지 않는 엔진이 많은 게 단점이다.

3.4. 예제

기본적인 정규식
  • ^[0-9]*$ : 숫자
  • ^[a-zA-Z]*$ : 영문자. 패턴변경자를 써서 /^[a-z]*$/i 같이 쓸 수 있다.
  • ^[가-힣]*$ : 현대 한글(유니코드를 지원하는 정규식 엔진에 한정)
  • ^[ㄱ-ㅣ가-힣]*$ : 한글 자모 낱자를 포함한 모든 현대 한글
  • ^[a-zA-Z0-9]*$ : 영문/숫자

3.5. 탐욕적 및 게으른 수량자

정규표현식에서는 일치하는 패턴을 찾는 횟수제한이 없어 필요이상의 상황을 연출하기도 하는데 이것은 의도적으로 수량자를 탐욕적으로 만들었기 때문이다. 문법에서 말하는 탐욕적 수량자란 가능하면 가장 큰 덩어리를 찾는다는 뜻이며, 반대의 개념인 게으른 수량자(Lazy Quantifier)는 패턴에 근접하는 최소한의 덩어리를 찾는다.
  • 탐욕적 수량자 : *, +, {n,}
  • 게으른 수량자 : *?, +?, {n,}?

사용 예(Python) :
>>> data = "<div> <p>First</p> <p>Second</p></div>"
>>> import re
>>> re.findall(r"<p>(.+)</p>", data)  # 기본적으로 탐욕적인 매칭
['First</p> <p>Second']
>>> re.findall(r"<p>(.+?)</p>", data)  # 게으른 수량자 사용
['First', 'Second']


사실 제대로 구현한 HTML파서는 정규식에 유한상태기계를 결합한 '오토마타'를 사용하지 게으른 수량자를 사용하지 않는다. 물론 언어별로 HTML파서는 거의 만들어져 있으므로 HTML파서를 또 구현할 필요는 전혀 없다.

그리고 수량자를 사용해도 정규식으로는 괄호 매치를 못 한다. 무슨 얘기냐면, 지금 매치한 닫힌 괄호에 대응하는 열린 괄호를 찾는 문제는 정규식만으로는 안 된다. 고정 갯수의 괄호는 매치할 수 있지만 임의의 괄호는 매치할 수 없다. 이는 정규식이 정규 언어이기 때문에 발생하는 한계이다. 괄호 매치를 하려면 최소 푸시다운 오토마타(문맥 자유 언어)가 필요하다.

4. 공부하는 방법

이 사이트에서 step-by-step 방식으로 매우 빨리 배울 수 있다. 또는 이 사이트에서 연습할 수 있다. 프로그래밍 언어에서 배우고 싶으면 자바스크립트의 정규식 엔진을[4], 쉽게 배우고 싶으면 Ruby를 추천한다. 원조는 Perl이지만 언어의 문법 자체가 괴랄하므로 펄 언어 자체를 공부하는 부담이 Ruby보다 크기 때문이다. 이 사이트에서는 여러 언어에 대해 정규식을 디버깅 해준다.

regexperdebuggex에서 정규식 자체를 시각화해준다. 남이 만든 정규식이 도저히 읽히질 않으면 여기에 그 코드를 복붙해보자. regexper.com은 정규식을 그림 파일로 다운로드하는 기능을 제공하고 debuggex.com은 실시간 정규식 시각화 및 매치 테스트를제공한다.

Webhacking.kr에서 정규표현식을 다루는 문제가 하나 있다.


[1] Perl Compatible Regular Expressions. 이름에서 보이듯이 Perl 호환 정규표현식이지만 완전한 호환은 아니다.[2] 물론, 오늘날에는 전부는 아니더라도 다른 정규표현식에 대응되는 명령들도 기본으로 포함하고 있는 유닉스나 리눅스 배포판들이 많다. 예를들어, grep 대신 Extended 정규표현식을 사용하는 egrep, sed 대신 PCRE 정규표현식을 사용하는 psed 등이 그것이다.[3] 원래 정규표현식은 노엄 촘스키가 만든 촘스키 위계 3유형에 속하는 정규 문법에 대응되는데, PCRE에는 정규 문법에서 허용되지 않는 역참조(이전에 매칭된 부분문자열과 같은 패턴을 다시 매칭)와 같은 기능을 추가로 제공한다.[4] 브라우저에서 F12만 누르면 즉시 사용가능