Programming/JavaScript

예제로 정리한 정규식 패턴

고고마코드 2022. 8. 9. 15:40
반응형

예제 코드는 Javascript로 작성되었습니다.
예제 코드에 플래그 활용이 포함됩니다. 플래그를 모른다면 아래 글을 참고하세요.

정규 표현식/정규식(RegExp) 플래그(Flag) 자세하게 알아보자!


1. 기본

dot(.) : 줄바꿈(\n)을 제외한 모든 문자(특수문자 포함)

var test = `1a
2b
@#`;

test.match(/./g)
[ '1', 'a', '2', 'b', '@', '#' ]

\d:모든 숫자 \D:숫자가 아닌 것(\d의 반대)

var test = `1a
2b
@#`;

test.match(/\d/g)
test.match(/\D/g)
[ '1', '2' ]
[ 'a', '\n', 'b', '\n', '@', '#' ]

\s:공백(space) \S:공백이 아닌 것(\s의 반대)**

var test = '1 a 2 b 3 c';

cl(test.match(/\s/g));
cl(test.match(/\S/g));
[ ' ', ' ', ' ', ' ', ' ' ]
[ '1', 'a', '2', 'b', '3', 'c' ]

\w:단어(문자+숫자) \W:단어가 아닌 것(\w의 반대)

var test = '1 !a@☆3B';

cl(test.match(/\w/g));
cl(test.match(/\W/g));
 [ '1', 'a', '3', 'B' ]
 [ ' ', '!', '@', '☆' ]

\t:탭

var test = '1    a    #'; // 각 문자와 숫자 사이의 공백은 탭(\t) 입니다.

cl(test.match(/\t/g));
[ '\t', '\t' ]

\n:줄바꿈 \특수문자:지정한 특수문자

var test = `1a
2b
@#`;

test.match(/\n/g)
test.match(/\@/g)
[ '\n', '\n' ]
[ '@' ]

\x[ASCII]:16진수 \0[ASCII]:8진수 (숫자 0입니다)

var test = `a!b@`;

test.match(/\x21/g) // \x21 : !
test.match(/\041/g) // \041 : !
[ '!' ]
[ '!' ]

\u:유니코드

var test = `a!b@`;

test.match(/\u0021/g)
[ '!' ]

그 외

  • \c : 제어 문자 (Control)

  • \f : Form Feed (\u000C)

  • \r : Carriage Return (\u000D)


2. 단어 경계

\b:단어 경계 (잘 쓰면 은근히 편함)

var test = `javascript java,java!java|javac`;

test.match(/\bjava\b/g)
[ 'java', 'java', 'java' ]

단어의 경계가 될 수 있는 것은 \W 에 일치하는 것 (위의 \W 참고)

지정한 단어가 정확히 일치해야 합니다.

위 예제에서는 { , ,, !, OR(|) } 4개의 경계가 있고

'javascript'와 'javac'는 'java'와 정확히 일치하지 않으므로 대상이 아닙니다.


3. OR(|)

|:OR (여러개의 패턴을 함께 쓸 때 사용)

var test = `abcdefg`;

test.match(/a|d/g)
[ 'a', 'd' ]

4. 앵커

^:문자열의 시작 $:문자열의 끝

var test = `abcdefg`;

test.match(/^\w|\w$/g)
[ 'a', 'g' ]

문자열의 시작과 끝을 가져오기

\w:모든 문자


5. 세트 및 범위

세트 [ ]:대괄호 내에 들어있는 문자가 세트가 됩니다.

var test = `a1b2c1d3`;

test.match(/[abcde]1/g)
[ 'a1', 'c1' ]

a,b,c,d,e 는 세트가 되었고 해당 세트 다음에 1이 나오는 문자열이 정규식 패턴입니다.

a1, c1은 [abcde] 세트 뒤에 1이 나왔으므로 패턴에 일치하지만

b2, d3는 패턴에 일치하지 않습니다.

세트는 주로 범위와 함께 쓰입니다.

범위 -:특정 범위를 지정할 수 있습니다.

var test = `a1b2c1d3E4F5`;

test.match(/[a-z]/g)
test.match(/[0-9]/g)
[ 'a', 'b', 'c', 'd' ]
[ '1', '2', '1', '3', '4', '5' ]

[a-z]는 알파벳 소문자 a~z를 뜻합니다.

그러므로 a,b,c,d는 패턴에 일치하지만 그 외 숫자들과 알파벳 대문자(E, F)는 패턴에 일치하지 않습니다.

[0-9]는 숫자 0~9까지를 뜻합니다.

