Skip to main content

7.정규 표현식

연습사이트 : https://regex101.com/

정규식을 이용하면 특정 문자열 패턴과 일치하는지 알 수 있다.

  • 글로벌 모드를 통해서 특정문자열과 일치하는 문자열들을 추출 가능.
  • 시작과 끝 엥커조건을 이용해서 문자열이 온전히 특정 패턴인지 검사 가능

1.패턴과 플래그

목적 : 문자 검색, 문자 교체, 문자 유효성 검사
구성 : pattern + flag

// (동적) 패턴에 문자열 템플릿을 사용 가능.
regexp = new RegExp("pattern", "flags");

// (정적) 아래 문법에서는, 정적인 패턴만 가능하다.
regexp = /pattern/; // 플래그가 없음
regexp = /pattern/gmi; // 플래그 g, m, i가

플래그

i

  • i 플래그가 붙으면 대·소문자 구분 없이 검색합니다. 따라서 A와 a에 차이가 없습니다.
    g
  • g 플래그가 붙으면 패턴과 일치하는 모든 것들을 찾습니다.
  • g 플래그가 없으면 패턴과 일치하는 첫 번째 결과만 반환됩니다.

m

  • 다중 행 모드(multiline mode)를 활성화합니다.
  • 자세한 내용은 앵커 ^와 $의 여러 행 모드, 'm' 플래그에서 다룰 예정입니다.

s

  • .이 개행 문자 \n도 포함하도록 ‘dotall’ 모드를 활성화합니다.

u

  • 유니코드 전체를 지원합니다. 이 플래그를 사용하면 서로게이트 쌍(surrogate pair)을 올바르게 처리할 수 있습니다.
  • 자세한 내용은 유니코드: 'u' 플래그와 \p{...} 클래스에서 다룰 예정입니다.

y

  • 문자 내 특정 위치에서 검색을 진행하는 ‘sticky’ 모드를 활성화 시킵니다.
  • 자세한 내용은 Sticky flag "y", searching at position에서 다룰 예정입니다.

str.match로 검색

일치하는 문자열의 index를 찾을때 유용하게 사용된다.

  • 1.g플래그 없다면, 매칭되는 첫번째 문자만 리턴한다.
    • ( Named groups 을 사용할때는 groups속성을 리턴해야 하므로 g플래그를 사용하면 안된다. )
  • 2.g플래그를 이용해서 모두 선택하는 경우(match 함수 리턴은 단순 배열)
  • 3.검색결과 없다면 null
let str = "We will, we will rock you";
str.match(/we/gi) // We,we (패턴과 일치하는 부분 문자열 두 개를 담은 배열)

---
let str = "We will, we will rock you";
let result = str.match(/we/i); // 플래그 g 없음
alert( result[0] ); // We (패턴에 일치하는 첫 번째 부분 문자열)
alert( result.length ); // 1
alert( result.index ); // 0 (부분 문자열의 위치)
alert( result.input ); // We will, we will rock you (원본 문자열)


str.replace로 치환

  • 1.g플래그가 없는 경우 패턴에 일치하는 첫 문자열부분만 교체 된다.
  • 2.g플래그가 있는 경우 패턴데 일치하는 모든 문자열이 교체 된다.
  • 3.패턴과 일치하는 부분문자열은 $& 이라고 지칭한다.
// 플래그 g 없음
alert( "We will, we will".replace(/we/i, "I") );
// I will, we will
- 대소문자 구분없으니 첫번째 We선택되며, g플래그가 없으니 이는 I로 변경되고 끝.
// 플래그 g 있음
alert( "We will, we will".replace(/we/ig, "I") );
// I will, I will

//$&를 사용한 예시를 살펴봅시다.
alert( "I love HTML".replace(/HTML/, "$& and JavaScript") );
// I love HTML and JavaScript

regexp.test로 일치 여부 검사

let str = "I love JavaScript";
let regexp = /LOVE/i;

alert( regexp.test(str) ); // true

2.문자 클래스

문자클래스 DSW (digit, space, word)

