본문 바로가기
  • 프론트엔드 개발자 세오세오 | Frontend dev Seo
Learn to Code

[JS] 정규 표현식 (Regular Expression, RegExp)

by CEOSEO 2021. 3. 26.
728x90
반응형

Photo by Bala GN on Behance 

 

정규 표현식은 문자열의 집합을 나타내는 패턴을 뜻한다. 정규 표현식을 사용하면 유저가 회원가입 페이지에 정보를 작성할 때, 핸드폰 번호나 이메일 등이 특정 패턴 모양에 맞게 작성이 되었는지를 더욱 쉽게 확인할 수 있다. 같은 작업을 반복문과 조건문을 통해 할 수도 있지만, 그러려면 엄청 긴 코드를 작성해야 하는데, 정규 표현식을 사용하면 더욱 간결하게 해결할 수 있어서 좋다. 아래 내용은 MDN 페이지의 정보를 기반으로 하고 있다(링크).

 

 

정규 표현식의 형태

/pattern/i

정규 표현식의 형태는 크게 패턴(pattern)플래그(flag) 두 영역으로 나뉜다.

시작기호 /종료기호 / 사이에 패턴이 들어가고, 종료기호 뒤에 플래그가 들어간다.

 

 

정규 표현식 만들기

정규 표현식을 만드는 방법은 두가지가 있다. 첫번째는 regular expression literal을 사용하는 방법이고, 두번째는 RegExp 객체의 생성자 함수(the constructor function)을 사용하는 방법이다.

 

// 첫번째 방법
let myRegExp = /ab+c/;

// 두번째 방법
let yourRegExp = new RegExp('ab+c');

 

첫번째 방법은 스크립트를 로드할 때 정규 표현식을 컴파일한다. 만약 정규 표현식의 내용이 일정하게 유지되는 경우라면, 첫번째 방법을 사용할 경우 두번째 방법보다 성능이 향상될 수 있다. 생성자 함수는 정규 표현식의 패턴이 가변적이거나 패턴을 다른 곳에서 받아오는 경우 (사용자 인풋 등) 사용하면 된다.

 

 

 

정규 표현식 사용 방법

정규 표현식을 사용하는 방법은 크게 두가지로 나눌 수 있다. 첫번째는 일반 문자들로 이루어진 간단한 패턴을 넣어서 사용하는 경우이고, 두번째는 특수문자들이 패턴에 함께 추가된 경우이다.

 

- 일반 글자들로만 이루어진 패턴 (예: /abc/ )

- 특수 문자가 포함된 패턴 (예: /Chapter(\d+)\.\d*/ ) 이게 뭔 개ㅅ..ㄹ

 

 (가독성이 심히 떨어진다는 것이 RegExp의 단점이다.)

 

특수문자 포함되지 않은 간단한 패턴

간단한 패턴은 패턴에 나오는 글자를 직접적으로 똬악! 찾고 싶을 때 사용한다. 위에 예제로 사용된 /abc/ 패턴 같은 경우, 문자열 중에서 정확히 'abc'라고 써진 경우가 있는 경우에만 매칭이 된다고 나오게 된다. 이 경우 'ab c'라던가 'a bc'라던가와 같은 문자열은 매칭되지 않는다.

 

그래서 특수 글자들은 도대체 뭐야?

만약 찾고싶은 매칭이 위에 말한 단순하면서 명확한 패턴으로는 찾을 수 없는, 좀더 복잡성을 요구한다면, 특정 역할들을 하는 특수문자들을 패턴 안에 작성하여 응용할 수 있다.

 

MDN 설명에 따르면, 정규 표현식 패턴안에 사용할 수 있는 특수 글자들은 아래 5가지 종류로 구분할 수 있다. 각각 내용이 방대하기 때문에, 유용할 종류들만 우선적으로 언급한다. 자세한 내용은 각 제목의 링크를 따라가면 MDN 페이지에서 찾아볼 수 있다.

 

1. Assertions

^, $, x(?=y), x(?!y), (?<=y)x, (?<!y)x, \b, \B

 

종류 의미
^ ^는 두 가지의 다른 의미가 있다.
1. 그룹의 앞에 쓰일 때
-> 그룹이 뜻하는 것의 반대를 말한다 (NOT)