그러므로 숫자는 모두 패턴에 일치합니다.

범위 제외 ^:특정 범위를 제외할 수 있습니다.

var test = `a1b2c1d3E4F5`;

test.match(/[^a-z]/g)
test.match(/[^0-9]/g)
[ '1', '2', '1', '3', 'E', '4', 'F', '5' ]
[ 'a', 'b', 'c', 'd', 'E', 'F' ]

위 범위 지정한 예제에서 정규식 패턴에 ^ 만 추가했습니다.

^ 는 단독으로 쓰이면 문자열의 시작을의미하는 앵커로 쓰이지만, 범위[ ] 와 함께 쓰이면 부정의 의미로 쓰입니다.


7. 수량

{ }:수량을 중괄호로 표현할 수 있습니다.

{3}:수량이 3개인 패턴을 찾아라.

var test = `1,23,456,7890`;
test.match(/\d{3}/g)

var test2 = `a,aa,aaa,aaaa,aaaaa`;
test2.match(/a{3}/g)
[ '456', '789' ]
[ 'aaa', 'aaa', 'aaa' ]

\d{3} 은 숫자의 수량이 3개인 패턴을 찾는 것입니다.

'1', '23' 은 수량이 3미만이라 패턴에 일치하지 않습니다.

'456', '7890'은 각각 수량이 3, 4 이므로 패턴에 일치하지만 정규식은 {3}으로 정확히 3개의 수량만 찾는 것이므로 '456', '789' 만 일치하는 패턴이 됩니다.

'aaa' 역시 수량이 3개인 패턴은 'aaa', 'aaaa', 'aaaaa' 를 찾을 수 있습니다. 그러나 정확히 3개의 수량만 찾는 것이므로 일치하는 패턴은 'aaa', 'aaa', 'aaa' 입니다.

그렇다면 3개 이상은 어떻게 찾을 수 있을까요?

{3,}:수량이 3개 이상인 패턴을 찾아라.

var test = `1,23,456,7890`;
test.match(/\d{3,}/g)

var test2 = `a,aa,aaa,aaaa,aaaaa`;
test2.match(/a{3,}/g)
[ '456', '7890' ]
[ 'aaa', 'aaaa', 'aaaaa' ]

위의 정확히 일치하는 수량 패턴과 다른 점은 반점(,)이 추가되었습니다.

결과도 [ '456', '789' ] 에서 [ '456', '7890' ] 으로 바뀌었습니다. 3개 이상의 패턴을 모두 찾기 때문입니다.

{3, 4}:수량이 3~4개인 패턴을 찾아라

var test2 = `a,aa,aaa,aaaa,aaaaa`;

test2.match(/a{3,4}/g)
[ 'aaa', 'aaaa', 'aaaa' ]

수량이 3개 이상인 'a'를 모두 찾지만 {3, 4} 이므로 4개까지만 일치하는 패턴을 찾습니다.

그 결과로 'aaaaa' 는 'aaaa'로 반환되었습니다.

그렇다면 수량이 3~4개인 패턴을 찾을 건데 4개가 초과하는 것은 아예 검색 대상에서 제외하는 방법은 무엇일까요?

예를 들어 'aaa', 'aaaa' 만 가져오고 싶은데 'aaaaa'가 'aaaa'로 반환되니까 이게 싫은 경우입니다.

경계 \b와 함께 활용

var test = `a,aa,aaa,aaaa,aaaaa`;

test.match(/a{3,4}/g)
test.match(/\ba{3,4}\b/g)
[ 'aaa', 'aaaa', 'aaaa' ]
[ 'aaa', 'aaaa' ]

위에서 언급한 경계(\b)를 활용하면 됩니다.

보다시피 수량이 3~4개인 패턴만 일치하는 것으로 나타납니다.

?:0개 또는 1개

var test = `a,ab,abb,ac,cb`;

test.match(/\bab?\b/g)
[ 'a', 'ab' ]

경계가 있어 헷갈릴 수 있지만 경계를 제외한 정규식은 ab? 입니다.

그대로 해석하면 'a' 뒤에 'b'가 0개 또는 1개인 패턴입니다. 'a' 뒤에 'b'가 오지 않아도 되지만, 'b' 대신 다른 문자가 있으면 안 됩니다.

'a' 는 'a'뒤에 'b'가 없고 다른 문자도 없으므로 패턴에 일치

'ab' 는 'a'뒤에 'b'가 1개이므로 패턴에 일치

'abb'는 'a'뒤에 'b'가 2개이므로 패턴에 불일치

