안녕하세요. IT 엘도라도 에 오신 것을 환영합니다.
글을 쓰는 것은 귀찮지만 다시 찾아보는 것은 더 귀찮습니다.
완전한 나만의 것으로 만들기 위해 지식을 차곡차곡 저장해 보아요.   포스팅 둘러보기 ▼

기타 IT 지식 (Etc)

[정규 표현식] 기본 개념의 이해 (프로그래밍 언어 공통)

피그브라더 2021. 3. 1. 23:58

정규 표현식(Regular Expression), 줄여서 정규식은 주어진 문자열에서 원하는 패턴의 부분 문자열을 탐색하거나 다른 문자열로 바꾸고 싶을 때 사용하는 하나의 도구이다. 이때 중요한 것은 정규식은 프로그래밍 언어와 독립적인 도구라는 것이다. 즉, 프로그래밍 언어별로 정규식을 사용하기 위한 별도의 문법을 제공하고 있을 뿐, 정규식이 특정 프로그래밍 언어만의 고유한 문법은 아니다. 그렇기 때문에 정규식을 한 번만 제대로 배워두면 어느 프로그래밍 언어에서든 쉽게 정규식을 사용할 수 있게 된다. 이번 포스팅에서 다루고자 하는 것은 정규식 그 자체의 기본 개념이다. 각 프로그래밍 언어별로 정규식을 사용하는 방법은 조금씩 다른데, 이에 대해서는 여기서 다루지 않겠다. 하지만 이 포스팅의 내용을 완전히 이해하고 나면 각 프로그래밍 언어별로 정규식을 사용하는 방법을 터득하는 것은 시간 문제일 것이다.

 

1. 정규식의 표현 방법 : 메타 문자와 정규 문자

먼저 정규식은 무엇으로 표현되는지부터 알아보도록 하자. 정규식은 메타 문자 또는 정규 문자를 이용하여 표현하게 된다.

 

  • 메타 문자 (Meta Character) : 특별한 의미를 지닌 특수 문자들을 말한다. 즉, 문자 그대로의 의미를 지니지 않고 정규식의 특정 문법을 표현하기 위한 수단으로 사용되는 문자들이다. 대표적인 예시로는 ., *, +, ?, ^, $, {, }, (, ), |, [, ], \가 있다.
  • 정규 문자 (Regular Character) : 리터럴(문자 그대로의) 의미를 지닌 문자들을 말한다. 몇 개의 메타 문자를 제외하면 전부 정규 문자이다. 메타 문자인 \를 사용하면 메타 문자도 정규 문자처럼 사용할 수 있다. 이러한 행위를 이스케이프라고 한다.

 

예를 들어, .+\.(png|jpg)는 확장자가 png 또는 jpg인 파일의 이름과 매칭 되는 정규식이다. 여기서 ., +, \, (, |, )가 정규식의 특정 문법을 표현하기 위해 사용된 메타 문자들에 해당한다. 그리고 p, n, g, j, p, g가 문자 그대로의 의미를 지닌 정규 문자들에 해당한다. 마지막으로, \.는 메타 문자인 .을 정규 문자처럼 사용하기 위해(문자 그대로의 의미를 지니도록 하기 위해) 메타 문자인 \를 사용하여 이스케이프를 한 경우에 해당한다. (당장 이 예시가 잘 이해되지 않아도 괜찮다.)

 

그렇다면 이제 메타 문자들을 이용하여 표현할 수 있는 정규식의 여러 문법들을 본격적으로 한 번 알아보도록 하자. 단, 앞서 소개했듯 여기서 설명하는 정규식의 문법들은 프로그래밍 언어 공통이다. 즉, 각 프로그래밍 언어마다 존재하는 고유의 정규식 문법들은 여기서 다루지 않겠다.

 

2. 정규식의 기본 문법

정규식 문법 설명
abc 정규 문자로만 구성된 단순 패턴으로, 해당 문자열 자체에 매칭 된다.

EX) 본 정규식의 경우 "abc"에 매칭 된다.
a{n,m} 문자 "a"가 n번 이상 m번 이하 반복되는 문자열에 매칭 된다.
n과 m의 자리는 비워둘 수도 있다. 이 경우, n의 기본값은 0이고 m의 기본값은 무한대이다.


EX) 예를 들어, 정규식 a{2,3}의 경우 "aa" 또는 "aaa"에 매칭 된다.

a* (= a{0,}) 문자 "a"가 0번 이상 반복되는 문자열에 매칭 된다.