자주 사용하는 문자 클래스에는 다음 클래스가 있습니다.

  • \d ('digit(숫자)'의 ‘d’) : 숫자: 0에서 9 사이의 문자
  • \s ('space(공백)'의 ‘s’) : 스페이스, 탭(\t), 줄 바꿈(\n)을 비롯하여 아주 드물게 쓰이는 \v, \f, \r 을 포함하는 공백 기호
  • \w ('word(단어)'의 ‘w’) : ‘단어에 들어가는’ 문자로 라틴 문자나 숫자, 밑줄 _을 포함합니다. 키릴 문자나 힌디 문자같은 비 라틴 문자는 \w에 포함되지 않습니다.
let str = "+7(903)-123-45-67";
let regexp = /\d/;
alert( str.match(regexp) ); // 7
---
let str = "+7(903)-123-45-67";
let regexp = /\d/g;
alert( str.match(regexp) ); // 일치하는 문자의 배열: 7,9,0,3,1,2,3,4,5,6,7
// 이 배열로 숫자만 있는 전화번호를 만듭시다.
alert( str.match(regexp).join('') ); // 79035419441
---
let str = "Is there CSS4?";
let regexp = /CSS\d/;

alert( str.match(regexp) ); // CSS4
---
alert( "I love HTML5!".match(/\s\w\w\w\w\d/) ); // ' HTML5'

반대 클래스

'반대’란 다음 예시들처럼 해당 클래스 제외한 모든 문자에 일치한다는 뜻입니다.

  • \D 숫자가 아닌 문자: \d와 일치하지 않는 일반 글자 등의 모든 문자
  • \S 공백이 아닌 문자: \s와 일치하지 않는 일반 글자 등의 모든 문자
  • \W 단어에 들어가지 않는 문자: \w와 일치하지 않는 비 라틴 문자나 공백 등의 모든 문자
let str = "+7(903)-123-45-67";
alert( str.match(/\d/g).join('') ); // 79031234567
---
let str = "+7(903)-123-45-67";
alert( str.replace(/\D/g, "") ); // 79031234567

점은 '아무 문자’에나 일치

  • 기본적으로는 점은 줄 바꿈 문자 \n와는 일치하지 않습니다.
alert( "Z".match(/./) ); // Z
let regexp = /CS.4/;
alert( "CSS4".match(regexp) ); // CSS4
alert( "CS-4".match(regexp) ); // CS-4
alert( "CS 4".match(regexp) ); // CS 4 (공백도 문자예요.)
alert( "CS4".match(/CS.4/) ); // null, 점과 일치하는 문자가 없기 때문에 일치 결과가 없습니다.

‘s’ 플래그와 점을 사용해 정말로 모든 문자 찾기

  • 줄바꿈 문자는 특별하게 취급되어 점으로 일치시킬 수 없다.
alert( "A\nB".match(/A.B/) ); // null (일치하지 않음)
alert( "A\nB".match(/A.B/s) ); // A\nB (일치!)
//어느 브라우저에서나 쓸 수 있는 대안
alert( "A\nB".match(/A[\s\S]B/) ); // A\nB (일치!)
alert( "1 - 5".match(/\d-\d/) ); // null, 일치 결과 없음!
alert( "1 - 5".match(/\d - \d/) ); // 1 - 5, 이제 제대로 되네요.
// \s 클래스를 사용해도 됩니다.
alert( "1 - 5".match(/\d\s-\s\d/) ); // 1 - 5, 이것도 됩니다.

요약 정리

문자 클래스에는 다음 클래스들이 있습니다.
\d – 숫자
\D – 숫자가 아닌 문자
\s – 스페이스, 탭, 줄 바꿈 문자
\S – \s를 제외한 모든 문자
\w – 라틴 문자, 숫자, 밑줄 '_'
\W – \w를 제외한 모든 문자
. – 정규 표현식 's' 플래그가 있으면 모든 문자, 없으면 줄 바꿈 \n을 제외한 모든 문자

3.유니코드: 'u' 플래그와 \p{...} 클래스

자바스크립트는 문자열에 유니코드 인코딩을 사용.

  • 대부분의 문자는 2바이트로 인코딩, 최대 65,536개의 글자밖에 표현
  • 그래서 일부 문자는 4바이트로 인코딩되어있습니다. 예를 들면 𝒳(수학에서 사용하는 X)나 😄(웃는 표정), 일부 상형 문자