'ac'는 'a'뒤에 'c'가 있으므로 패턴에 불일치, 'b'가 없는 건 괜찮지만 'b'가 아닌 다른 것이 있으면 안 됩니다.

'cb'는 'a'가 없으므로 패턴에 불일치

*:0개 이상

var test = `a,ab,abb,ac,cb`;

test.match(/\bab*\b/g)
[ 'a', 'ab', 'abb' ]

'a' 뒤에 'b'가 0개 이상인 패턴을 찾습니다. 'a'뒤에 'b'가 오지 않아도 되지만, 'b' 대신 다른 문자가 있으면 안 됩니다.

'a'는 'a' 뒤에 'b'가 없고 다른 문자도 없으므로 패턴에 일치

'ab'는 'a' 뒤에 'b'가 0개 이상이므로 패턴에 일치

'abb'는 'a' 뒤에 'b'가 0개 이상이므로 패턴에 일치

'ac'는 'a' 뒤에 'b'가 아닌 'c'가 있으므로 패턴에 불일치

'cb'는 'a'가 없으므로 패턴에 불일치

+:1개 이상

var test = `a,ab,abb,ac,cb`;

test.match(/\bab+\b/g)
[ 'ab', 'abb' ]

위의 0개 이상(*)과 비교하면 차이를 알 수 있습니다.

'a'가 빠졌는데 'a' 뒤에 'b'가 1개 이상 있어야 하는데 1개도 없기 때문에 불일치입니다.


7. 탐욕적 수량자와 게으른 수량자

위에서 알아본 수량자를 사용할 때 기본적인 탐색 기준은 탐욕적 수량자입니다.

탐욕적이란 모든 문자열에 대해서 최대한 많은 요소를 찾으려고 하는 것입니다.

게으른 수량자는 탐욕적 수량자의 반대로, 최대한 적은 요소를 찾는 것입니다.

욕심이 없는 수량자라고 보면 됩니다. 최소한의 조건만 갖추면 됩니다.

게으른 수량자로 탐색하려면 수량자 뒤에 ? 를 붙이면 됩니다. 모든 수량자에 쓸 수 있습니다.

탐욕적 vs 게으른

var test = `123 456 7 89`;

test.match(/\d+/g) // 탐욕적 수량자
test.match(/\d+?/g) // 게으른 수량자
[ '123', '456', '7', '89' ]
[ '1', '2', '3', '4', '5', '6', '7', '8', '9' ]

탐욕적, 게으른 모두 1개 이상의 숫자를 찾는 정규식 패턴입니다.

'123' 은 탐욕적 수량자인 경우 1개 이상인 숫자를 찾는 것이므로 제일 많이 가져가고 싶으니 숫자가 끊기는 부분까지 '123'을 모두 가져옵니다.

게으른 수량자의 경우 제일 적게 가져가고 싶으니 숫자가 1개라도 있으면 그냥 바로바로 가져오면 됩니다.


8. 그룹 캡처

그룹 캡처는 소괄호()로 표현합니다.

그룹 캡처 이해하기 (일반)

var test = `hahaha`;

test.match(/ha+/g)
[ 'ha', 'ha', 'ha' ]

ha+ 는 'h'뒤에 'a'가 1개 이상 있는 패턴을 찾습니다.

그런데 원하는 것은 'ha' 가 1개 이상 있는 패턴을 찾고 싶습니다.

그룹 캡처 이해하기 (그룹화)

var test = `hahaha`;

test.match(/(ha)+/g)
[ 'hahaha' ]

'ha' 를 소괄호로 묶어주면 하나의 그룹이 됩니다.

그룹으로 만들면 'ha' 가 1개 이상 있는 패턴을 찾게 되는 것이죠.

기본적으로 탐욕적 수량자이므로 패턴에 일치하는 만큼 최대한 많이 가져옵니다.

위에서 언급한 게으른 수량자를 활용하면 아래와 같은 결과도 만들 수 있습니다.

그룹 캡처 이해하기 (게으른 수량자 활용)

var test = `hahaha`;

test.match(/(ha)+?/g)
[ 'ha', 'ha', 'ha' ]

응용하기 (각 도메인이 행으로 구분되어 있는 문자열을 배열로 반환받기)

var test = `
gmail.com
naver.com
daum.net
`;

test.match(/(\w)+\.{1}(\w)+/gm)
[ 'gmail.com', 'naver.com', 'daum.net' ]

(\w)+ : 문자 1개 이상

\.{1} : dot(.) 1개

(\w)+ : 문자 1개 이상

응용하기 (핸드폰 번호 양식 체크)

