정규표현식

정규 표현식 - 메타문자

  • -

이번 포스트에서는 본격적으로 정규 표현식 작성 방법에 대해 살펴보자.

정규 표현식은 사용하는 언어에 따라 기호들의 범위가 다른데 여기서는 JavaScript를 기준으로 살펴본다.

 

정규표현식

 

기본적인 정규 표현식 구성

정규 표현식은 /pattern/flag 형태로 구성되는데 검사하고 싶은 문자열에서 pattern에 부합되는 내용이 있는지 찾아보는 게 목적이다. pattern에는 찾으려는 문자열을 넣어주면 된다. 문자 중 특수한 의미를 갖는 문자를 메타문자라고 한다. 예를 들어 '.'은 any character 1개를 의미한다.

flag는 정규표현식을 이용해서 탐색을 진행할 때 사용되는 일종의 검색 옵션이다. 대표적으로 다음의 것들이 사용된다.

  • g: global(전체)에 걸쳐서 계속해서 검색한다. 이 옵션이 없을 경우는 맨 처음 발견하면 더 이상 탐색하지 않는다.
  • i: ignore case로 찾는 문자가 영문일 경우 대/소문자를 구분하지 않는다.
  • m: multiline으로 데이터의 행이 바뀌어도 ^와 $ 규칙에 맞는 문자열을 검색한다.

다음의 그림은 /S./가 적용됐을 때와 /S./gi가 적용되었을 때의 매칭 결과를 보여준다.

/S./ /S./gi

일반적으로 정규 표현식을 작성할 때는 위와 같이 일반 문자(Literal)만을 검색하는 일은 거의 없고 메타 문자를 활용한 패턴에 기반하여 검색하게 된다. 

 

Meta Sequence

 

메타 시퀀스

대부분의 언어에서 처럼 '\'는 정규 표현식에서 escape 문자로 사용된다. 따라서 메타문자 앞에 사용되면 표현식의 의미를 제거한다.

정규표현식에서는 '\'가 특별한 알파벳과 조합돼서 좀 더 특별한 의미를 갖기도 하는데 일반적으로 소문자인 경우는 대상이 됨을, 대문자인 경우는 그렇지 않음을 의미한다.

표현식 레일로드 다이어그램 의미 적합 부적합
\   \뒤의 문자를 그냥 문자로 처리함
 - \.는 단순히 . 문자
   
.
어떤 문자 하나가 있다.    
\d
숫자를 의미한다.
- [0-9]와 동일
1, 2, 3 A, a
\D
숫자가 아닌 것을 의미한다.
- [^0-9]와 동일
A, a 1, 2, 3 
\s
공백을 의미한다.
- [\r\t\n\f\v ]와 동일
  a, b
\S
공백이 아닌것을 찾는다.
- [^\r\t\n\f\v ]와 동일
a, b  
\w
단어를 구성하는 알파벳, 숫자, _를 찾는다. 따라서 한글은 대상이 아니다.
- [a-zA-Z0-9_]와 동일
a, 1, _ 가, !
\W
알파벳, 숫자, _를 제외한 문자를 찾는다.
- [^a-zA-Z0-9_]와 동일
!, 가 a, 1, _
x|y
두 표현식 중 하나가 올 수 있다. x, y a, b

 

활용 예

다음의 표현식을 읽고 해석후 적합 한 문자열을 이야기해 보자.

  • /\W\d\w/gm
  • /\w:\s\d/gm
  • /\w|\s|\W/gm

 

Anchors

 

앵커

Anchors는 문자열의 시작과 종료와 관련된 메타문자이다.

메타문자 레일로드 다이어그램 의미 적합 부적합
^x
문장이 특정 패턴으로 시작한다. x, xa, xb ax, bx 
x$
문장이 특정 패턴으로 끝난다. x, ax, bx xa, xb
\b
단어(\w)의 경계의 위치(단어의 시작이나 끝)를 찾는다.    
\B
\b가 아닌 위치를 찾는다.    
^나 $를 여러 줄에 걸쳐서 확인하기 위해서는 flag에 m(multi line)이 필요하다.

다음은 \b와 \B의 차이를 보여준다.

 

활용 예