EX) 본 정규식의 경우 "", "a", "aa" 등에 매칭 된다.

a+ (= a{1,}) 문자 "a"가 1번 이상 반복되는 문자열에 매칭 된다.

EX) 본 정규식의 경우 "a", "aa" 등에 매칭 된다.

a? (= a{0,1}) 문자 "a"가 0번 또는 1번 반복되는 문자열에 매칭 된다.

EX) 본 정규식의 경우 "" 또는 "a"에 매칭 된다.

a{n,m}?, a*?, a+?, a?? 바로 위에서 소개한 수량자의 바로 뒤에 물음표를 사용하는 경우에 해당한다.
(기본적으로 탐욕스러운) 수량자를 탐욕스럽지 않게 만든다.
탐욕스럽지 않다는 것은, 수량자를 가능한 적은 개수의 문자들에 매칭 시킨다는 것이다.

EX) 정규식 <.*?>의 경우 "<div><ul></ul></div>"에서 "<div>"에 매칭 된다. 여기에 물음표를 사용하지 않았다면(탐욕적) "<div><ul></ul></div>"에 매칭 되었을 것이다.

. 개행 문자(\n)를 제외한 임의의 문자 하나에 매칭 된다.
정규식 옵션에 따라 개행 문자까지 매칭 되도록 하는 것도 가능하다.
[abc]




대괄호 안에 나열된 문자들 중 하나에 매칭 된다.
문자 클래스(Character Class) 또는 문자 셋(Character Set)으로 불린다.

EX) 본 정규식의 경우 "a", "b", 또는 "c"에 매칭 된다.


대괄호 안에 나열된 문자들은 대부분의 경우 이스케이프가 필요 없는 정규 문자로 인식
된다. 그러나 다음과 같은 네 개의 문자는 대괄호 안에서도 메타 문자로 인식되며, 따라서 문자 그대로의 의미를 지닌 정규 문자처럼 사용하기 위해서는 이스케이프가 필요하다.

] 문자 클래스의 끝을 표현할 때 사용하는 메타 문자이다.
- 두 문자 사이의 범위를 표현할 때 사용하는 메타 문자이다. 단, 이 메타 문자는 두 문자 사이에서 사용될 때만 유효하다.

EX) 대괄호 안에서 a-c는 abc와 동일하고 0-3은 0123과 동일하다. 
^ 문자 클래스 내에서 반대(Not)의 의미를 표현할 때 사용되는 메타 문자이다. 단, 이 메타 문자는 여는 대괄호의 바로 다음, 즉 문자 클래스의 첫 부분에서 사용될 때만 유효하다.

EX) [^0-9]는 숫자가 아닌 문자 하나에 매칭 되는 정규식이다. 
/ 메타 문자를 문자 그대로의 의미를 지닌 정규 문자처럼 사용하기 위해 이스케이프를 할 때 사용하는 메타 문자이다.
정규식1|정규식2 정규식1 또는 정규식2에 매칭 되는 문자열에 매칭 된다.

EX) 정규식 red|blue의 경우 "red"에도 매칭 되고 "blue"에도 매칭 된다.

^정규식 문자열의 시작 부분에 매칭 된다.
문자열이 여러 줄인 경우, 기본적으로 전체 문자열의 시작 부분에 매칭 된다. 그러나 정규식 옵션에 따라 각 줄의 시작 부분에 매칭 되도록 하는 것도 가능하다.

EX) 정규식 ^a의 경우 "ab""a"에는 매칭 되지만 "ba""a"에는 매칭 되지 않는다.

정규식$ 문자열의 끝 부분에 매칭 된다.
문자열이 여러 줄인 경우, 기본적으로 전체 문자열의 끝 부분에 매칭 된다. 그러나 정규식 옵션에 따라 각 줄의 끝 부분에 매칭 되도록 하는 것도 가능하다.

EX) 정규식 a$의 경우 "ba""a"에는 매칭 되지만 "ab""a"에는 매칭 되지 않는다.

\b 단어의 경계 부분에 매칭 된다.
기본적으로 단어의 구분 기준은 공백 문자이다.

EX) 정규식 \bclass의 경우 "has class""class"에는 매칭 되지만 "subclass is""class"에는 매칭 되지 않는다.
\B 단어의 경계가 아닌 부분에 매칭됩니다.
기본적으로 단어의 구분 기준은 공백 문자이다.