최근 핸드폰 양식에 맞게 식별번호는 '010' 가운데는 4자리로 판단했습니다.

var test = `01012345678`;
var test2 = `010-1234-5678`;

var regExp = /^(010)-?(\d{4})-?(\d{4})$/g;

regExp.test(test) // true
regExp.lastIndex = 0;
regExp.test(test2) // false

^(010) : 시작은 '010' 으로 시작해야 합니다.

-? : '-' 는 있어도 되고 없어도 됩니다. (단, 있을 경우 1개만 있어야 합니다.)

(\d{4}) : 숫자 4개가 있어야 합니다.

(\d{4})$ : 마지막은 숫자 4개로 끝나야 합니다.

  • regExp.lastIndex
    test()가 true가 false를 반환할 때는 lastIndex가 0으로 초기화 되지만 true를 반환했다면 lastIndex는 초기화되지 않습니다. 그러므로 수동으로 초기화를 해 주었습니다.
    아래 설명을 참고하세요.

응용하기 (이메일 양식 체크)

var test = `test.js@site.com`;
var test2 = `test.js@site.co.kr`;
var test3 = `test-js@sitecom`;

var regExp = /^\w([-_\.]|\w)*\@\w([-_\.]|\w)*(\.\w+)$/g

regExp.test(test); // true
regExp.lastIndex = 0;
regExp.test(test2); // true
regExp.lastIndex = 0;
regExp.test(test3); // false

^\w : 시작은 문자가 와야 한다.

([-_\.]|\w)* : 특수문자 -_. 와 문자가 있을 수 있다. (없어도 됨)

\@\w : 문자 @가 있어야 하며 @뒤에는 문자가 와야 한다.

(\.\w+)$ : dot(.)으로 시작되는 문자가 마지막에 와야 한다.

정규식을 제대로 알기 전에는 복잡해 보였지만 알고나니 이 정도 수준은 쉽게 작성할 수 있게 되었습니다.


9. 패턴의 역참조

그룹 캡처로 패턴을 그룹으로 만드는 방법을 알았습니다.

이렇게 그룹으로 만든 패턴의 전체를 가져오는 것이 아니라, 그룹된 패턴만 가져올 수 있습니다.

이것을 그룹된 패턴을 역으로 참조한다고 해서 '패턴의 역참조' 라고 합니다.

응용하기 (핸드폰 번호 양식 체크)

var test = `010-1234-5678`;

var regExp = /^(010)-?(\d{4})-?(\d{4})$/g;

위에서 핸드폰 번호 양식 체크하는 방법을 응용해 보았습니다.

저는 이 핸드폰 번호의 가운데만 가져오고 싶어요.

역참조를 통해 그룹화된 패턴을 선택해 활용할 수 있습니다.

응용하기 - 치환 (핸드폰 번호 가운데 가져오기)

var test = `010-1234-5678`;

var regExp = /^(010)-?(\d{4})-?(\d{4})$/g;

test.replace(regExp, "$2") // 1234

여기서 ($패턴의숫자) 처럼 사용하면 해당 패턴에 일치하는 부분을 가져올 수 있습니다.

$1 = (010)

$2 = (\d{4})

$3 = (\d{4})

주의할 점은 그룹 캡처로 되어 있는 부분만 가져오므로 역참조를 사용하기 위해서는 그룹화를 잘 시키는 것이 중요합니다.

두 번째 사용법은 이미 사용한 패턴의 역참조입니다.

핸드폰 번호 양식 체크로 한 번 더 예를 들어보겠습니다.

응용하기 (핸드폰 번호 양식 체크)

var test = `010-1234-1234`;

var regExp = /^(010)-?(\d{4})-?(\d{4})$/g;

예시로 확인하고 싶은 것은 핸드폰 번호 가운데와 뒷자리가 똑같은지 확인을 해 보고 싶습니다.

번호별 역참조

var test = `010-1234-5678`;
var test2 = `010-1234-1234`;

var regExp = /^(010)-?(\d{4})-?\2$/g;

test.match(regExp) // null
test2.match(regExp) // [ '010-1234-1234' ]

기존 정규식 패턴에서 맨 뒤에 있던 (\d{4})$\2$ 로 변경했습니다.

이것은 앞의 그룹화된 패턴을 참조하겠다는 의미인데

\1 : (010)

\2 : (\d{4})

를 의미합니다.

주의할 점은 패턴 자체를 참조하는 것이 아니라 패턴의 값을 참조한다는 점입니다.

(\d{4}) 에 해당되는 문자열 '1234' 와 동일한 문자열을 찾는 것이지 동일한 패턴을 찾는 것이 아닙니다.

