Regular Expressions
Strings
Methods used to process the strings
Regular Expressions
Literal Characters
Regular Expression character classes
Flags/Modifiers/Quantifiers
Regular Expression(정규 표현식)은 특정 패턴과 문자열이 일치하는지 찾는 데 쓴다.
JavaScript에도 다른 언어들과 마찬가지로 Regular Expression가 존재하는데 RegExp라는 class로 대표된다. RegExp는 String class와 비슷하여 패턴이 복잡한 일치하는 값 찾기 작업을 간단히 하는 데 도움을 준다.
Regular Expression를 쓰기에 앞서 Regular Expression의 문법부터 이해해보자. 우리는 Regular Expression를 짜는 데는 크게 두 가지 방식이 있다.
첫 번째 방식은 regular expression literal을 쓰는 것, regular expression은 아래 보기처럼 '/'로 감싸서 만든다.
let myPattern = /a$/;
두 번째 방식은 RegExp 객체의 constructor function을 쓰는 것이다. RegExp() constructor function을 씀으로써 위의 첫 번째 보기와 똑같은 결과물을 만들 수 있다.
let myPattern = new RegExp('a$');
위의 예시들은 'a'로 끝나는 모든 문자열과 매칭된다. 간단한 regular expression을 예로 들자면 /abc/와 같이 나타낼 수 있는데 'abc'가 연속하는 문자열을 찾을 때 쓰인다. 참고로 regular expression의 약어는 'regexr'로 쓴다.
const myString = `Hi, do you know your abc's`;
const regex = /abc/;
//const regex = new RegExp('abc');
console.log(regex.test(myString));
//test(): regular expression과 특정 문자열이 일치하는지 확인하여 boolean형식으로 반환하는 method
위의 예시를 실행해보면 결과로 'true'를 반환한다. 왜냐하면 우리가 찾으려는 'abc'라는 패턴이 'myString'이라는 변수에 존재하기 때문이다. 다음 예도 함께 살펴보자.
const myString1 = `Hi, do you know your abc's`;
const regex1 = /ac/;
//const regex1 = new RegExp('ac');
console.log(regex1.test(myString1));//false
위의 예는 'false'를 반환한다. 왜냐하면 'ac'와 정확하게 일치하는 문자열이 존재하지 않기 때문이다.
알파벳이나 숫자의 경우 regular expression과 맞아떨어진다. 하지만 때때로 알파벳이 아닌 문자의 일치 여부를 확인해야 하는데 이 같은 기능도 JavaScript에서 지원한다. 아래 표를 확인하자.
문법과 자료형 MDN_Link
Character | Matches |
---|---|
\ t | Tab |
\ n | Newline |
\ v | Form Feed |
\ f | Carriage Return |
\ xmm | Latin character; example \x0A is same as \n |
\ () | The Null Character |
Flags | Description |
---|---|
standard | |
g | global |
i | case-insensitive matching |
m | this performs multiline matching |
u | Unicode |
y | sticky |
const btn = document.querySelector('#clickBtn');
const pTag = document.querySelector('.result');
const myString = `Regular expressions are patterns used to match character combinations in strings.
In JavaScript, regular expressions are also objects`;
const regPattern = /ions/;
btn.addEventListener('click',(e)=>{
const result = myString.match(regPattern);
pTag.innerHTML = result;
});
button을 누르면 아래와 같은 결과가 반환된다.
위 예제의 myString에서 regular expression으로 준 'ions'가 3군데(expressions, combinations, expressions)에 존재함에도 결과값은 ions 단 하나만 반환되는 것일까? 아무런 flag를 쓰지 않고서는 처음 일치하는 값만을 반환한다. 이 regular expression에 g flag를 붙이면 어떻게 될까?
const regPattern = /ions/g;//global
전체 영역을 탐색하면서 결과값은 'ions, ions, ions'가 된다.
그렇다면 시작값과 끝값은 같으나 중간값이 다른 문자열의 일치 여부는 어떻게 확인할까?
const myString = `Mam, Mom, Mum`;
const regPattern = /M/g;//global
만약 이렇게 g flag만을 주게 되면 결과값은 'M, M, M'이 된다. 단순히 M이 아닌 단어 전체를 반환하려면 어떻게 해야 할까? regular expression을 다음과 같이 쓰면 된다.
const regPattern = /M.m/g;//global
마침표.
는 위의 예에서 글자 가운데의 문자 하나를 대체한다. 따라서 아래 예와 같이 중간값의 길이가 길어진다면 작동하지 않는다. 왜냐하면 마침표는 공백을 제외하고 단 하나의 문자만을 대체하기 때문이다.
const myString = `Mam, Mom, Mum, Magm`;
그렇다면 소수점이 포함된 문자열 탐색은 어떻게 할까? 아래 예를 살펴보자.
const myString = `5.00, 510, 570`;
const regPattern = /5.0/g;
위 예제는 '5.0, 510, 570'을 반환한다. 여기서 '5.0'만을 반환하려면 어떻게 해야 할까? 바로 아래 예처럼 regular expression에 백슬래시\
를 .
앞에 넣어준다.
const regPattern = /5\.0/g;
이렇게 되면 \
바로 뒤의 문자는 무시가 되고 결과적으로 '5'와 '0'사이의 문자를 .
으로 대체한 값을 탐색하는 것이 아니라 '5.0' 을 탐색하게 되는 것이다.
참고로 quoatation mark는 regular character 취급을 하기 때문에 탐색 시 \
를 덧붙여 쓰지 않아도 된다.
앞서 말했다시피 regular expression을 구현하는 데는 2가지 방식이 있다. 이때까지는 regular expression literal 방식을 활용하여 작성했는데 RegExp object의 constructor function을 활용해서는 어떻게 구현하는지 살펴보자.
const regExpression = new RegExp(‘ab+c’,’i’);
첫 번째 argument는 regular expression liter이고 두 번째는 flag인데 선택적 입력값이기 때문에 필요에 따라 적절한 flag값을 입력하면 된다. 또한 text()
, exec()
두 가지 method도 사용 가능하다. 자세한 설명은 chapter1 후반부에 다루도록 하겠다.
test():
MDN_link
exec():
MDN_link
다음 두 regular expression의 차이는 무엇일까?
/abc/
/[abc]/
첫 번째는 연속된 'abc'를 탐색하는 데 쓰이고, 두 번째는 []
로 감싸줌으로써 각각의 문자를 classes로 통합하여 대괄호 내의 'a', 'b', 'c' 어떤 값이든 일치 여부를 탐색 가능하게 한다. 이와 반대로 대괄호 안의 값을 제외한 모든 값을 탐색하고 싶을 때는 [^abc]
와 같이 ^
를 맨앞에다 덧붙이면 된다.
자주 쓰이는 표현들을 살펴보자
[0-9]
: 0부터 9까지 숫자 탐색
[a-z]
: a부터 z까지 소문자 알파벳 탐색
[A-Z]
: A부터 Z까지 대문자 알파벳 탐색
위의 표현들은 조합하여 사용 가능하다. 예를 들어 모든 소문자, 대문자를 탐색하고 싶을 때는 [a-zA-Z]
와 같이 쓰고, 모든 알파벳, 숫자를 탐색하고 싶다면 [a-zA-Z0-9]
와 같이 쓰면 된다.
아래 예를 살펴보면서 개념을 정리해보자.
const myString = `Regular expressions are patterns used to match character combinations in strings.
In JavaScript, regular expressions are also objects`;
const regPattern = /[JaS]/g;//global
//expected Output: a,a,a,a,a,a,a,J,a,a,S,a,a,a
[]
안의 'J', 'a', 'S'에 해당하는 값을 global mode로 탐색해서 모두 반환하는 것이다. 반대의 예도 살펴보자.
const regPattern = /[^JaS]/g;//global
/* expected output
R,e,g,u,l,r, ,e,x,p,r,e,s,s,i,o,n,s, ,r,e, ,p,t,t,e,r,n,s, ,u,s,e,d, ,t,o, ,m,t,c,h, ,c,h,r,c,t,e,r, , , , , , , , ,
,c,o,m,b,i,n,t,i,o,n,s, ,i,n, ,s,t,r,i,n,g,s,., ,I,n, ,v,c,r,i,p,t,,, ,r,e,g,u,l,r, ,e,x,p,r,e,s,s,i,o,n,s, ,r,e, ,l,s,o,
,o,b,j,e,c,t,s */
[]
안의 'J', 'a', 'S'를 제외한 모든 값을 반환하는 것을 볼 수 있다. 이는 숫자에서도 똑같이 적용된다.
아래 표는 대표적인 metacharacter 모음이다.
metachracters | description |
---|---|
\t | It matches the tab character |
\v | It matches the vertical tab character |
\r | It matches the carriage return character |
\f | It matches the form feed character |
\b | It finds a match at the beginning or the end of a word |
\B | It tries to find a match but not at the beginning or the end of a word |
\s | It matches a whitespace character |
\S | It matches a non-whitespace character |
\d | It matches any digit character, any ASCII digit, or same as [0-9] |
\D | It matches a non-digit character |
\w | Tries to find any ASCII word character, same as if we wrote [azA-Z0-9_] |
\W | Tries to find a non-word character or same as [^a-zA-Z0-9_] |
[...] | Matches any character between the brackets |
[^...] | Matches any character that is not in between the brackets |
\uhhh | Matches a UTF-16 value WITH four hexadecimal digits. |
아래 예제를 통해 어떻게 쓰이는지 알아보자.
const myString = `Regular expressions are patterns used to match character combinations in strings
100% true. In JavaScript, regular expressions are also objects!`;
const regPattern = /\w/g;//global
/* expected output
R,e,g,u,l,a,r,e,x,p,r,e,s,s,i,o,n,s,a,r,e,p,a,t,t,e,r,n,s,u,s,e,d,t,o,m,a,t,c,h,c,h,a,r,a,c,t,e,r,c,o,m,b,i,n,a,t,i,o,n,
s,i,n,s,t,r,i,n,g,s,1,0,0,t,r,u,e,I,n,J,a,v,a,S,c,r,i,p,t,r,e,g,u,l,a,r,e,x,p,r,e,s,s,i,o,n,s,a,r,e,a,l,s,o,o,b,j,e,c,t,s
*/
\w
는 모든 문자의 일치 여부만을 확인하기 때문에 %
, !
빼고는 다 반환된다.
const regPattern = /\W/g;//global
/* expected output
, , , , , , , , , , , , , , , , , , ,%, ,., , ,,, , , , , ,!
*/
\W
는 문자가 아닌 값 %
와 !
만을 반환한다. \d
는 어떤 값을 반환할까?
const regPattern = /\d/g;//global
/* expected output
1,0,0
*/
\d
는 숫자값만을 찾아 반환한다.
만일 regular expression에 \
을 포함시키고 싶다면 어떻게 해야 할까? \\
이런 식으로 포함할 \
앞에 또 다른 \
를 덧붙여 쓰면 된다.
\
p{...}ES2018부터 Unicode문자를 다루려면 u flag를 써야 한다. u flag를 쓴다면 character class인 \p{...}
과 \P{...}
역시 지원된다. Unicode 내의 모든 문자는 Unicode standard에 의해 규정되는 properties를 갖는다. 예를 들어 문자는 알파벳에 속하고 숫자라면 Arabic이나 Chinese alphabets에 속한다.
\p{...} class
를 쓴다면 반드시 끝에 u flag
를 덧붙여줘야 한다. Number
와 Letter
properties는 각각 N
과 L
이라는 alias
를 갖는다.
다음은 u flag와 p class를 활용하여 regular expression을 구현한 예이다.
let mixedString = "Hi აბ 必 ᄇ ᄉ ";
let regex1 = /\p{L}/gu;
let regex2 = /\p{L}/g;
console.log(regex1.test(mixedString));//true
console.log(regex2.test(mixedString));//false
앞서 설명했다시피 \p{...}
class는 u flag와 함께 써야 하므로 regex1은 true
, regex2는 false
를 반환한다.
만약 10진수(decimal number)를 탐색하는 regular expression을 작성하고 싶다면
/\p{Decimal_Number}/u
위와 같이 작성하면 된다.
중국어, 키릴문자, 그리스어, 아랍어 등과 같은 문자를 탐색할 때 쓰이는 Unicode property는 Script
인데 약어로 sc
를 쓴다. 아래 실례를 살펴보자.
let mixedString = "Истражувањето на компанијата";
let regex1 = /\p{sc=Cyrillic}/gu;
console.log(regex1.test(mixedString));//true
또한 $, €, ¥와 같은 화폐기호를 찾을 때 쓰는 Unicode property도 있는데 Currency_Symbol
로서 약어는 Sc
로 쓴다. 숫자와 함께 쓰인 예를 보자.
let mixedString = `$5, €10, ¥109`;
let regex1 = /\p{Sc}\d/gu;
console.log( regex1.test(mixedString));//true
Quantifier(수량자, 数量詞)는 해당 문자열에서 숫자가 얼마나 반복될 수 있는지 나타낼 때 쓰인다. 앞서 배웠듯 4개의 문자 일치 여부를 탐색하는 regular expression을 /\d\d\d\d/
로 작성할 수 있다. 허나 몇 번이나 개체가 반복되어야 하는지 알아야 한다면 quantifier 문자를 사용해야 한다.
MDN_link
quantifier | description |
---|---|
+ | This means that it will match one or more occurrences of the item |
* | This means that it will match zero or more occurrences of the item |
? | This matches zero or one occurrence of the item |
{N} | It matches the exact N number of occurrences of the specified item |
{N,} | It matches the N or more number of |
{N, M} | It matches any string that contains N number of occurrences, but it can be no more than M |
몇 가지 예를 보면서 quantifier의 쓰임새를 알아보자. 먼저 +
이다.
const myString = `Regular expressions are awesome!`;
const regPattern = /a+/g;
console.log(myString.match(regPattern));
//expected output: a, a, a
myString 변수에서 'a'가 총 3군데(Regular expressions are awesome!) 쓰였기 때문에 'a'를 3번 반환한다.
다음은 *
를 살펴보자.
const regPattern = /a*/g;
//expected output: ,,,,,a,,,,,,,,,,,,,,,a,,,,a,,,,,,,,
output이 조금 이상하게 보일 수 있다. 위의 표에서도 볼 수 있듯 *
는 'a'뿐만 아니라 공백도 포함하여 출력한다. 즉 문자열에 존재하는 모든 문자를 탐색하고 일치하면 해당 문자, 일치하지 않는 위치에는 공백을 반환하는 것이다.
*
나 ?
를 쓸 때 주의해야 할 부분은 일치하는 문자가 하나도 없다면 공백을 반환한다는 점이다. ?
는 *
와 쓰임새가 비슷하다. 예를 들어 be
, bee
, bees
세 가지 전부와 일치하는 regular expression을 짤 때 /be+s?/
와 같이 쓴다.
quantifier는 숫자와 함께 쓸 수도 있다. 다음 예를 보자.
const myString = `Regular expressions are awesome!`;
const regPattern = /s{2}/g;
이 예는 awesome!
의 s
와는 일치하지 않지만 expressions
의 ss
와는 일치한다. 만약 /s{3}
이라고 쓰면 작동하지 않는다. 왜냐하면 변수에 sss
가 쓰인 곳이 한 군데도 없기 때문이다.
quantifier 중 *
와 +
는 greedy
라고 부른다. 이 두 개는 문자열 내에 일치하는 값을 될 수 있는 때까지 찾으려고 한다. 이 quantifier 뒤에 ?
를 붙이면 greedy가 아니게 되고(비탐욕, 非貪欲) 첫 번째로 일치하는 값을 찾자마자 멈춘다.
다음 예를 통해 문자 앞뒤의 공백까지 일치하는지 확인하는 regular expression을 살펴보자.
let longString = `As we know JavaScript is a scripting language that enables you to create
dynamically updating content,
control multimedia, animate images, and pretty much....`;
let regex2 = /\s+JavaScript\s+/g;
console.log(longString.match(regex2));
//expected output:[‘ JavaScript ’]
공백을 탐색하는 \s
와 하나 이상의 값을 찾는 +
를 조합하여 앞뒤에 위치한 공백까지 반환한다.
이때까지 쓰인 중요한 reserved characters를 정리하자.
.
+
*
?
{}
|
()
[]
\
^
$
알고리즘 문제를 풀다가 몇 번인가 마주쳤던 regular expression에 대해서 깊이 있게 공부할 수 있었다. 글을 적다 모르는 부분이 나오면 이해가 될 때까지 공부하다가, 다시 적다가를 반복하다 보니 이 글을 쓰는 데만 5시간이 걸렸다. 다음부터는 책을 읽으면서 동시에 글을 요약하는 느낌으로 작성해야겠다.
책을 읽으면 인터넷으로 깔짝거리며 자료조사하는 것보단 깊이 있는 공부가 가능해서 좋지만, 다른 공부할 것도 많아서 책 정리에만 시간을 너무 많이 쏟을 수는 없을 것 같다.