alert('😄'.length); // 2
  • length는 4바이트 문자를 2바이트 문자 2개로 취급.
  • 이런 문자를 '서로게이트 쌍’이라고 한다.
  • 기본적으로는 정규 표현식도 4바이트의 '긴 문자’를 2바이트 문자 2개로 취급하여, 이를 해결할 수 있는 플래그 "u"를 지원

참고) 유니코드

아스키(ASCII)는 7Bit로 인코딩 되어 128개 문자를 포현.

  • 영문자, 숫자, 특수 문자, 제어문자

유니코드(Unicode): 21비트로 인코딩되어 1,114,112개의 문자(0x0000-0x10FFFF)를 표현할 수 있습니다.

  • 전 세계 모든 언어의 문자, 기호, 이모티콘 등을 포함.

UTF-8: 가변 길이 인코딩 방식. / 1바이트에서 4바이트로 문자 표현.

  • ASCII와 호환됨. 영어와 같은 라틴 문자는 1바이트로, 다른 문자는 2~4바이트로 인코딩. -> 효율적이고 널리 사용됨.

UTF-16: 가변 길이 인코딩 방식. / 2바이트 또는 4바이트로 문자 표현.

  • 대부분의 일반적인 문자는 2바이트로 인코딩.
  • BMP(기본 다국어 평면) 문자는 2바이트, 그 외의 문자는 4바이트로 인코딩.

UTF-32: 고정 길이 인코딩 방식. / 모든 문자를 4바이트로 표현.

  • 모든 문자를 같은 길이로 인코딩하므로 단순하지만 비효율적일 수 있음. -> 메모리 사용량이 많음.

유니코드 프로퍼티 \p{…}

유니코드 문자는 내부적으로 프로퍼티를 가지고 있다. 같은 문자열 임에도 유니코드상으로 Letter에 속하는지 아니면 Number에 속하는지 알 수 있다.

  • 그 외, 소문자, 대문자, 대시, 통화 기호, 공백 등등 다양한 범주가 있다.
  • 특정 그룹의 유니코드르 타겟팅 한다면 위 프로퍼티를 고려하자.

  • 이러한 프로퍼티를 사용하려면 p를 이용하면 된다.
let str = "A ბ ㄱ";
// 유니코드의 문자열 프로퍼티를 grap한다.
alert( str.match(/\p{L}/gu) ); // A,ბ,ㄱ
alert( str.match(/\p{L}/g) ); // null ('u' 플래그가 없어서 일치 결과 없음)
---
// 예시: 16진수
let regexp = /x\p{Hex_Digit}\p{Hex_Digit}/u;
alert("number: xAF".match(regexp)); // xAF
---
// 예시: 한자
let regexp = /\p{sc=Han}/gu; // 한자를 반환
let str = `Hello Привет 你好 123_456`;
alert( str.match(regexp) ); // 你,好
---
// 예시: 통화
let regexp = /\p{Sc}\d/gu;
let str = `Prices: $2, €1, ¥9`;
alert( str.match(regexp) ); // $2,€1,¥9

4.앵커: 문자열의 시작 ^과 끝 $

캐럿(caret) 기호 ^와 달러 기호 $는 정규식에서 특별한 뜻을 가진다.

  • 앵커 라고 하며, 조건패턴에 해당 한다.

  • ^ : 특정 문자열로 시작하는지 판단 가능

  • $ : 특정 문자열로 끝나는지 판단 가능

  • ^...$ : 한 행이 특정 패턴으로 시작해서 끝나는지, 완전 일치 판단 가능

    • ^$와 일치하는 문자열은 오직 빈문자 뿐.!
// 텍스트가 Mary로 시작하는지 검사해보죠.
let str1 = "Mary had a little lamb";
alert( /^Mary/.test(str1) ); // true
---
//문자열이 snow로 끝나는지 검사
let str1 = "it's fleece was white as snow";
alert( /snow$/.test(str1) ); // true
---
// 완전 일치 판단
let goodInput = "12:34";
let badInput = "12:345";

