본문 바로가기

JavaScript

9장 정규 표현식

Regular Expression! 정규표현식

정규표현식?

: string data에서 패턴을 묘사하는 방법

정규표현식은 끔찍하게 이상하면서도 무지하게 유용하다!

문자열을 다루는 강력한 도구!

정규표현식 만들기!

정규표현식 = Object 타입!

  • RecExp 생성자
  • 앞뒤 슬래쉬(/)
    var reg1 = new RegExp("abc");
    var reg2 = /abc/;

    //
    정규표현식 오브젝트 모두 abc 패턴을
    나타냅니다

   

첫번째 방법,

생성자를 이용하면 보통 스트링처럼 쓰기 때문에

문자열 쓸 때의 백슬래시 룰이 적용됩니다

console.log("\a\c");
-> ac
console.log("\\a\\c");
-> \a\c

   

두번째 방법은,

슬래시 사이에 놓기 때문에 백슬래시 사용방법이 다릅니다

슬래시를 패턴을 끝낼 때 사용하고 있으므로 패턴으로 슬래시를 쓰고 싶으면

백슬래시를 이용합니다 (\/)

특별한 캐릭터 코드(\n)가 아니면 백슬래시는 무시되기보다는 보존되어서 패턴의 일부가 됩니다.

var regSlash = /ab\c/;
var regConstr = RegExp("ab\c");

console.log(regSlash.test("ab\\c"));
->true
console.log(regConstr.test("ab\\c"));
->false

특수문자(*, +…)는 정규표현식에서 특별한 의미를 갖고 있는 경우가 많기 때문에 패턴으로 쓰고 싶으면 백슬래시를 쓰세요!

var eighteenPlus = /eighteen\+/;

정규표현식 Match 테스트하기!

정규표현식 오브젝트는 많은 메소드를 갖고있어요!

가장 단순한 것 중 하나는 test입니다

문자열을 넘겨주면 같은 패턴을 갖고있는지 Boolean 값을 리턴합니당

console.log(/abc/.test("abcde"));
//-> true

console.log(/abc/.text("ade"));
//-> false

특수문자(*,+…)가 포함되지 않은 정규표현식은 단순히 문자의 순서를 의미합니다. 문자열 내부 어느 위치에 있던지 true를 반환합니다

Set of 문자 매칭하기

문자열에서 abc가 포함되어 있는가는 indexOf 함수로도 충분히 찾아낼 수 있는데…

정규표현식은?? 더 복잡한 패턴을 표현할 수 있게 해주는 것입니다!

숫자 중에 어떤 숫자든 존재하면 된다고 해봅시다

정규표현식에서는 대괄호로 문자들의 리스트나 범위를 둘러싼다.

대괄호[ ] 안에 문자들을 넣으면, 괄호 안 문자들 중 아무 문자를 의미한다.

console.log(/[0123456789]/.test("in 1992"));
//
true
console.log(/[0-9]/.test("in 1992"));
//
true

대시(-)는 문자의 범위로, 유니코드 순서를 따릅니다

ex) [0-9]는 유니코드 48부터 57

   

   

-범위 설정처럼 유용한 단축문자 그룹-

\d모든 숫자 캐릭터 ( =[0-9] )

\w영문 + 숫자

\swhitespace 캐릭터 (space, tab, newline, and similar)

\D숫자가 아닌 캐릭터

\W영문 + 숫자가 아닌 캐릭터

\Swhitespace 아닌 캐릭터

.newline 제외한 모든 캐릭터

   

단축문자를 이용한 날짜 패턴 정규표현식!

var dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/;
console.log(dateTime.test("30-01-2003 15:20"));
//
true
console.log(dateTime.test("30-jan-2003 15:20"));
//
false

하지만 엄청 이상해보여… 백슬래시너무많아;; 패턴을 너무 읽기 힘들게 만들쟈낭

업그레이드 되도록 계속 배워봅시다

   

+

백슬래시 코드 단축문자는 대괄호[ ]안에서도 사용가능합니다

예를들어 [\d.] 모든 숫자 또는 마침표

마침표가 단축문자 그룹에서 newline 제외한 모든 캐릭터, 와일드카드로 쓰인다고 배웠는데