괄호가 많아져 복잡해지는 것을 방지하기 위해 이름을 지정해 역참조를 할 수 있습니다.

이름별 역참조

var test = `010-1234-5678`;
var test2 = `010-1234-1234`;

// var regExp = /^(010)-?(\d{4})-?\2$/g;
var regExp = /^(010)-?(?<mid>\d{4})-?\k<mid>$/g;

test.match(regExp) // null
test2.match(regExp) // [ '010-1234-1234' ]

더 복잡해 보이지만, 괄호가 많아져서 번호별 역참조를 하면 해석이 어려운 경우 직관성이 있어 유용합니다.

?<name> : 참조할 패턴의 이름 지정

\k<name> : 역참조할 패턴의 이름


10. 전후방 탐색

전후방 탐색은 정규식의 핵심이라고 생각합니다.

기본적인 개념은 특정 패턴 전후로 탐색을 가능하게 한다는 개념입니다.

바로 전방탐색부터 살펴보겠습니다.

전방 탐색

(?=) : 특정 패턴을 기준으로 이전을 탐색합니다.

전방 탐색 알아보기 (적용 전)

var test = `사탕 1개 가격은 500원 입니다.`;

var regExp = /\d+(원)/g;
test.match(regExp) // [ '500원' ]

저는 사탕의 가격이 궁금합니다.

근데 '원'은 빼고 가격만 가져오고 싶어요. 그럼 (원)을 빼면 될까요?

(원)을 빼면 [ '1', '500' ] 이라는 값이 나올 거예요. 정수를 다 찾으니까요.

이럴 때 사용하는 것이 전방탐색입니다.

전방 탐색 알아보기 (적용 후)

var test = `사탕 1개 가격은 500원 입니다.`;

var regExp = /\d+(?=원)/g;
test.match(regExp) // [ '500' ]

'원' 이라는 패턴을 찾아서 그 앞에 있는 숫자를 찾는 정규식입니다.

전후방탐색 모두 일반 패턴을 사용하는 것과 개념은 같지만 찾은 패턴을 반환 시 포함하지 않는다는 점이 핵심입니다.

즉, '500원' 을 찾는 것은 같지만 전방탐색 시 사용한 '원' 은 반환 시 포함하지 않습니다.

전방 탐색 부정

(?!) : 특정 패턴이 없다는 기준으로 이전을 탐색합니다.

이번엔 사탕의 개수를 구하고 싶습니다.

전방 탐색 부정 알아보기 (1)

var test = `사탕 1개 가격은 500원 입니다.`;

var regExp = /\d+(?!원)/g;
test.match(regExp) // [ '1', '50' ]

전방 탐색 부정만 하면 될 줄 알았는데 숫자 중에서 '원' 바로 앞에 있는 '0' 만 빠졌네요.

금액은 한 자리 숫자가 아니므로 금액이 아니라는 경계를 주어야 하네요.

전방 탐색 부정 알아보기 (2)

var test = `사탕 1개 가격은 500원 입니다.`;

var regExp = /\d+\b(?!원)/g;
test.match(regExp) // [ '1' ]

후방 탐색

(?<=) : 특정 패턴이 없다는 기준으로 이후를 탐색합니다.

방향만 다르지, 전방 탐색과 사용 방법은 비슷해요

후방 탐색 알아보기 (예시 설명)

var test = `
<h1>hello</h1>
<div class="container">
  <a href="test.com"></a>
</div>
`;

위와 같은 HTML 태그가 있습니다.

저는 여기서 a 태그의 주소 (test.com) 만 가져오고 싶습니다.

후방 탐색 알아보기 (예시 적용)

var test = `
<h1>hello</h1>
<div class="container">
  <a href="test.com"></a>
</div>
`;

var regExp = /(?<=href=")(.*)(?=")/gs;
test.match(regExp) // [ 'test.com' ]

후방 탐색 부정

(?<!) : 특정 패턴이 없다는 기준으로 이후를 탐색합니다.

후방 탐색 부정 알아보기 (예시 설명)

var test = `1 -2 3 -4 5 -6`;

음수가 아닌 값을 찾고 싶습니다.

후방 탐색 부정 알아보기 (예시 적용)

var test = `1 -2 3 -4 5 -6`;

var regExp = /(?<!-)\d+/g;
test.match(regExp) // [ '1', '3', '5' ]

(?<!-)\d+ : - 뒤에 정수가 오지 않는 패턴


11. 참고 자료

  1. JAVASCRIPT.INFO

반응형