let regexp = /^\d\d:\d\d$/;
alert( regexp.test(goodInput) ); // true
alert( regexp.test(badInput) ); // false
---

5.앵커 ^와 $의 여러 행 모드, 'm' 플래그

\n 개행을 기준으로 여러행들을 모두 검사하고 싶다면 m(멀티라인) 플래그 사용할것.

m 플래그를 사용하면 여러 행 모드(multiline mode)를 활성화 가능

  • 여러 행 모드는 ^와 $의 작동 방식에만 영향
  • 여러 행 모드에서는 두 앵커가 전체 문자열의 처음과 끝뿐 아니라 각 행의 시작과 끝에도 대응합니다.
let str = `1st place: Winnie
2nd place: Piglet
3rd place: Eeyore`;
alert( str.match(/^\d/gm) ); // 1, 2, 3
---
// str를 싱글라인으로 취급
let str = `1st place: Winnie
2nd place: Piglet
3rd place: Eeyore`;
alert( str.match(/^\d/g) ); // 1
---
let str = `Winnie: 1
Piglet: 2
Eeyore: 3`;
alert( str.match(/\d$/gm) ); // 1,2,3
---
// 줄 바꿈을 찾을 때는 앵커 ^ $뿐 아니라 줄 바꿈 문자 \n을 사용 가능.
// 하지만 grap된다.
let str = `Winnie: 1
Piglet: 2
Eeyore: 3`;
alert( str.match(/\d\n/gm) ); // 1\n,2\n

정규식에는 2가지 패턴이 있다.

  • 문자열을 소비하는 (grap) 패턴
    • \d\n 처럼 \n 도 매치함수에 걸린다.
  • 그렇지 않은 Condition 패턴
    • \d$ 처럼 $는 매치의 보조조건으로 사용되며, 매치에 걸리진 않는다.

6.Word boundary: \b

단어경계 클래스는 \b 로 표현한다.

  • 문장의 시자과 끝 조건을 거는 앵커(^$) 패턴처럼 조건으로 사용한다.
    • 단어를 분리하고 싶을때 사용될 수 있다.

    • 단어가 \w(공백,특수문자)으로 감싸여 있는지 체크한다.

3가지 조건을 본다.

  • 문자열 시작 시 첫 번째 문자열 문자가 단어 문자인 경우 \w.
  • 문자열 끝에서 마지막 문자열 문자가 단어 문자인 경우 \w.
  • 문자열의 경계의 두 문자 사이에서 하나는 단어 문자 \w이고 다른 하나는 그렇지 않습니다.
  • *쉽게 생각하면 단어 경계 지점은
Java!라는 단어가 있다면 단어와 단어 사이 가림막이 존재한다고 생각하자.
|J|a|v|a|!|
o x x x o x
가림막 기준으로 앞뒤 문자 중 하나만 \w에 해당되면 \b조건이 참이다.
// 독립된 Java라는 문자가 있다.  
alert( "Hello, Java!".match(/\bJava\b/) ); // Java

// JavaScript라는 문자가 있지 Java는 없다.
alert( "Hello, JavaScript!".match(/\bJava\b/) ); // null
---
// H앞은 단어 문자 아니라 ok, o뒤부분은 특수문자라 ok
alert( "Hello, Java!".match(/\bHello\b/) ); // Hello
alert( "Hello, Java!".match(/\bJava\b/) ); // Java
// l뒤 o는 문자여서 No
alert( "Hello, Java!".match(/\bHell\b/) ); // null (no match)
// 아래 경우는 무쓸모 짓인데, !뒤에 단어가 아니라ok인데 !본인도 단어가 아니라서 결국 단어 경계라고 볼 수 없다.
alert( "Hello, Java!".match(/\bJava!\b/) ); // null (no match)
---
alert( "1 23 456 78".match(/\b\d\d\b/g) ); // 23,78
alert( "12,34,56".match(/\b\d\d\b/g) ); // 12,34,56
---

//eg) 09:00 만 선택하시오.
"Breakfast at 09:00 in the room 123:456."

alert( "Breakfast at 09:00 in the room 123:456.".match( /\b\d\d:\d\d\b/ ) ); // 09:00