대괄호 안에서 쓰이면 특별한 의미를 잃습니다. + 같은 특수문자들도!

http://regexone.com/lesson/wildcards_dot

+

7이 들어가는 문자열은 찾지 않기! 처럼

특정 문자가 없는 패턴을 찾고싶다면

특수문자 ^ 사용하기

console.log(/[^01]/.test("1100100"));
// -> false
console.log(/[^01]/.test("1100102"));
// -> true(1
이나 0 아닌 2 발견!!)

패턴의 부분 반복하기!!

숫자 하나 일치하는 거 찾는 건 알겠는데….한 개 이상 많은 숫자들의 일치 찾기!

   

   

+는 한 번 이상 반복(한 번은 존재 해야)

console.log(/'\d+'/.test("'123'"));
//
true
console.log(/'\d+'/.test("''"));
//
false
console.log(/'\d*'/.test("'123'"));
//
true
console.log(/'\d*'/.test("''"));
//
true

*는 +와 같은 기능인데, 0번도 가능

?는 optional! 그 문자가 있어도되고 없어도되고

패턴에서 정확히 몇번 일어나는지 정하고 싶으면{}사용!

{min, max}

{4}4번{2,4}2번-4번

{,5}0번-5번

{5,}5번-무한대번

Grouping Subexpression

한번에 *또는 +를 일련의 문자들에 사용하려면 괄호이용

괄호 안에 있는건 하나의 element로 친다

var cartoonCrying = /boo+(hoo+)+/i;
console.log(cartoonCrying.test("Boohoooohoohooo"));
//
true

+

마지막의 i는 case insensitive

Matches and groups

test메소드는 엄청 심플한 방식입니다!

오직 매치되는지 아닌지만 알랴줌

새로운 메소드 exec

매치되는게 없으면 null

있으면 매치에 관한 정보 오브젝트를 리턴

var a1 = /\d+/.exec("one two 100");
console.log(a1);
//
["100"]
console.log(a1.index);
//
8

exec가 리턴하는 오브젝트는 index 프로퍼티도 가지고 있습니다

인덱스 프로퍼티가 아니라면 스트링 배열로 보인다

   

비슷하게 match 메소드도있음

console.log("one two 100".match(/\d+/));
//
["100"]

정규표현식이 괄호로된 subexpression을 포함할때

subexpression과 매치되는것도 배열에서 보입니다.

배열 첫번째 요소는 전체적인 매치

다음 요소는 그 다음으로 열린 괄호…이런식으로!

var quotedText = /'([^']*)'/;
console.log(quotedText.exec("she said 'hello'"));
//
["'hello'", "hello"]

그룹이 안 맞는 경우 배열에서 다음 요소들로 undefined로 나옵니다

비슷하게, 그룹이 한 개 이상 맞을 경우 마지막으로 매치시킨 것만 배열에 보입니다

console.log(/bad(ly)?/.exec("bad"));
//
["bad", undefined]
console.log(/(\d)+/.exec("123"));
//
["123", "3"]

그룹은 문자열의 부분을 추출하는데 유용할 수 있습니다.

만약 문자열이 날짜를 포함하고 있는지 분별하길 원할 뿐만아니라 추출해서 오브젝트를 만들고 싶으면, digit 패턴에 괄호를 씌워서 exec의 결과에서 날짜를 얻어낼 수 있다.

   

   

그러나 자바스크립트에서 날짜와 시간 값을 저장하는 더 나은 방법!

The Date Type

자바스크립트는 Date라는 날짜를 나타내는 표준 오브젝트 타입이 있다

간단하게 new를 이용해 데이트 오브젝트를 만들면, 현재 날짜와 시간을 얻을수있다

console.log(new Date());
//
Wed Dec 04 2013 14:24:57 GMT+0100 (CET)

특정한 시간으로 오브젝트를 만들수도있고

console.log(new Date(2009, 11, 9));
//
Wed Dec 09 2009 00:00:00 GMT+0100 (CET)
console.log(new Date(2009, 11, 9, 12, 59, 59, 999));
//
Wed Dec 09 2009 12:59:59 GMT+0100 (CET)

   

주의할 점:

자바스크립트는 달을 숫자로쓰면 0부터 시작한다는 약속!

그래서 12월은 11. 그러나 일(day)은 1부터 시작한다 헷갈리고 바보같은 부분이니 조심하기

마지막 4개 arguments 시분초 그리고 밀리초는 옵셔널이고 넣지 않으면 0으로 설정된다

Timestampsms

1970부터 시작한 밀리세컨드를 저장한다. 1970이전의 날짜는 음수.(1970년대쯤 발명된 유닉스 타임이라는 룰)

date 오브젝트에서 getTime 메소드가 이 숫자를 반환한다.

console.log(new Date(2013, 11, 19).getTime());
//
1387407600000
console.log(new Date(1387407600000));
//
Thu Dec 19 2013 00:00:00 GMT+0100 (CET)

Date 생성자에서 argument를 하나만 주면 밀리세컨드로 여겨진다.

Date오브젝트.getTime() = Date.now()

Date 오브젝트는 getFullYear, getMonth, getDate, getHour, getMinute 같은 메소드를 제공합니다.

Word and String boundaries

매칭은 문자열의 어느 부분에서나 일어날수있다

만약 매치 범위가 문자열 전체이길 원한다면 ^시작과 $끝을 설정한다.

/^\d+$/ 전체적으로 숫자로 이루어짐

/^!/ 느낌표로 시작하는

/x^/^가 시작인데 앞에 x가 있으므로 맞는 문자열은 있을 수 없다

   

+

마커 \b를 사용하면 단어 경계를 사용할 수 있다

console.log(/cat/.test("concatenate"));
//
true

console.log(/\bcat\b/.test("concatenate"));
//
false

console.log(/\bcat\b/.test("con cat enate"));
//
true

아무 구두점이나 여백문자로 둘러싸면 단어로 인식하여 일치됩니다

바운더리 마커는 실제 문자가 아닌 것을 기억하세요!!

패턴 선택하기

숫자뿐만아니라 숫자에 따라오는 단어처럼 복수 형태를 찾을 때,

우리는 여러번 정규표현식을 쓰고 테스트해볼수있어

그러나 더좋은방법이 있지!

pipe | 이용하기 choice를 나타냅니다 왼쪽->오른쪽

var animalCount = /\b\d+ (pig|cow|chicken)s?\b/;
console.log(animalCount.test("15 pigs"));
//
true
console.log(animalCount.test("15 pigchickens"));
//
false

괄호는 파이프에서 패턴을 제한할 수 있습니다

2개 이상의 패턴 중에 선택하게 할 수 있다는 것!

BackTracking 역추적

역추적이 자바스크립트 성능 문제의 전부는 아니지만,

역추적이 어떻게 동작하는지 이해하고 어떻게 줄일 수 있는지 이해하면 정규표현식을 효율적으로 쓰는 데 큰 도움이 됩니다.

정규표현식이 동작할 때는 문자열의 각 지점마다 정규표현식의 왼쪽에서 오른쪽으로 진행하며 일치하는 토큰이 있는지 검사합니다.

*나 +?,{2,}같은 수량자를 만나면 일치하는 다른 문자가 있는지 검사할 때를 결정해야하며,

or 연산자를 만나면 가능한 옵션 중에 하나를 결정해야 합니다

정규표현식에서 이러한 결정을 내릴때마다 나중에 필요하면 돌아올 수 있도록 다른 옵션을 기억합니다

선택한 옵션에 일치하는 것이 있으면 정규표현식에 있는 다른 패턴을 계속 시도하며 매칭이 완료되고,

선택한 옵션에 일치하는 것이 없거나 그 이후 패턴에서 실패한다면

시도해보지 않은 옵션이 있는 마지막 지점까지 역추적해서 다른 옵션을 선택합니다

or 연산자와 역추적