다음의 표현식을 읽고 해석 후 적합 한 문자열을 이야기해 보자.

  • /^\w....\d$/gm
  • Her name is Wendy! 에서 Wendy의 n을 찾으려면 어떤 표현식을 써야 할까?

 

Character Classes

 

문자 클래스

문자 클래스는 [...] 형태로 [] 안에 있는 문자 또는 그룹 중 하나와 일치함을 의미한다.

괄호 레일로드 다이어그램 의미
[xy]
[]안의 문자 중 하나를 의미한다. 
- [bts]: b 또는 t 또는 s
[a-z]
'-'를 사용하면 from ~ to를 의미한다.
- [a-z]: 알파벳 소문자 하나
- [0-9]: 숫자 하나
- [가-힣]: 한글 하나
- [a-zA-Z0-9]: 알파벳 대/소, 숫자 중 하나
[^x]
^가 [] 내부에 사용되는 경우 시작 문자가 아닌 부정의 의미
- [^0-9]: 숫자가 아닌것

[...] 내부에서는 메타시퀀스(\s, \d)등을 제외한 메타 문자들은 모두 일반 문자로 취급된다는 점도 기억해두자.

 

Quantifiers

 

수량자

수량자는 패턴의 등장 회수를 지정하는 것으로 ?, +, * 처럼 간단한 기호 종류도 있고 {}을 이용해 좀 더 세밀하게 조절할 수도 있다. {}를 이용할 때에는 pattern{m[, [n]]} 형태로 작성하며 "pattern이 m번 이상, n번 이하 반복된다"를 의미한다. 이때 ,와 n을 생략해서 다양한 경곗값을 구성할 수 있다.

괄호 레일로드 다이어그램 의미 적합 부적합
x+
바로 앞의 표현식이 1회 이상 반복한다. x, xx, xxx a, b, c
x?
바로 앞의 표현식이 0 또는 1회 나타난다. x, a  
x*
바로 앞의 표현식이 0회 이상 반복한다. a, x, xx, xxx  
x{m}

x{3}
x{m}: m번 반복됨 xxx x, xx
 x{m, }

x{3, }
 
{m, }: m 번 이상 반복됨 xxx, xxxx x, xx
 x{m, n}

x{3, 6}
 
{m, n}: m번 이상 n번 이하 반복됨 xxx, xxxxxx x, xx

 

Lazy와 Greedy

+, ?, * 수량자와 관련해서는 탐욕적인 Greedy 방식과 게으른 Lazy라는 두 가지 방식이 존재한다. 

방식 설명 표현 예 적용 예
Greedy 정규 표현식의 기본 방식으로 최대한 많은 문자와 일치하도록 시도한다. 즉 .*는 최대한 많은 문자를 일치시키려고 탐색한다. a.*e abcde
abddddddde
Lazy Lazy 방식은 수량자 뒤에 ?를 추가하는 방식인데 Greedy와 반대로 최소한으로 필요한 문자와 일치하도록 시도하고 필요하다면 확장한다. a.*?e  

 

알쏭달쏭한 이 Greedy와 Lazy 한 성격은 언제 활용될 수 있을까? <h1>hello</h1><h1>Hi</h1>이라는 문장에서 첫 번째 <h1>을 선택하려고 한다고 해보자. 기본적으로 <h1>과 </h1> 사이에 여러 텍스트가 들어갈 수 있는 상황이므로 .+를 써볼 수 있다. 이를 각각 Greedy 한 방식과 Lazy 한 방식으로 작성해 보자.

Greedy 한 방식은 최대한 많은 . 로 해석해서 맨 앞의 <h1>과 맨 뒤의 </h1>을 경계로 문자를 채운 결과 전체적으로 하나의 match가 이뤄진 반면 Lazy 한 방식은 최소한으로 잡은 결과 2개의 match가 이뤄진 것을 확인할 수 있다.

 

활용 예

다음의 표현식을 읽고 해석 후 적합 한 문자열을 이야기해 보자.

  • xa?y$
  • [가-힣]{2,5}
  • [a-z]{2}[0-9][-][^A-Z]

 

Grouping and Capturing

 

그룹핑

()는 그룹을 형성하고 그룹 내의 패턴은 하나의 단위로 취급된다.