7.Escaping, special characters

\처럼 정규식에는 특별한 문자열들이 있으며 그 목록은 아래와 같다.
[ \ ^ $ . | ? * + ( ).

  • 위 특별한 문자열들을 정규식의 문법으로 사용하고있지만, 특수문자들 자체를 고를때는 이스케이핑(문법적 의미 탈출)을 해야 한다.

new RegExp을 사용해서 정규식을 표현하면 아래사항을 주의 해야 한다.

  • 1) .을 이스케이핑 하고 싶다면 \. 라고 적어야 한다. (\n처럼 문자열 자체에서 consume하는 경우 )
  • 2) * 정적 정규식 표현에서는 슬래시(//)를 사용하는데 RegExp함수에서는 슬래시를 미사용하므로 이스케이프를 제거해도 된다.

Escaping

// . 자체를 찾고 싶을때  
alert( "Chapter 5.1".match(/\d\.\d/) ); // 5.1 (match!)
alert( "Chapter 511".match(/\d\.\d/) ); // null (looking for a real dot \.)
---
// 괄호도 특수 문자열이다.
alert( "function g()".match(/g\(\)/) ); // "g()"
---
// 백슬레시도 특수 문자열이다.
alert( "1\\2".match(/\\/) ); // '\'

A slash

// 슬래시(/)는 주석,정규식 시작과 끝으로 JS언어에서 사용되므로 이스케이핑 해야 한다.  
alert( "/".match(/\//) ); // '/'
---
// new을 이용하는 경우라는 그렇지 않다.
alert( "/".match(new RegExp("/")) ); // finds /

new RegExp

사실 문자열 자체도 \을 이스케이핑 처리하고 있다.

  • 예를들어 \n은 문자열에서 새로운 개행을 의미한다.
    • \u1234 – becomes the Unicode character with such code,
    • \d or \z 는 특별한 의미가 없기 때문에 제거된다.
let regexp = new RegExp("\d\.\d"); // 문자열에서 \d를 이스케이핑 처리해서 d클래스가 정상작동하지 않는다.  
alert( "Chapter 5.1".match(regexp) ); // null
---
let regStr = "\\d\\.\\d"; // 이스케이핑 문자열 \d을 쓰는 경우
alert(regStr); // \d\.\d (correct now)
let regexp = new RegExp(regStr);
alert( "Chapter 5.1".match(regexp) ); // 5.1

8.Sets and ranges [...]

Sets

내가 직접 문자 클래스를 만들고 싶다면 []를 이용하자.

// find [t or m], and then "op"
alert( "Mop top".match(/[tm]op/gi) ); // "Mop", "top"
---
// 셋트중 하나만 선택된다.
// find "V", then [o or i], then "la"
alert( "Voila".match(/V[oi]la/) ); // null, no matches

Ranges

  • 단어 문자 \w나 하이픈 -을 찾으려면 [\w-]가 됩니다.
  • 여러 클래스를 결합하는 것도 가능합니다.
  • [\s\d]는 "공백 문자 또는 숫자"를 의미합니다.
alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF

---
\d – is the same as [0-9],
\w – is the same as [a-zA-Z0-9_],
\s – is the same as [\t\n\v\f\r ], plus few other rare unicode space characters.

---
// Example: multi-language \w
\w는 영어만 검색이 된다. 다양한 언어를 검색하러면 아래와 같은 조건으로 봐야한다.

let regexp = /[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]/gu;
let str = `Hi 你好 12`;

// finds all letters and digits:
alert( str.match(regexp) ); // H,i,你,好,1,2

---
// 서로쌍 게이트 문자열이 있다면 u플래그를 추가하자.
alert( '𝒳'.match(/[𝒳𝒴]/u) ); // 𝒳
alert( '𝒴'.match(/[𝒳-𝒵]/u) ); // 𝒴

Excluding ranges

^을 추가해서 해당 범위가 아닌것으로 뒤집을 수 있다.

[^aeyo] – any character except 'a', 'e', 'y' or 'o'.
[^0-9] – any character except a digit, the same as \D.
[^\s] – any non-space character, same as \S.
---
alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // @ and .

Escaping in […]

일반적으로 정확히 특수 문자를 찾으려면 .처럼 이스케이프해야 합니다. 백슬래시가 필요한 경우 \ 등을 사용합니다.

  • 근데 []안에서는 []자체 외에는 이스케이프 할 필요가 없지만, 해도 괜찮다.

// No need to escape
let regexp = /[-().^+]/g;
alert( "1 + 2 - 3".match(regexp) ); // Matches +, -

// Escaped everything
let regexp = /[\-\(\)\.\^\+]/g;
alert( "1 + 2 - 3".match(regexp) ); // also works: +, -

9.Quantifiers +, *, ? and {n}

Quantity {n}

  • The exact count: {5}
  • The range: {3,5}, match 3-5 times
  • \d{3,} : 3 or more:
// 정확히 5개 숫자랑 매칭하고자 할때  
// \d{5} 는 \d\d\d\d\d. 와 같다.
alert( "I'm 12345 years old".match(/\d{5}/) ); // "12345"

// 3~5개의 숫자랑 매칭을 할때, 조건에 맞으면 문자열 전체가 소비된다.
alert( "I'm not 12, but 1234 years old".match(/\d{3,5}/) ); // "1234"

// 3개 이상의 숫자라면 grap
alert( "I'm not 12, but 345678 years old".match(/\d{3,}/) ); // "345678"

// eg) 1개 이상의 숫자라면 grap
let str = "+7(903)-123-45-67";
let numbers = str.match(/\d{1,}/g);
alert(numbers); // 7,903,123,45,67

Shorthands ?*+

? the same as {0,1} : 있다면 1개까지 허용해서 매칭 가능  
(optional이라서 1개까지만 기꺼이 허용)
* the same as {0,} : 없어도 된다
(곱셉이라서 무제한 가능)
+ the same as {1,} : 1개는 무조건 있어야 매칭 가능
(십자가 플러스라서 1 부터)

---
// +는 1개 이상의 숫자와 매칭
let str = "+7(903)-123-45-67";
alert( str.match(/\d+/g) ); // 7,903,123,45,67

// ?는 0~1개 u문자와 매칭
let str = "Should I write color or colour?";
alert( str.match(/colou?r/g) ); // color, colour

alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1
alert( "100 10 1".match(/\d0+/g) ); // 100, 10
// 1 not matched, as 0+ requires at least one zero

Examples

  • html 태그 추출하기
    • 숫자로 시작하지 않는다.
    • h1,h2 등 문자 다음에는 숫자도 가능하다.
    • 처럼 여는태그
      처럼 닫는 태그 모두 선택해야 한다.
alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345  

alert( "<body> ... </body>".match(/<[a-z]+>/gi) ); // <body>

alert( "<h1>Hi!</h1>".match(/<[a-z][a-z0-9]*>/gi) ); // <h1>

alert( "<h1>Hi!</h1>".match(/<\/?[a-z][a-z0-9]*>/gi) ); // <h1>, </h1>
---
// eg) 3개 이상의 생략 부호를 모두 찾기
let regexp = /\.{3,}/g;
alert( "Hello!... How goes?.....".match(regexp) ); // ..., .....
---
// eg) HTML에서 쓰이는 색상 검출을 위한 정규표현식
- #ABCDEF과 같이 HTML에서 사용하는 색상을 검출할 수 있는 정규표션식을 만들어보세요.
- 해당 색상은 첫 글자 #과 6개의 16진수로 구성됩니다.

/#[0-9A-F]{6}/gi
/#[0-9A-F]{6}\b/gi

오답노트 : {6} 이라고쓰면 6개 문자열만 선택할것 같지만 선택은 그렇게 되지만, 실제로는 #11111123 등의 문자열에서도 선택된다.
- 조건을 걸어서 단어를 끊어야 한다.


10.Greedy and lazy quantifiers

아래 예제에서 "witch" "broom" 등 "으로 감싸진 워드를 선택하고 싶다.

  • 아래 정규식으로 수행하면 잘 동작할까?
  • 답은 그렇지 않다.
  • 심지어 .으로 모든 단어를 선택했는데 "에서 끝나기도 한다.
let regexp = /".+"/g;
let str = 'a "witch" and her "broom" is one';
alert( str.match(regexp) ); // "witch" and her "broom"

'a "witch" and her "broom" is one' 문자열에서 왜 모든 "witch an이후의 모든 문자열이 모두 선택되지 않았는지 로직을 보자.

  • 정규식 엔진을 특이하게 우선 탐욕을 부리고 줄이는 과정이 있다.
  • /".+"/g 의 의미를 보고 정규식 엔진이 되보자.
1.0번 인덱스부터 탐색을 하며 
정규식의 첫번째 패턴인 "과 일치하는지 체크한다.

'a "witch" and her "broom" is one'
^ 2번 인덱스에서 매칭이 되었다.!

2.그 이후 .+ 을 만났으니 모든 문자열을 Grap 한다.

3.너무 많이 Grap한것 같아 다시 뒤로 빽 한다.
'a "witch" and her "broom" is one'
^^^^^^^^^^^^^^^^^^^^^^^ "라는 패턴이 나올때까지 빽 한다.

우선 수량 연산자를 만나면 Grap하고 그 후 패턴이 나오면 빽 하는 로직이다.

Lazy mode (?)

  • ?을 수량자 뒤에 붙여서 LazyMode를 활성화 한다.
  • 최소한의 횟수로만 반복하도록 한다.
  • lazy mode works for +?. Quantifiers *? and ??
let regexp = /".+?"/g;
let str = 'a "witch" and her "broom" is one';
alert( str.match(regexp) ); // witch, broom
---

// Lazy모드의 끝에 패턴이 없는 경우 바로 종료된다.
alert( "123 456".match(/\d+ \d+?/) ); // 123 4

Alternative approach

  • .으로 광범위한 단어 및 수량자로 패턴을 만드는것이 모든 해결책은 아니다.
let regexp = /"[^"]+"/g; // "의 아닌 패턴이 걸리므로 그리디 하지 않게 원하는 목적을 달성.!  
let str = 'a "witch" and her "broom" is one';
alert( str.match(regexp) ); // witch, broom

---
// eg) a태그에서 클래스가 doc인 경우 href를 추출하고 싶은경우, a태그 전체를 검색하자.
// 아래 경우는 실패다.
let str = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
let regexp = /<a href=".*?" class="doc">/g;

// Wrong match!
alert( str.match(regexp) ); // <a href="link1" class="wrong">... <p style="" class="doc">

---
// 개선 결과
let str1 = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
let str2 = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
let regexp = /<a href="[^"]*" class="doc">/g;

// Works!
alert( str1.match(regexp) ); // null, no matches, that's correct
alert( str2.match(regexp) ); // <a href="link1" class="doc">, <a href="link2" class="doc">

11 Capturing groups

그룹은 ()의 문법을 사용. 이는 2가지 효과가 있습니다.

  • 결과 배열에서 일치 항목의 일부를 별도의 항목으로 가져올 수 있습니다.
  • 괄호 뒤에 수량자를 넣으면 괄호 전체에 적용.

용법중 하나로 어떠한 특정 패턴을 만들었는데, 이러한 패턴 또한 반복가능할때 그룹핑에 수량자를 쓴다.

// go+ 대신 (go)+ 는 다른 의미이다.  
alert( 'Gogogo now!'.match(/(go)+/ig) ); // "Gogogo"

---
// my-site.com 의 경우에는 매칭되지 않는다.
let regexp = /(\w+\.)+\w+/g;
alert( "site.com my.site.com my-site.com".match(regexp) ); // site.com,my.site.com
// eg, - 포함하여 선택
let regexp = /([\w-]+\.)+\w+./g;
alert( "site.com my.site.com my-site.com".match(regexp) ); // site.com ,my.site.com ,my-site.com
---
// 이메일 선택
let regexp = /[-.\w]+@([\w-]+\.)+[\w-]+/g;
alert("my@mail.com @mail.com @ his@site.com.uk".match(regexp)); // my@mail.com, his@site.com.uk

📌 Named groups

  • 그룹에 이름을 지정하면 인덱스 대신 문자열로 처리 가능하다.
  • (?<name>) 이러한 패턴으로 사용한다.
    • g태그를 사용하면 안된다. g는 match 리턴을 배열로 처리한다.
// 년,월,일 뽑기
let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
let str = "2019-04-30";
let groups = str.match(dateRegexp).groups;

alert(groups.year); // 2019
alert(groups.month); // 04
alert(groups.day); // 30

// g 플래그 여부
// let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g;
// str.match(dateRegexp) -> 객체가 아닌 단순 배열 [ '2011-01-02' ]

// 오답노트 - 아래처럼 named group 밖에 수량자를 넣으면 year, month, day 은 각각 9,4,0 으로 마지막 매칭된것만 리턴
let dateRegexp = /(?<year>[0-9]){4}-(?<month>[0-9]){2}-(?<day>[0-9]){2}/;

Note) 중간정리, 복잡한 정규식을 읽는 방법

  • 1.명확한 문자 패턴을 먼저 고른다. 패턴에 복잡한 조건이 붙는경우는 나중에 본다.
    • 예, 이메일 정규식에서 @가 무조건 와야 하는 경우에는 @ 중점으로 본다.
  • 2.수량자를 확인한다.
  • 3.정규식의 grap부분 이후에는 condition문법을 해석한다.