2. 그룹의 앞이 아닐 때
-> 문장의 가장 앞 글자가 패턴과 일치하는지 본다.
예를 들어 /^A/ 패턴 같은 경우, 'an A'라는 스트링의 'A'와는 매칭이 안되지만, 첫번째 글자가 'A'인 'An A'와는 매칭이 된다.
& 스트링의 마지막을 뜻한다.
예) /t$/ 일 경우, 'take'의 't'는 매칭이 안되지만 'root'의 't'는 맨 뒤에 있기 때문에 매칭이 된다.
x(?=y) x를 찾는 건데, 그냥 x가 아니라 x의 뒤에 y가 있는 경우만 찾는 것이다.

예) 같은 'tasty '지만 snack이 뒤에 나오는 경우만 캐치한다.

let reg = /tasty (?=snack)/;
let sentence = 'tasty vegetable tasty snack tasty soup';
sentence.match(reg);
// ["tasty ", index: 16, input: "tasty vegetable tasty snack tasty soup", groups: undefined]
x(?!y) 위 x(?=y)와 반대의 경우이다. x를 찾는 것은 똑같은데, x 다음에 나오는 애가 y가 아닌 경우만 매칭된다.
(?<=y)x 마찬가지로 x를 찾는데, x의 앞에 y가 올 경우만을 찾는다.
(?<!y)x x를 찾는데, 그 x의 앞에 y가 오지 않을 경우만을 찾는다.

 

 

 

 

2. Character classes

\, ., \cX, \d, \D, \f, \n, \r, \s, \S, \t, \v, \w, \W, \0, \xhh, \uhhhh, \uhhhhh, [\b]

종류 의미
d 모든 아라빅 숫자. [0-9]이라고 적는 것과 같은 의미이다.

예:
let str = 'Do you live with more than 3 dogs?'
let reg = /\d/ 또는 /[0-9]

str.match(reg);
// ["3", index: 27, input: "Do you live with more than 3 dogs?", groups: undefined]
\D 숫자가 아닌 모든 문자. [^0-9]와 같은 의미이다. 

예:
let str = 'Do you live with more than 3 dogs?'
let reg = /\D/g 또는 /[^0-9]/g 

str.match(reg);
["D", "o", " ", "y", "o", "u", " ", "l", "i", "v", "e", " ", "w", "i", "t", "h", " ", "m", "o", "r", "e", " ", "t", "h", "a", "n", " ", " ", "d", "o", "g", "s", "?"]
\w 기본 라틴 알파벳의 모든 문자 및 숫자와 밑줄 (underscore _ ). [A-Za-z0-9_]와 같은 의미이다.

예:
/\w/ 일때 match를 하면:
"apple" => "a"
"$5.28" => "5"
"3D" => "3"
"Émanuel" => "m"
\W 기본 라틴 알파벳의 문자가 아닌 모든 것을 뜻한다. [^A-Za-z0-9_]와 같은 뜻이다. 소문자 \w의 반대다.

예:
/\W/를 match 하면:
"50%" => "%"

 

 

3. Groups and ranges

(x), (?:x), (?<Name>x), x|y, [xyz], [^xyz], \Number

 

종류 의미
x|y x 또는 y랑 매칭된다는 뜻.
예를 들어, /orange|apple/은 'orange color'의 'orange'와도 매칭이 되고 'apple juice'의 'apple'과도 매칭이 된다.
[abcd]
[a-d]
범위 안의 문자열. - 을 써서 중간 내용을 생략할 수 있다. [abcd]는 [a-d]와 같은 의미이다.
그런데 만약 - 하이픈이 맨 앞이나 맨 뒤에 올 경우엔 의미가 달라질 수 있다. 예를들어, [-abcd] 또는 [abcd-]라고 작성한다면 'non-profit'의 '-'과 매칭이 된다. 

 

 

 

 

4. Quantifiers

*, +, ?, x{n}, x{n,}, x{n,m}

 

5. Unicode property escapes

\p{UnicodeProperty}, \P{UnicodeProperty}

 

그런데 만약, 위와 같이 패턴의 정교화가 아닌 특수 문자 그 자체를 문자열에서 찾고 싶은 경우엔 백슬래시 \를 사용해주어야 한다.

// 'a' 뒤에 '*'이 나오고 그 뒤에 'b'가 나오는 패턴을 찾는다.
let regOne = /a\*b/;

