정규 표현식은 특정한 규칙을 가진 문자열 집합을 표현하는데 사용하는 형식 언어이다.

정규 표현식은 패턴(pattern)으로 특정 목적을 위해 필요한 문자열 집합을 지정하기 위해 쓰이는 식이다.

정규 표현식은 POSIX, Vim, Perl 여러 형태로 존재하지만 해당 글은 ASCII를 기준으로 설명한다.

  • 정규 문자 : 문자 그대로의 리터럴
  • 메타 문자 : 특정한 패턴을 지정하기 위한 문자 집합

정규식은 / 문자로 감싸지며 내부에 정규문자 또는 메타문자가 위치하게 된다.

ex) /tomato/

Java & Javascript

Java

  • Java 경우에는 Pattern, Matcher 객체를 이용해서 원하는 문자열을 탐색할 수 있다.
  • String 객체의 replace도 정규식을 지원하는데 내부적으로는 Pattern, Matcher를 이용한다.
  • 특이하게도, Java에서는 Global 플래그가 디폴트로 적용되어 있다.
Pattern pattern = Pattern.compile("tomato", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher("tomato apple TOMATO");
while(matcher.find()) {
  System.out.println("count : " + matcher.groupCount());
  System.out.println("matched string : " + matcher.group());        // 패턴에 매칭된 문자열
  System.out.println("matched string : " + matcher.group(0));        // 패턴에 매칭된 문자열(matcher.group()과 동일)

}

- 결과 -
count : 0
matched string : tomato
matched string : tomato
count : 0
matched string : TOMATO
matched string : TOMATO

Javascript

  • Javascript 경우에는 RegExpString 객체에서 지원하는 함수를 이용해서 여러 정규식 처리를 할 수 있다.
  • RegExp 객체
    • exec() : 정규식 패턴과 일치하는 문자열을 배열로 반환한다. 없는 경우에는 null을 반환한다. 글로벌 플래그 사용시 lazy 하게 매칭된 문자열에 접근
    • test() : 정규식 패턴과 대응하는 문자열의 존재 여부를 true/false 로 반환한다.
    • match() : exec() 와 동일한 기능이지만, exec() 는 글로벌 플래그 사용시 즉시 매칭된 문자열 배열을 반환한다.
  • String 객체
    • search() : 정규식 패턴과 대응하는 문자열의 인덱스를 반환한다. 없는 경우에는 -1을 반환한다.
    • replace() : 정규식 패턴과 대응하는 문자열을 다른 문자열로 치환한다.
    • split() : 정규식 패턴을 기준으로 전체 문자열을 나눈다.
## exec() ##
let match;
const reg = /tomato/gi;
while((match = reg.exec('tomato apple TOMATO')) !== null) {
        // 매칭된 문자열 처리
}

- 결과 -
[ 'tomato',
  index: 0,
  input: 'tomato apple TOMATO',
  groups: undefined ]
[ 'TOMATO',
  index: 13,
  input: 'tomato apple TOMATO',
  groups: undefined ]

## test() ##
/tomato/gi.test('tomato apple TOMATO');

- 결과 -
true

## match() ##
"tomato apple TOMATO".match(/tomato/gi)

- 결과 -
[ 'tomato', 'TOMATO' ]

## replace() ##
"tomato apple TOMATO".replace(/tomato/gi, "banana")

- 결과 -
banana apple banana

## split() ##
"tomato apple TOMATO".split(/\s/)

- 결과 -
[ 'tomato', 'apple', 'TOMATO' ]

정규 문자

정규 표현식은 기본적으로 정규문자 패턴으로 이용해서 문자열 집합을 매칭할 수 있다.
예를 들어, "tomato", "tomato apple tomato" 에서 'tomato' 패턴만 매칭하고 싶은 경우가 있다. 정규문자로 이루어진 /tomato/ 정규식은 위 문자열에서 가장 낮은 인덱스에서 매칭되는 'tomato' 문자열을 반환한다.

첫 번째 tomato 문자열만 매칭

> "tomato".match(/tomato/);

- 결과 - 
[ 'tomato', 
 index: 0, 
 input: 'tomato', 
 groups: undefined ]

> "tomato apple tomato".match(/tomato/);

- 결과 - 
[ 'tomato',
  index: 0,
  input: 'tomato apple tomato',
  groups: undefined ]

메타문자를 포함하지 않고 정규문자로만 이루어진 정규식은 매칭되는 가장 낮은 인덱스에서의 문자열만을 탐색한다. 메타문자도 정규문자로 사용하기 위해서는 '' 문자를 메타문자 앞에 붙여주면 된다.

선택 한정자

선택 한정자라는 용어는 없다. 특정 문자를 선택하는 과정에서 많이 사용되기 때문에 임의로 구분해보았다.
선택 한정자는 패턴에 사용되는 특별한 의미를 가진 메타문자이다. 선택 한정자는 문자 클래스, 그룹 등과 함께 특정 문자를 탐색하기 위해 사용될 수 있다.

메타문자 설명
. 모든 문자
| OR
^x 전체 문자열이 x로 시작 여부, 문자 클래스에서는 의미가 달라진다.
x$ 전체 문자열이 x로 끝나는 문자
\s 공백 문자(space, tab, new line)
\S 공백(space, tab, new line)이 아닌 문자
\d 숫자
\D 숫자가 아닌 문자
\w 알파벳 + 숫자 + '_'
\W 알파벳 + 숫자 + '_' 제외한 문자
\n new line
\b boundary 약자로 문자와 공백 사이의 경계 지점을 나타낸다.
\B 문자와 공백 경계에 있지 않은 지점을 가리킨다.

모든 문자 선택

  • 메타 문자 : .
> "tomato apple pineapple".match(/./g)

- 결과
[ 't', 'o', 'm', 'a', 't', 'o', ' ', 
 'a', 'p', 'p', 'l', 'e', ' ', 
 'p', 'i', 'n', 'e', 'a', 'p', 'p', 'l', 'e' ]

OR 선택

  • 메타 문자 : |
  • OR 의미를 가지며, 아래 예시에서는 tomato 또는 pineapple 문자열을 선택하는데 사용된다.
> "tomato apple pineapple".match(/tomato|pineapple/g)

- 결과
[ 'tomato', 'pineapple' ]

전체 문자열이 특정 문자로 시작 여부 확인

  • 메타 문자 : ^
  • 전체 문자열이 t 로 시작하는 지 확인
> "tomato apple pineapple pomato".match(/^tomato/g)

- 결과
[ 'tomato']

> "tomato apple \npineapple pomato".match(/^pomato/g)

- 결과
null

전체 문자열이 특정 문자로 끝나는지 확인

  • 메타 문자 : $
  • 전체 문자열이 o 로 끝나는 지 확인
> "tomato apple pineapple pomato".match(/pomato$/g)

- 결과
[ 'pomato' ]

> "tomato\n pineapple pomato".match(/tomato$/g)

- 결과
null

공백 문자 처리

공백 문자(space, tab, new line) 탐색
  • 메타 문자 : \s
> "tomato apple pineapple pomato".match(/\s/)

- 결과
[ ' ',
  index: 6,
  input: 'tomato apple pineapple pomato',
  groups: undefined ]

> "tomato\tapple pineapple pomato".match(/\s/)

- 결과
[ '\t',
  index: 6,
  input: 'tomato\tapple pineapple pomato',
  groups: undefined ]

> "tomato\napple pineapple pomato".match(/\s/)

- 결과
[ '\n',
  index: 6,
  input: 'tomato\napple pineapple pomato',
  groups: undefined ]

공백 문자(space, tab, new line) 아닌 문자 탐색

  • 메타 문자 : \S
> " apple pineapple pomato".match(/\s/)

- 결과
[ 'a',
  index: 1,
  input: ' apple pineapple pomato',
  groups: undefined ]

> "\tapple pineapple pomato".match(/\S/)

- 결과
[ 'a',
  index: 1,
  input: '\tapple pineapple pomato',
  groups: undefined ]

> "\napple pineapple pomato".match(/\S/)

- 결과
[ 'a',
  index: 1,
  input: '\napple pineapple pomato',
  groups: undefined ]

Boundary 위치를 이용해 문자 처리

boundary 지점
  • 메타 문자 : \b
  • 문자와 공백 경계 지점의 위치를 가리킨다.
  • 가장 앞의 시작점도 포함이 된다.
  • 아래 예제에서는 \bp 패턴으로 문자와 공백 경계 지점에 있는 'p' 문자를 탐색한다.
> "tomato apple pineapple pomato".match(/\bt/)

- 결과
[ 't',
  index: 0,
  input: 'tomato apple pineapple pomato',
  groups: undefined ]

> "tomato apple pineapple pomato".match(/\bp/)

- 결과
[ 'p',
  index: 13,
  input: 'tomato apple pineapple pomato',
  groups: undefined ]
boundary 아닌 지점
  • 메타 문자 : \B
  • 문자와 공백 경계 지점이 아닌 위치를 가리킨다.
  • 아래 예제에서는 \Bp 패턴으로 문자와 공백 경계가 아닌 지점에 있는 'p' 문자를 탐색한다.
> "tomato apple pineapple pomato".match(/\Bt/)

- 결과
[ 't',
  index: 4,
  input: 'tomato apple pineapple pomato',
  groups: undefined ]

> "tomato apple pineapple pomato".match(/\Bp/)

- 결과
[ 'p',
  index: 8,
  input: 'tomato apple pineapple pomato',
  groups: undefined ]

문자 클래스

문자 클래스은 로 [] 안에서 지정한 패턴 조건을 기준으로 하나의 문자를 선택한다. 따라서 [ ] 문자 클래스는 특정한 문자 하나를 선택하기 위한 조건식으로 생각하면 된다. 문자 클래스도 Global 플래그가 없다면, 매칭된 문자 중에서 가장 낮은 인덱스를 반환한다.

메타문자 설명
[xy] x, y 둘 중 하나의 문자
[^xy] x, y를 제외한 문자
[\특수문자] 특수 문자 문자 포함
[a-z] 소문자
[A-Z] 대문자
[0-9] 숫자

알파벳 중 매칭되는 가장 낮은 문자 선택

  • 예제는 모든 알파벳에서 g 또는 t 문자를 찾는 것이다.
  • g는 t보다 앞에 있기 때문에, g가 선택된다.
> "abcdefghijklmnopqrstuvwxyz".match(/[gt]/)

- 결과
[ 'g',
  index: 6,
  input: 'abcdefghijklmnopqrstuvwxyz',
  groups: undefined ]

매칭되지 않는 문자 중 가장 낮은 문자 선택

  • 메타 문자 : ^
  • 아래 예제에서는 j, u, v 중 가장 가까운 인덱스인 j 문자를 선택한다.
> "abcdefghijklmnopqrstuvwxyz".match(/[^abcdefghiklmnopqrstwxyz]/)

- 결과
[ 'j',
  index: 9,
  input: 'abcdefghijklmnopqrstuvwxyz',
  groups: undefined ]

특수문자 선택

  • 메타 문자 : \
  • 메타문자로 사용되는 특수 문자는 \ 문자로 사용해 정규 문자로 해석되도록 한다.
> "This is Tomato.".match(/[\.]/)

- 결과
[ '.', 
 index: 14, 
 input: 'This is Tomato.', 
 groups: undefined ]

소문자 매칭

  • 문자 클래스에서 사용 가능한 패턴식 : [a-z]
  • 소문자를 탐색한다.
> "This is Tomato.".match(/[a-z]/)

- 결과
[ 'h', 
 index: 1, 
 input: 'This is Tomato.', 
 groups: undefined ]

대문자 매칭

  • 문자 클래스에서 사용 가능한 패턴식 : [A-Z]
  • 대문자를 탐색한다.
> "This is Tomato.".match(/[A-Z]/)

- 결과
[ 'T', 
 index: 0, 
 input: 'This is Tomato.', 
 groups: undefined ]

숫자 매칭

  • 문자 클래스에서 사용 가능한 패턴식 : [0-9]
  • 숫자를 탐색한다.
> "This is Tomato 7.".match(/[0-9]/)

- 결과
[ '7', 
 index: 15, 
 input: 'This is Tomato 7.', 
 groups: undefined ]

그룹화

정규 표현식의 패턴을 의미 단위로 묶기위해 ()를 사용한다. 구체적으로, 패턴과 일치하는 문자열을 탐색하고 () 그룹 단위로 나눠서 캡쳐를 해놓는다. 쉽게 생각해보면, () 없는 경우와 같이 동일한 패턴의 문자열을 찾고, 해당하는 문자열을 ()로 나눠 그룹화하는 것이다. 이러한 기능은 매칭되는 문자열에서 의미 단위로 나눠서 처리를 유연하게 한다는 장점이 있다. 매칭된 문자열을 split 함수로 또 다시 나눌 필요가 없다는 것이다. 그룹은 1번부터 번호가 부여되고, 0번은 패턴과 일치하는 전체 문자열이다.

ps. 내부적으로는 Tree 이용해서 구현하고 있을 것으로 추측이 된다. 아래 예제를 보면 1, 2, 3번 그룹이 0번 그룹을 가리키고 있는 것으로 볼 수 있다.

그룹화

> "tomato".match(/(to)(ma)(to)/)

- 결과
[ 'tomato',
  'to',
  'ma',
  'to',
  index: 0,
  input: 'tomato',
  groups: undefined ]

그룹 참조

  • 캡쳐된 각각의 그룹은 $1, ..., $9 로 참조가 가능하다.
  • $0 은 매칭된 전체 문자열이다.
> "tomato apple tomato pineapple tomato".replace(/(to)(ma)(to)/g, '$1-$3')

- 결과
to-to apple to-to pineapple to-to

해당 그룹 이름 지정

  • 메타 문자 : ?<name>
  • 각각의 그룹에 이름을 지정할 수 있으며, 해당 이름으로 참조할 수 있다.
  • groups에 이름과 그룹이 객체로 저장한다.
> "tomato".match(/(?<first>to)(?<second>ma)(?<third>to)/)

- 결과
[ 'tomato',
  'to',
  'ma',
  'to',
  index: 0,
  input: 'tomato',
  groups: [Object: null prototype] { first: 'to', second: 'ma', third: 'to' } ]

그룹 비캡쳐링

  • 메타 문자 : ?:
  • 해당하는 그룹은 패턴으로써의 역할은 하지만, 캡쳐되지는 않는다.
  • 아래 예제에서는 'ma' 문자열이 캡쳐되지 않는 것을 확인할 수 있다.
> "tomato".match(/(to)(?:ma)(to)/)

- 결과
[ 'tomato',
  'to',
  'to',
  index: 0,
  input: 'tomato',
  groups: undefined ]

앞쪽 문자열 기준의 그룹 조건 (비캡쳐링)

  • 메타 문자 : ?=
  • 앞쪽 문자열을 기준으로 그룹 조건을 만족하는 지를 비교한다.
  • 해당 그룹은 패턴으로써 작동은 하나 캡쳐링되지 않는다.
  • 아래 예제에서는 'to' 문자열을 찾는데, 뒤에 'ma' 문자열이 있는 'to' 문자열을 탐색하는 것이다.
> "tomato".match(/to(?=ma)/)

- 결과
[ 'to', 
 index: 0, 
 input: 'tomato', 
 groups: undefined ]

앞쪽 문자열 기준의 그룹 조건 (비캡쳐링)

  • 메타 문자 : ?!, ?= 반대 의미
  • 앞쪽 문자열을 기준으로 그룹 조건을 만족하지 않는 지를 비교한다.
  • 아래 예제에서는 'to' 문자열을 찾는데, 뒤에 'ma' 문자열이 없는 'to' 문자열을 탐색하는 것이다.
  • 결과적으로 마지막 4 인덱스의 'to'는 뒤에 'ma' 가 없기 때문에 선택된다.
> "tomato".match(/to(?!ma)/)

- 결과
[ 'to', 
 index: 4, 
 input: 'tomato', 
 groups: undefined ]

뒤쪽 문자열 기준의 그룹 조건 (비캡쳐링)

  • 메타 문자 : ?<=
  • ?= 은 앞쪽을 기준으로 했다면, ?<= 은 뒤쪽을 기준으로 한다.
  • 뒤쪽 문자열을 기준으로 그룹 조건을 만족하는 지를 비교한다.
  • 아래 예제에서는 'to' 문자열을 찾는데, 앞에 'ma' 문자열이 있는 'to' 문자열을 탐색하는 것이다.
> "tomato".match(/(?<=ma)to/)

- 결과
[ 'to', 
 index: 4, 
 input: 'tomato', 
 groups: undefined ]

뒤쪽 문자열 기준의 그룹 조건 (비캡쳐링)

  • 메타 문자 : ?<=, ?<= 반대 의미
  • 뒤쪽 문자열을 기준으로 그룹 조건을 만족하지 않는 지를 비교한다.
  • 아래 예제에서는 'to' 문자열을 찾는데, 앞에 'ma' 문자열이 없는 'to' 문자열을 탐색하는 것이다.
  • 결과적으로 마지막 0 인덱스의 'to'는 앞에 'ma' 가 없기 때문에 선택된다.
> "tomato".match(/(?<!ma)to/)

- 결과
[ 'to', 
 index: 0, 
 input: 'tomato', 
 groups: undefined ]

수량 한정자

패턴에서 문자의 수량을 지정하며, 정규 문자또는 문자 클래스, 그룹과 함께 사용할 수 있다.

메타문자 설명
* 0 번 이상
+ 1 번 이상
? 0 또는 1 번
{n} n 번
{n, } n 번 이상
{n, m} n 번 이상 m 번 이하

플래그

플래그는 정규 표현식의 패턴의 전체적인 조건을 부여한다. 정규 표현식을 의미하는 / / 이후에 플래그를 나타내고 해당 정규 표현식에 해당 조건이 설정된다.

플래그 의미 설명
g Global 매칭되는 모든 문자열 탐색
i Ignore 대소문자를 구분하지 않음
m Multi Line 행 구분없이 문자열 탐색
u Unicode ASCII가 아닌 유니코드를 처리

Global

  • 플래그 : g
  • 패턴과 대응하는 첫 번째 인덱스의 문자열만이 아닌 모든 인덱스 기준으로 탐색한다.
> "tomato apple pineapple pomato tomato".match(/tomato/g)

- 결과
[ 'tomato', 'tomato' ]

Ignore

  • 플래그 : i
  • 해당하는 패턴을 문자열의 대소문자를 구분하지 않고 탐색한다.
> "tomato apple pineapple pomato ToMaTo".match(/tomato/gi)

- 결과
[ 'tomato', 'ToMaTo' ]

Multi Line

  • 플래그 : m
  • 해당하는 패턴을 문자열의 new line를 구분하지 않고 탐색한다.
> "tomato apple pineapple pomato\ntomato".match(/tomato/gm)

- 결과
[ 'tomato', 'tomato' ]

Unicode

  • 플래그 : u
  • 해당하는 패턴을 ASCII 가 아닌 Unicode로 탐색한다.
  • 이모지
> "🤡🤧👺💀👻👽🤖💩😺😸😹😻😼😽🙀😿😾".match(/[😸-😿]/gu

- 결과
[ '😺', '😸', '😹', '😻', '😼', '😽', '😿', '😾' ]

URL 매칭 예제

  • URL 문자열을 scheme, user, password, host, port, path 의미 단위로 나누는 정규식을 작성해본다.
  • RFC 1738BNF 규격을 따른다.
const scheme = "([a-z0-9+-.]+)";

const digit = "[0-9]";
const hex = `${digit}|[a-fA-F]`;
const escape = `%${hex}${hex}`;
const unreserved = `[a-zA-Z]|[0-9]|[$\\-_.+]|[!*'(),]`;
const uchar = `${unreserved}|${escape}`;
const user = `${uchar}|[;?&=]`;
const password = `${uchar}|[;?&=]`;
const login = `(?:((?:${user})+)(:(?:${password})+)*@)*`;

const alphadigit = `[0-9a-zA-Z]`;
const domainlabel = `${alphadigit}(?:-${alphadigit})*`;
const toplabel = `[a-zA-Z](?:-[a-zA-Z])*`;
const hostname = `(?:${domainlabel}.)*(?:${toplabel})+`;
const hostnumber = `${digit}+.${digit}+.${digit}+.${digit}+`;
const host = `(${hostname}|${hostnumber})+`;
const port = `(:[0-9]+)*`;
const domain = `${host}${port}`;

const reserved = `[;/?:@&=]`;
const xchar = `${reserved}|${unreserved}|${escape}`;
const path = `((?:${xchar})*)*`;
const reg = new RegExp(`${scheme}:\\/\\/${login}${domain}${path}`);

reg.exec(url);

- 결과 -
[ 'http://raccoonback:1234@www.example.com:8080/path1/path2/path3/resource',
  'http',
  'raccoonback',
  ':1234',
  'www.example.com',
  ':8080',
  '/path1/path2/path3/resource',
  index: 0,
  input: 'http://raccoonback:1234@www.example.com:8080/path1/path2/path3/resource',
  groups: undefined ]

[ 'http://123.123.122.22',
  'http',
  undefined,
  undefined,
  '123.123.122.22',
  undefined,
  undefined,
  index: 0,
  input: 'http://123.123.122.22',
  groups: undefined ]

[ 'http://123.123.122.22:8090',
  'http',
  undefined,
  undefined,
  '123.123.122.22',
  ':8090',
  undefined,
  index: 0,
  input: 'http://123.123.122.22:8090',
  groups: undefined ]

[ 'http://123.123.122.22:8090/orange/apple/banana',
  'http',
  undefined,
  undefined,
  '123.123.122.22',
  ':8090',
  '/orange/apple/banana',
  index: 0,
  input: 'http://123.123.122.22:8090/orange/apple/banana',
  groups: undefined ]

유용한 온라인 사이트

참고