14.Lookahead and lookbehind

  • ()안에 ? 가 있는것이 특징이다.
Pattern type    matches
X(?=Y) Positive lookahead X if followed by Y
X(?!Y) Negative lookahead X if not followed by Y
(?<=Y)X Positive lookbehind X if after Y
(?<!Y)X Negative lookbehind X if not after Y

문자열을 grap하지 않고 특정 패턴과 전방 일치 혹은 후방 일치 하는 조건을 걸고싶을때 사용 한다.

  • "lookahead" 및 "lookbehind"라고 하며 함께 "lookaround"라고 한다.

Lookahead

구문 : X(?=Y)

  • X라는 패턴을 찾을건데, 조건이 있어 뒤에 Y라는 패턴과 일치해야해.
  • 더 복잡하게도 가능 e.g. X(?=Y)(?=Z)
    • 위 조건은 AND조건으로 봐야 한다.
// 1개이상의 숫자덩어리를 찾을꺼야, 단 €가 있어야 하며 이를 선택하지 말것 
let str = "1 turkey costs 30€";
alert( str.match(/\d+(?=€)/) ); // 30, the number 1 is ignored, as it's not followed by €
---
// 1개 이상의 숫자 덩어리를 찾을꺼야
// - 단 그 뒤에 바로 공백이 와야 하고
// - 단 그 후에 어디든 30이라는 숫자가 있어야 해
let str = "1 turkey costs 30€";
alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1