// 만약 백슬래시 \를 넣지 않는다면?
let regTwo = /a*b/;

// 이 경우에 regTwo 패턴은 여러개의 'a' 뒤에
// 'b'하나가 나오는 경우를 뜻하게 된다.
// 예: 'aaaaaaaaaabc'

 

( ) 소괄호(Parentheses)

저장 용도로 사용된다. 소괄호 안에 들어오는 패턴의 일부를 나중에 사용하기 위해서 저장한다는 것을 뜻한다,

 

 

 

플래그(Flag)

정규 표현식을 이루는 두 영역, 패턴과 플래그 중 하나인 플래그이다. 플래그에는 총 6가지 종류가 있다. 이중 가장 많이 사용되는 것은 위에 3개인 i, g, 그리고 m 이다.

i 대소문자를 구분하지 않고 패턴을 검색한다.
g 패턴과 일치하는 모든 것들을 전역 검색한다. (예: match()와 함께)
m 다중행(multi-line) 모드를 활성화한다. 즉, 문자열의 행이 바뀌더라도 패턴 검색을 지속한다.
s Dotall 모드를 활성회시킨다. Dotall 모드는 마침표 . 가 새로운 줄을 시작하는 \n 도 포함되게 한다.
u 유니코드 전체를 지원한다.
y 특정 위치에서 패턴 매칭을 검색하는 sticky 모드를 활성화시킨다.

출처: javascript.info/regexp-introduction

 

 

Regexp와 사용하는 메소드

RegExp 메소드

exec()

- 인수로 전달받은 문자열에 대해 패턴을 검색하여 매칭 결과를 배열로 리턴한다. 다만, 여러개의 매칭 결과가 있어도 항상 가장 앞에 있는 첫번째 매칭 결과만 리턴하기 때문에 한 개 이상의 매칭 결과를 찾아야할 경우엔 별로 좋지 않다.

 

let str = 'I want you to find me.';
let reg = /me/;

reg.exec(str);
// ["me", index: 19, input: "I want you to find me.", groups: undefined]

 

 

 

test()

- 문자열에 매칭되는 패턴이 있는지 테스트한다. 값은 true / false로 나온다.

let heart = 'You are in my heart';
let reg = /You/;

reg.test(heart); // 삐빅! 진실입니다

 

 

 

함께 사용되는 String 메소드

match()

- 패턴과의 매칭 결과를 배열로 리턴한다. exec()도 동일하게 작동하지만, /패턴/g 와 같이 뒤에 플래그로 g (=global)을 붙어주었을 때 매칭되는 모든 결과물들을 배열에 집어넣는다.

 

let sentence = 'Thirty, thirty, thirty-years-old! You are thrity-years-old!';
let reg = /thirty/;

sentence.match(reg); // ["thirty", index: 8, input: "Thirty, thirty, thirty-years-old! You are thrity-years-old!", groups: undefined]


// g 플래그 사용했을 경우
let reg = /thirty/g;
sentence.match(reg); // ["thirty", "thirty"]

 

replace()

- 패턴과 매칭되는 일부 또는 모든 부분들을 대체해서 새로운 문자열을 리턴한다. 첫번째 인자로 regexp가 들어가고, 두번째 인자로 그 매칭되는 부분을 대체할 새로운 문자열 (newSubstr) 또는 그 새로운 문자열을 만들어낼 함수가 들어간다.

 

let newStr = str.replace(regexp, newSubstr or function)

 

 

- 원본 문자열을 수정하지 않는다 (immutability).

 

let str = 'I love apples.';
let newStr = str.replace(/apples/, 'oranges')

console.log(newStr); // 'I love oranges.';
console.log(str); // 'I love apples.';

 

 

매칭되는 모든 패턴의 경우를 바꾸고(replace) 싶다면, g 플래그를 함께 사용해야 한다.

 

let str = `I don't like soju. I don't think I like the smell of it.`;
let reg = /don't /g;
let newStr = str.replace(reg, '');

console.log(newStr);
//`I like soju. I think I like the smell of it.`

 

 

그외 정규 표현식과 함께 사용되는 String 메소드로는 다음 네가지가 있다: matchAll(), replaceAll(), search(), split()





+ 몇가지 예제

 