EX) 정규식 \Bclass의 경우 "subclass is""class"에는 매칭 되지만 "has class""class"에는 매칭 되지 않는다.
\d 숫자 문자 하나에 매칭 된다.
[0-9]와 동일하다.
\D 숫자 문자가 아닌 문자 하나에 매칭 된다.
[^0-9]와 동일합니다.
\s 공백 문자 하나에 매칭 된다.
[ \t\n\r\f\v]와 동일하다.
\S 공백 문자가 아닌 문자 하나에 매칭 된다.
[^ \t\n\r\f\v]와 동일하다.
\w 밑줄/영문/숫자 문자 하나에 매칭 된다.
[A-Za-z0-9_]와 동일하다.
\W 밑줄/영문/숫자 문자가 아닌 문자 하나에 매칭 된다.
[^A-Za-z0-9_]와 동일하다.

 

3. 정규식의 심화 문법 : 그룹화와 전방 탐색

3-1. 그룹화 (Grouping)

정규식의 특정 부분을 소괄호로 감싸면 그 부분은 하나의 그룹(Group)이 된다. 이를 그룹화(Grouping)라고 한다. 이러한 그룹화는 특정 패턴이 반복되는 문자열을 찾고 싶거나, 문자열의 특정 부분을 참조 혹은 수정하고 싶을 때 주로 사용하는 방법이다.

 

먼저, 특정 패턴이 반복되는 문자열을 찾고 싶을 때 소괄호를 사용하는 정규식의 예시로는 (abc)+가 있다. abc라는 정규식이 표현하는 문자열이 1번 이상 반복되는 문자열을 찾기 위해 소괄호를 사용하여 그룹화를 한 경우이다. 단, 이 경우에는 특정 패턴의 반복을 표현하기 위해 그룹화를 했을 뿐, 그 그룹을 다시 참조하여 사용할 일은 없다. 이와 같이 '기억'할 필요가 없는 그룹의 경우에는 소괄호 내부의 맨 앞에 ?:을 붙여줌으로써 해당 그룹을 기억하지 않도록 만들 수도 있다. (?:abc)+와 같이 말이다.

 

다음으로, 문자열의 특정 부분을 참조 혹은 수정하고 싶을 때 소괄호를 사용하는 정규식의 예시로는 (\d+)[-](\d+)[-](\d+)가 있다. 이는 전화번호를 표현하는 정규식이다. 010-1234-5678과 같은 전화번호에서 1234에 해당하는 부분만을 알고 싶거나 이를 다른 문자열로 바꾸고 싶다면 해당 부분이 소괄호를 통해 그룹화되어 있어야 한다. 이 예시에서는 두 번째 그룹, 즉 인덱스가 2인 그룹을 참조 혹은 수정하면 될 것이다. 참고로, 각 프로그래밍 언어별로 특정 그룹에 이름을 지어주는 방법이 존재하기도 한다. 이 경우에는 인덱스 대신 이름을 사용하여 해당 그룹을 참조 혹은 수정할 수 있기 때문에 코드가 더욱 직관적으로 변할 것이다.

 

심지어는 정규식 내에서 특정 그룹을 재참조하는 것도 가능하다. (abc|def)-\1과 같이 말이다. 여기서 \1은 첫 번째 그룹, 즉 인덱스가 1인 그룹에 매칭 된 문자열과 동일한 문자열에 매칭 되는 정규식을 나타낸다. 따라서 이 정규식은 "abc-abc" 혹은 "def-def"에 매칭이 될 것이다.

 

3-2. 전방/후방 탐색 (Lookahead/Lookbehind Assertion)

전방/후방 탐색은 사실 초보자가 제대로 이해하기는 다소 까다로운 문법이다. 그러니 여기에서는 프로그래머가 사용할 수 있는 수준으로만 간단히만 소개하겠다. 필자도 이 정도로만 이해하고 있고, 아직까지는 사용하는 데 크게 불편함을 느끼지 못했기 때문이다.

 

우선, 전방/후방 탐색은 이렇게 기억하는 게 편할 것이다. "검색에는 사용하지만 검색 결과에는 포함되지 않을 부분을 표현하기 위한 정규식 문법" 정도로 말이다. 구체적으로 이것이 무슨 말인지는 곧 설명할 예시들을 통해 한 번 알아보자. 전방/후방 탐색은 크게 두 종류로 구분된다. 하나는 긍정형 전방/후방 탐색이고, 나머지 하나는 부정형 전방/후방 탐색이다.

 