Negative lookahead

구문 : X(?!Y)

  • X라는 패턴을 찾을건데, 조건이 있어. 뒤에 Y라는 패턴이 없어야해.
// 통화 표시를 제외한 숫자를 고르시오,
// 단, 통화표시 숫자는 반드시€가 붙어 있습니다.
let str = "2 turkeys cost 60€";
alert( str.match(/\d+(?!€)/) ); // 2 (the price is skipped)

위 조건들을 앞에 붙이기가 가능하다. (lookbehind, 조건에 맞으면 뒤를본다.)

  • 하지만 구문이 달라진다. <라는 문자열이 추가된다.
  • Positive lookbehind: (?<=Y)X,
  • Negative lookbehind: (?<!Y)X,
let str = "1 turkey costs $30";

// the dollar sign is escaped \$
alert( str.match(/(?<=\$)\d+/) ); // 30 (skipped the sole number)
---
let str = "2 turkeys cost $60";

alert( str.match(/(?<!\$)\d+/) ); // 2 (skipped the price)

Capturing groups

lookahead를 조건으로 사용도 하면서, grap도 하고 싶은 경우에는 괄호를 감싸준다.

  • 대신 별도의 grap으로 도출된다.

let str = "1 turkey costs 30€";
let regexp = /\d+(?=(€|kr))/; // extra parentheses around €|kr

alert( str.match(regexp) ); // 30, €
---
let str = "1 turkey costs $30";
let regexp = /(?<=(\$|£))\d+/;

alert( str.match(regexp) ); // 30, $