1. 카카오 코딩테스트의 한 부분으로 나왔던 문제이다. 요구 사항은 주어진 문자열에서 알파벳 소문자, 모든 숫자- (빼기) , _ (언더바), 그리고 . 을 제외한 문자가 사용되었을 경우 제거해야 한다는 것이다. 이와 같은 문제를 풀 때 정규 표현식을 사용하면 간단하게 처리가 가능하다. str이 주어진 문자일 경우에 다음과 같이 작성한다. 이것을 하나하나 파해쳐보자!

str.replace( /[^\w-_.]/g,  '' )

replace( 인자1, 인자2 )

이건 그냥 자바스크립트의 스트링 메소드이다. (MDN 링크) 메소드를 호출한 문자열(str)에서 인자1의 모양을 인자2로 대체하는(replace) 메소드이다. 인자1의 자리엔 정규 표현식이나 일반 문자열을 넣으며, 인자2에는 대신 집어넣고 싶은 문자열을 넣는다. 이번 예제 같은 경우엔, 정규 표현식 패턴이 인자1에 들어갔고, 인자2엔 빈 스트링 ''이 들어갔다. (왜냐면 패턴에 해당되는 놈이 있을 경우 그놈을 제거하는 것이 목적이기 때문이다!)

 

/   /

정규 표현식의 패턴의 영역을 표시해준다. 요 짝대기들 안에 있는 내용이 패턴이다.

 

g

플래그(flag)이다. 주어진 문자열 str 전체를 대상으로 패턴에 부합하는 놈이 있는지 찾기 위해 붙인다.

 

[    ]

그룹으로 묶어주는 것이다. str의 문자 중 [이 안에 있는 것들]에 해당되는 애가 있으면 패턴에 걸린다.

 

^

[ 그룹 ] 안에서 사용되었기 때문에, not의 의미를 가진다. 즉 이 패턴은 "이 [ ] 그룹 안에 있는 게 아닌 문자열을 찾아라"가 된다. not의 의미를 가진 ^는 바로 뒤에 있는 \w 에만 적용되는 것이 아니라, [ ] 이 그룹 안에 묶인 전체에게 적용된다.

 

\w

기본 라틴 알파벳의 모든 문자, 숫자와 밑줄( _ )을 의미한다.

 

-_.

보여지는 것 그대로이다. - 와 _ 와 . 이다.



즉, str.replace(/[^\w-_.]/g,  '' )는 " [ 그룹 ] 안에 ^가 있으니, [ 이 안]에서 말하는 것들에 해당되지 않는 (^ 때문에) 문자열이 str에 있다면, 그걸 '' (빈 스트링)으로 대체하라"는 뜻이 된다.

 

 

2. 카카오 코딩 테스트 같은 문제에서 요구된 다른 조건이다. 문자열의 맨 앞 또는 맨 뒤에 . 이 왔을 경우 제거하라는 지령(?)이다. 이 경우엔 아래와 같이 작성할 수 있다:

 

str.replace( /^\.|\.$/, '' )

 

/   /

위와 동일하다. 정규 표현식의 패턴의 영역을 표시해준다. 요 짝대기들 안에 있는 내용이 패턴이다.

 

^

이번 정규표현식에서 사용된 ^는 위에서 사용한 것과 다르다. 위에선 [ 그룹 ] 안에 ^를 사용했지만, 이번엔 [ 그룹 ] 이 아니라 밖에 나와있다. 이럴 경우엔, not이 아니라 '맨 앞글자'를 의미하게 된다. 여기서는, 뒤에 나오는 \. (역슬래시\와 쩜.) 이 맨 앞글자에 오는 경우를 뜻한다.

 

\.

여기서 역슬래시 \는 패턴을 의미하지 않는다. 여기서는, 뒤에 나오는 . 이 정규표현식에서 사용되는 .의 의미가 아니라 그냥 문자 그대로 . 인 걸 나타내기 위에 그 앞에 \를 붙인 것이다. 즉, ^\. 은 str의 '맨 앞 글자가 . 으로 시작하는 경우'를 뜻한다.

 

|

or을 뜻한다. 즉, "앞에 나오는 ^\. 또는 뒤에 나오는 .$ "을 뜻한다. 

 

$

이건 ^의 반대이다. '맨 뒷글자'를 뜻한다. 여기서는, \.$ 라고쓰였는데, 이건 '맨 뒷글자가 마침표 . 인 경우'를 뜻한다.

 

 

728x90
반응형

댓글