/h(ello|appy) hippo/.test("hello there, happy hippo");

  • 정규표현식은 우선 h를 찾는데, 문자열의 첫문자에서 일치하는 것을 발견
  • 다음의 하위표현식 (ello|appy)에서 두 가지 경로를 선택할 수 있습니다
    정규표현식은 가장 왼쪽에 있는 옵션을 선택해서 ello가 문자열의 다음 문자와 일치하는지 검사합니다
    (or 연산자는 항상 왼-> 오 진행)
  • 일치하는 것을 발견했고 그다음에 있는 공백 문자도 일치했습니다.
    그런데 그 다음에 있는 hippo의 h는 문자열에서 다음에 있는 t와 일치하지 않습니다.
    가능한 옵션을 모두 검사한 것은 아니므로, 마지막으로 분기했던 지점인
    정규표현식의 맨 처음에 있는 h 바로 다음까지 역추적해서 두 번째 옵션(appy)에 일치하는지 검사합니다.
    이것 역시 일치하지 않고 더 이상은 시도할 수 있는 옵션이 없으므로
    첫 문자에서는 찾을 수 없다는 결론을 내리고 두번째 문자로 이동하여 다시 시도합니다.
  • 두번째 문자는 h가 아니므로 h를 찾아서 14번째 문자까지 진행하고 happy에서 h를 찾습니다. 그 다음 매치들 모두 성공!

Replace 메소드

문자열은 replace 메소드를 가지고있어서 string의 부분을 바꿀 수 있습니다

"papa".replace("p","m")
-> mapa

   

첫번째 인자가 정규표현식으로 표현될 수 있다는게 포인트!

첫번째로 매치되는 곳이 대체됩니다

console.log("Borobudur".replace(/[ou]/, "a"));
//
Barobudur
console.log("Borobudur".replace(/[ou]/g, "a"));
//
Barabadar

+옵션 g를 사용하면(글로벌) 매치되는 모든 것이 대체됩니다

replace option g = replaceAll

   

   

정규표현식을 replace와 함께 쓰는 강력한 장점은 일치하는 그룹들을 이용할 수 있다는 것

각 줄마다 사람이름이 있습니다

Lastname , Firstname 포맷을

-> FirstName LastName포맷으로 바꾸는 코드

console.log(
"Hopper, Grace\nMcCarthy, John\nRitchie, Dennis"
.replace(/([\w ]+), ([\w ]+)/g, "$2 $1"));
//
Grace Hopper
// John McCarthy
// Dennis Ritchie

$1과 $2은 괄호 안 그룹의 패턴을 의미합니다 ([\w ]+)

$1는 첫번째 그룹과 매치되고 2는 두번째 괄호… $9까지 있습니다

모든 매치는 @@bodyamp;로 부를 수 있습니다

replace의 두번째인자로 문자열 말고 함수 넘겨주는 것도 가능합니다

각각의 replacement, 함수는 매치된 그룹과 함께 호출될 것입니다

var s = "the cia and fbi";
console.log(s.replace(/\b(fbi|cia)\b/g, function(str) {
return str.toUpperCase();
}));
//
the CIA and FBI

   

   

재미있는 예제

var stock = "1 lemon, 2 cabbages, and 101 eggs";
function minusOne(match, amount, unit) {
amount = Number(amount) - 1;
if (amount == 1) // only one left, remove the 's'
unit = unit.slice(0, unit.length - 1);
else if (amount == 0)
amount = "no";
return amount + " " + unit;
}
console.log(stock.replace(/(\d+) (\w+)/g, minusOne));
//
no lemon, 1 cabbage, and 100 eggs

GREED(탐욕모드, 최후 일치)

기본적으로 *,?, +, {min, max}는 탐욕적(greedy)입니다!

전체 패턴을 만족시킬 수 있는 가장 최후까지 모든 문자를 소모시키기 때문이죠

대신 최초로 일치하는 문자에서 멈추려면, 물음표를 다음에 붙이면 됩니다.

예를 들어, 물음표가 없는 패턴 <.+>는 <로 시작하고 하나 이상의 아무 문자와 >로 끝나는 것을 찾는데,

< em >text< /em > 전체 문자열과 일치하게 됩니다

이것을 막으려면 <.+?>와 같이 물음표를 사용해서 matching as little as possible! 탐욕적이 되는 것을 막습니다

최초의 >에서 멈추도록, < em >을 얻게 됩니다

(+?, *?, ??, {}?) 이런식으로 사용가능!

Dynamically creating RegExp Object