먼저, 긍정형 전방 탐색부터 알아보자. 정규식1(?=정규식2)는 사실상 정규식1정규식2와 검색 과정이 동일하다. 그러나 전자의 경우 검색 결과에 해당하는 문자열 내에 정규식2에 매칭 된 문자열이 포함되어 있지 않은 반면, 후자의 경우에는 그렇지 않다. 예를 들어, http(?=:) 정규식을 이용하여 "http://www.naver.com"을 검색하면 "http:"가 매칭은 되지만 반환되는 검색 결과는 "http"이다. 하지만 동일한 정규식을 이용하여 "http//www.naver.com"을 검색했다면 "http:"가 매칭 되지 않아 검색 결과를 얻지 못할 것이다.

 

긍정형 전방 탐색이 앞의 문자열을 찾는 것이라면, 긍정형 후방 탐색은 뒤의 문자열을 찾는 것이다. 즉, (?<=정규식1)정규식2는 사실상 정규식1정규식2와 검색 과정이 동일하지만 검색 결과로 돌려주는 문자열에 정규식1에 매칭 된 문자열이 포함되어 있지 않다.

 

다음으로, 부정형 전방 탐색을 알아보자. 정규식1(?!정규식2)정규식1에 대한 매칭 이후 정규식2에는 매칭 되지 않는 문자열을 찾는다. 예를 들어, \w+\.(?!bat)\w+ 정규식을 이용하여 "file.png"를 검색했다면 "file.png"가 매칭 되어 반환되지만, "file.bat"을 검색했다면 매칭 자체가 이뤄지지 않을 것이다.

 

부정형 전방 탐색이 앞의 문자열을 찾는 것이라면, 부정형 후방 탐색은 뒤의 문자열을 찾는 것이다. 즉, (?<!정규식1)정규식2정규식1에 매칭 되지 않은 이후 정규식2에 매칭 되는 문자열을 찾는다.

 

4. 프로그래밍 언어별로 살펴볼 부분

지금까지 설명한 내용은 전부 프로그래밍 언어와 독립적인, 정규식 그 자체의 기본 개념이었다. 만약 지금까지 설명한 내용들을 잘 이해하였다면, 이제는 본인이 사용하고 있는 프로그래밍 언어에서 정규식의 사용을 위한 문법을 어떻게 제공하고 있는지 살펴볼 때이다. 이때 프로그래밍 언어별로 다를 수 있는 부분이 무엇인지 미리 인지하고 가면 좋을 듯하여 여기서 짚고 넘어가고자 한다.

 

먼저, 정규식을 표현하는 방법 자체가 각기 다르다. 예를 들어, Python에서는 단순한 문자열 혹은 맨 앞에 r이 붙은 문자열로 정규식을 표현한다. 파이썬은 리터럴 문법으로 인한 백 슬래시 문제 때문에 r이 붙은 문자열을 사용하는 게 안전하다. 또한, JavaScript에서는 /로 감싸진 부분에 정규식을 작성할 수도 있고 RegExp 클래스의 인스턴스로 정규식 객체를 만들어 사용할 수도 있다.

 

다음으로, 정규식을 이용한 매칭, 검색, 교체를 수행하기 위한 API가 각기 다르다. 즉, 정규식을 이용하여 매칭 여부를 확인하고, 매칭 된 것들의 결과를 확인하고, 매칭 된 특정 문자열을 다른 문자열로 바꾸기 위한 메소드들의 이름, 기능, 사용 방법 등이 프로그래밍 언어별로 굉장히 많이 다르다. 따라서 각 프로그래밍 언어의 공식 문서를 통해 해당 API들의 사용 방법을 충분히 숙지하도록 하자.

 

마지막으로, 정규식 사용을 위한 몇몇 옵션 설정 방법이 각기 다르다. 여기서 말하는 옵션이란 정규식을 이용한 검색, 매칭, 교체를 수행할 때 영향을 주는 몇 가지 옵션들을 말한다. 예를 들어 검색 시 대소문자 구별을 하지 않도록 할 수 있고, 멀티 라인 옵션을 주어 ^과 $가 전체 문자열의 시작 부분과 끝 부분이 아닌 각 줄의 시작 부분과 끝 부분에 매칭 되도록 할 수도 있고, .이 개행 문자를 포함하도록 할 수도 있으며, 첫 번째 매칭뿐 아니라 모든 매칭을 찾도록 할 수도 있다. 각 프로그래밍 언어별로 이러한 옵션들을 어떻게 설정할 수 있게 하는지 잘 살펴보기 바란다.

 

JavaScript에서 정규식 사용하기 (링크)

Python에서 정규식 사용하기 (링크)