정규 표현식은 특정한 규칙을 가진 문자열 집합을 표현하는데 사용하는 형식 언어이다.
정규 표현식은 패턴(pattern)으로 특정 목적을 위해 필요한 문자열 집합을 지정하기 위해 쓰이는 식이다.
정규 표현식은 POSIX, Vim, Perl 여러 형태로 존재하지만 해당 글은 ASCII를 기준으로 설명한다.
정규식은 /
문자로 감싸지며 내부에 정규문자 또는 메타문자가 위치하게 된다.
ex) /tomato/
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
RegExp
와 String
객체에서 지원하는 함수를 이용해서 여러 정규식 처리를 할 수 있다.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".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' ]
|
> "tomato apple pineapple".match(/tomato|pineapple/g)
- 결과
[ 'tomato', 'pineapple' ]
^
> "tomato apple pineapple pomato".match(/^tomato/g)
- 결과
[ 'tomato']
> "tomato apple \npineapple pomato".match(/^pomato/g)
- 결과
null
$
> "tomato apple pineapple pomato".match(/pomato$/g)
- 결과
[ 'pomato' ]
> "tomato\n pineapple pomato".match(/tomato$/g)
- 결과
null
\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 ]
\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 ]
\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 ]
\B
> "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] | 숫자 |
> "abcdefghijklmnopqrstuvwxyz".match(/[gt]/)
- 결과
[ 'g',
index: 6,
input: 'abcdefghijklmnopqrstuvwxyz',
groups: undefined ]
^
> "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 ]
> "tomato apple tomato pineapple tomato".replace(/(to)(ma)(to)/g, '$1-$3')
- 결과
to-to apple to-to pineapple to-to
?<name>
> "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' } ]
?:
> "tomato".match(/(to)(?:ma)(to)/)
- 결과
[ 'tomato',
'to',
'to',
index: 0,
input: 'tomato',
groups: undefined ]
?=
> "tomato".match(/to(?=ma)/)
- 결과
[ 'to',
index: 0,
input: 'tomato',
groups: undefined ]
?!
, ?=
반대 의미> "tomato".match(/to(?!ma)/)
- 결과
[ 'to',
index: 4,
input: 'tomato',
groups: undefined ]
?<=
?=
은 앞쪽을 기준으로 했다면, ?<=
은 뒤쪽을 기준으로 한다.> "tomato".match(/(?<=ma)to/)
- 결과
[ 'to',
index: 4,
input: 'tomato',
groups: undefined ]
?<=
, ?<=
반대 의미> "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가 아닌 유니코드를 처리 |
g
> "tomato apple pineapple pomato tomato".match(/tomato/g)
- 결과
[ 'tomato', 'tomato' ]
i
> "tomato apple pineapple pomato ToMaTo".match(/tomato/gi)
- 결과
[ 'tomato', 'ToMaTo' ]
m
> "tomato apple pineapple pomato\ntomato".match(/tomato/gm)
- 결과
[ 'tomato', 'tomato' ]
u
> "🤡🤧👺💀👻👽🤖💩😺😸😹😻😼😽🙀😿😾".match(/[😸-😿]/gu
- 결과
[ '😺', '😸', '😹', '😻', '😼', '😽', '😿', '😾' ]
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 ]
그룹 패턴 잘 보고 갑니다