var name = "harry";
var text = "Harry is a suspicious character.";
var regexp = new RegExp("\\b(" + name + ")\\b", "gi");
console.log(text.replace(regexp, "_$1_"));
//
_Harry_ is a suspicious character.

바운더리 마크를 만들 때 \b, 문자열이기 때문에 슬래시 두 번 쓰는 것 잊지 않기!

생성자 두번째 인자는 옵션!

"gi" for global and case-insensitive.

The Search Method

indexOf 메소드가 정규표현식에서 사용될 수 없기 때문에

search 메소드가 있습니다! 존재하지 않으면 역시 -1

console.log(" word".search(/\S/));
//
2
console.log(" ".search(/\S/));
//
-1

안타깝게도 indexOf 두번째 인자로 검색 시작점을 지정할 수 있는 것 같은 기능은 없습니당…

The LastIndex Property

exec 메소드 역시 시작점을 설정할 수 있는 간편한 방법이 없습니다

불편한 방법은 있지요

정규표현식 오브젝트는 프로퍼티를 갖고 있습니다

그 중 하나는 lastIndex, 좀 제한된 환경에서 다음 매치가 시작되는 곳

제한된 환경이란?

g 글로벌 옵션에서

exec 메소드를 사용할때

var pattern = /y/g;
pattern.lastIndex = 3;
var match = pattern.exec("xyzzy");
console.log(match.index);
//
4
console.log(pattern.lastIndex);
//
5

성공했으면 자동적으로 lastIndex를 업데이트해주고

없으면 0으로 돌린다

여러번 exec 호출시 자동적 업데이트가 문제가 될 수 있다

var digit = /\d/g;
console.log(digit.exec("here it is: 1"));
//
["1"]
console.log(digit.exec("and now: 1"));
//
null

Looping over matcehs

var input = "A string with 3 numbers in it... 42 and 88.";
var number = /\b(\d+)\b/g;
var match;
while (match = number.exec(input))
console.log("Found", match[1], "at", match.index);
//
Found 3 at 14
// Found 42 at 33
// Found 88 at 40

match = number.exec(input)을 while문의 조건으로

매치가 더 이상 없을 때 까지 반복할 수 있다.

Summary

/abc/ A sequence of characters

/[abc]/ Any character from a set of characters

/[^abc]/ Any character not in a set of characters

/[0-9]/ Any character in a range of characters

/x+/ One or more occurrences of the pattern x

/x+?/ One or more occurrences, nongreedy

/x*/ Zero or more occurrences

/x?/ Zero or one occurrence

/x{2,4}/ Between two and four occurrences

/(abc)/ A group

/a|b|c/ Any one of several patterns

/\d/ Any digit character

/\w/ An alphanumeric character ("word character")

/\s/ Any whitespace character

/./ Any character except newlines

/\b/ A word boundary

/^/ Start of input

/$/ End of input

Exercise

// Fill in the regular expressions

verify(/.../,
["my car", "bad cats"],
["camper", "high art"]);

verify(/.../,
["pop culture", "mad props"],
["plop"]);

verify(/.../,
["ferret", "ferry", "ferrari"],
["ferrum", "transfer A"]);

verify(/.../,
["how delicious", "spacious room"],
["ruinous", "consciousness"]);

verify(/.../,
["bad punctuation ."],
["escape the dot"]);

verify(/.../,
["hottentottententen"],
["no", "hotten totten tenten"]);

verify(/.../,
["red platypus", "wobbling nest"],
["earth bed", "learning ape"]);

function verify(regexp, yes, no) {
// Ignore unfinished exercises
if (regexp.source == "...") return;
yes.forEach(function(s) {
if (!regexp.test(s))
console.log("Failure to match '" + s + "'");
});
no.forEach(function(s) {
if (regexp.test(s))
console.log("Unexpected match for '" + s + "'");
});

console.log("good expression: ", regexp);
}

+

http://regexone.com/problem/matching_filenames

generated by haroopad

'JavaScript' 카테고리의 다른 글

8장 버그와 에러  (0) 2016.11.13
5장 Higher - Order Functions  (0) 2016.11.13
4장. 데이터 구조: 객체와 배열  (0) 2016.11.13
Chapter 1 - VALUES, TYPES, AND OPERATERS  (0) 2016.11.13