메타문자 레일로드 다이어그램 의미 적합 부적합
(xy)
괄호 안의 내용을 하나로 그룹화 한다. xy x, y

 

캡쳐링

그룹핑한 내용은 순서대로 캡처돼서 나중에 재사용(Back Reference)이 가능 한데 이때 '\그룹번호'를 사용한다. 다음은 (ha)와 (haa) 두 개의 그룹이 있는데 첫 번째 그룹 내용을 재사용하기 위해서 \1으로, 두 번째 그룹 내용을 다시 사용하기 위해서 \2로 참조하고 있다. 따라서 다음 표현에 부합하는 문장은 ha-ha,haa-haa가 된다.

경우에 따라서는 그룹을 캡처하지 않을 수도 있는데(non-capturing) 이때는 (?:)를 사용한다. 즉 다음의 표현에서 두 개의 그룹이 있지만 첫 번째 그룹을 캡처하지 않기 때문에 haa를 캡처할 때 \2가 아니라 \1이라고 쓰고 있다.

참고로 캡쳐링을 사용하면 그룹에서 추출되는 다양한 정보를 유지하기 때문에 더 많은 메모리를 소모한다. 따라서 캡쳐된 정보를 활용하지 않는다면 non-capturing을 사용하는 것이 권장된다.

 

이름 있는 그룹과 캡쳐링

index를 통한 캡쳐링은 식이 복잡해지면 이해하기 어려워질 수밖에 없다. 이런 경우는 그룹에 이름을 부여하고 이름 기반으로 참조가 가능하다. 이름을 부여할 때는 (?<name>...)을 사용하고 이름으로 참조할 때는 \k<name>을 사용한다.

 

전방탐색(Look ahead)과 후방탐색(Look behind)

일반적인 정규표현식은 체크하면서 기준에 통과하면 읽고 거기까지를 체크 범위로 포함한다. 예를 들어 여러 개의 url 중에서 protocol 정보만 뽑아내고 싶은 경우 .+:라는 표현식을 생각해 보자. 

 

즉 어떤 문자열이 쭉 오고 마지막에 ':'까지가 대상이다. 따라서 http만 사용하기 위해서는 http: 까지 선택된 값을 프로그래밍으로 가져온 후 마지막 :을 지워줘야 한다. 약간 번거로울 수 있는데 이때 사용할 수 있는 방법이 전방탐색과 후방탐색이다. 

전방탐색을 표현할 때는 '(?=조건)'을 사용한다. 진행하고 있는 방향에 혹시 조건에 만족하는 내용이 있나 체크해 보는 형태이다. 그리고 그 조건 자체는 포함시키지 않는다.

이제 ':'를 제거하기 위해서 별도로 신경 쓸 필요가 없어졌다.

후방 탐색도 마찬가지이다. 방향이 원래의 진행방향이 아닌 반대 방향으로 가는 것이다.  후방탐색을 표현할 때는 방향을 개입시켜서 '?<=조건'의 형태를 사용한다. 

그런데 Regexper.com에서는 이 내용을 표현하지 못한다. 바로 테스트를 살펴보자.

$를 표시할 때 \를 이용해서 escape 시킨 내용에 주의하자.

 

부정 전/후방 탐색

이 외에도 부정 전방 탐색과 부정 후방 탐색도 존재한다. 말 그대로 앞서 언급한 전방/후방 탐색에 부정만 붙인 것이다.

  • (?=조건): 전방 탐색
  • (?<=조건): 후방 탐색
  • (?!조건): 부정 전방 탐색
  • (?<!조건): 부정 후방 탐색

 

활용 예

다음의 표현식을 읽고 해석 후 적합 한 문자열을 이야기해 보자.

  • (a1){2}b{2,4}c{2,}
  • ^(abc)(def)+(ghi)$
  • ^.(\$|a)?y*|test$

 

'정규표현식' 카테고리의 다른 글

정규 표현식 - SQL  (0) 2020.06.13
정규 표현식 - JavaScript  (0) 2020.06.11
정규 표현식 - Java  (0) 2020.06.11
정규 표현식 - 유용한 표현들  (1) 2020.06.10
정규 표현식 - 개요  (1) 2020.06.09
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.