정규표현식(정규식:正規式)은 문자열에서 특정한 규칙에 따른 문자열 집합을 표현하기 위해 사용되는 형식 언어입니다. 정규표현식을 이용한다면 수십 줄이 필요한 코딩 작업을 간단하게 한두 줄로 끝낼 수 있습니다.
자바스크립트로 문자열을 다룰 때 정규표현식을 사용하면 손쉽게 원하는 문자열을 찾거나 변경이 가능합니다.
아래에 프로그래밍을 하면서 자주 사용하는 정규표현식을 정리하였습니다.
//생성자
new RegExp("표현", "옵션");
new RegExp("[a-z]", "gi") /
// 리터럴
표현 : [a-z]/gi;
const str = "RegExr was created by gskinner.com. Edit the Expression & Text to see matches."
let regexp = /R[a-z]/gi // 정규식 선언 후 변수에 할당(첫번째 문자가 'r' 또는 'R'이고 두번째 문자가 a에서 z인 또는 A에서 Z임을 의미)
str.match(regexp) // ['Re', 're', 're']
const str = "RegExr was created by gskinner.com. Edit the Expression & Text to see matches."
let regexp = /R[a-z]g/gi // 정규식 선언 후 변수에 할당(첫번째 문자가 'r' 또는 'R'이고 두번째 문자가 a에서 z인 또는 A에서 Z이면서 세번째 글자가 'g' 또는 'G'임을 의미
str.match(regexp) // ['Reg']
const str = "RegExr was created by gskinner.com. Edit the Expression & Text to see matches."
let regexp = /R[a-z]g/gi // 정규식 선언 후 변수에 할당(첫번째 문자가 'r' 또는 'R'이고 두번째 문자가 a에서 z인 또는 A에서 Z이면서 세번째 글자가 'g' 또는 'G'임을 의미
str.replace(regexp, 'haha') // "hahaExr was created by gskinner.com. Edit the Expression & Text to see matches."
exec 는 execution 의 줄임말로, 원하는 정보를 뽑아내고자 할 때 사용합니다. 검색의 대상이 찾고자 하는 문자열에 대한 정보를 가지고 있다면 이를 배열로 반환하며, 찾는 문자열이 없다면 null을 반환합니다.
let pattern = /a/; // 찾고자 하는 문자열
pattern.exec('banana') // 검색하려는 대상을 exec 메소드의 첫 번째 인자로 전달합니다.
// ['a']
찾고자 하는 문자열이 대상 안에 있는지의 여부를 boolean 으로 리턴합니다.
let pattern = /a/;
pattern.exec('banana') // true
RegExp.exec() 와 비슷한 기능을 하며, 정규 표현식을 인자로 받아 주어진 문자열과 일치된 결과를 배열로 반환합니다. 일치되는 결과가 없으면 null을 리턴합니다.
let str = "banana";
console.log(str.match("a"));
// ['a']
'검색 후 바꾸기'를 수행합니다. 첫 번째 인자로는 정규표현식을 받고, 두 번째 인자로는 치환하려는 문자열을 받습니다. 문자열에서 찾고자 하는 대상을 검색해서 이를 치환하려는 문자열로 변경 후 변경된 값을 리턴합니다.
let str = "banana";
console.log(str.replace("b", "B"));
// Banana
첫 번쨰 하나의 요소가 아니라 모든 요소를 바꿉니다.
let str = "banana";
console.log(str.replace("a", "Z"));
// BZnZnZ
정규표현식을 인자로 받아 가장 처음 매칭되는 부분 문자열의 위치를 반환합니다. 매칭되는 문자열이 없으면 -1을 반환합니다.
"JavaScript".search(/script/); // -1 대소문자를 구분합니다
"JavaScript".search(/Script/); // 4
"JavaScript".search(/ava/); // 1
i를 붙이면 대소문자를 구분하지 않습니다.
let withi = /j/i;
let withouti = /j/;
"JavaScript".match(withi); // ['J']
"JavaScript".match(withouti); // null
global 의 약자로, g를 붙이면 검색된 모든 결과를 리턴합니다.
let withi = /a/g;
let withouti = /c/;
"JavaScript".match(withi); // ['a, a']
"JavaScript".match(withouti); // ['a']
m을 붙이면 다중행을 검색합니다.
let str = `1st : cool
2nd : Java
3rd : Script`;
str.match(/o/gm)
// 3개의 행을 검색하여 모든 o 를 반환합니다.
// ['o', 'o']
str.match(/o/m)
// m은 다중행을 검색하게 해 주지만, g 를 빼고 검색하면 검색 대상을 찾는 순간 검색을 멈추기 때문에 ['o'] 만 리턴합니다.
^는 문자열의 처음을 의미하며, 문자열에서 ^뒤에 붙은 단어로 시작하는 부분을 찾습니다. 일치하는 부분이 있더라도, 그 부분이 문자열의 시작 부분이 아니면 null을 리턴합니다.
"coding is fun".match(/^co/); // ['co']
"coding is fun".match(/^fun/); // null
$는 문자열의 끝을 의미하며, 문자열에서 $앞의 표현식으로 끝나는 부분을 찾습니다. ^와 비슷하지만 ^는 문자열의 시작을 찾는 반면, $는 문자열의 마지막 부분을 찾습니다. 마찬가지로 일치하는 부분이 있더라도, 그 부분이 문자열의 끝부분이 아니면 null을 리턴합니다.
"coding is fun".match(/un$/); // ['un']
"coding is fun".match(/is$/); // null
"coding is fun".match(/^coding is fun$/);
// 문자열을 ^ 와 $ 로 감싸주면 그 사이에 들어간 문자열과 정확하게 일치하는 부분을 찾습니다
// ["coding is fun"]
는 의 바로 앞의 문자가 0번 이상 나타나는 경우를 검색합니다. 아래와 같은 문자열이 있을 때에 /ode*/g 을 사용하게 되면 "od" 가 들어가면서 그 뒤에 "e"가 0번 이상 포함된 모든 문자열을 리턴합니다.
"co cod code codee coding codeeeeee codingding".match(/ode*/g);
// ["od", "ode", "odee", "od", "odeeeeee", "od"]
"co cod code codee coding codeeeeee codingding".match(/ode+/g);
// ["ode", "odee", "odeeeeee"]
? 는 또는 + 와 비슷하지만, ? 앞의 문자가 0번 혹은 1번 나타나는 경우만 검색합니다. ? 또는 +? 와 같이 ?는 * 혹은 + 와 함께 쓰는 것도 가능하며, 함께 사용하였을 경우 검색 결과가 어떻게 달라지는지 아래 예시를 통해 비교해 보세요.
"co cod code codee coding codeeeeee codingding".match(/ode?/g);
// ["od", "ode", "ode", "od", "ode", "od"]
"co cod code codee coding codeeeeee codingding".match(/ode*?/g);
// ["od", "od", "od", "od", "od", "od"]
"co cod code codee coding codeeeeee codingding".match(/ode+?/g);
// ["ode", "ode", "ode"]
{}는 , ?, +, +? 의 확장판으로 생각할 수 있습니다. , ?, +, +? 가 '0개 이상' 또는 '1개 이상' 검색이 전부였던 반면, {}는 직접 숫자를 넣어서 연속되는 개수를 설정할 수 있습니다. 아래 예시와 함께 위 표에서 {}와 , ?, +, +? 의 차이를 다시 한 번 비교해 보세요.
"co cod code codee coding codeeeeee codingding".match(/ode{2}/g);
// 2개의 "e"를 포함한 문자열을 검색합니다.
// ["odee", "odee"]
"co cod code codee coding codeeeeee codingding".match(/ode{2,}/g);
// 2개 이상의 "e"를 포함한 문자열을 검색합니다.
// ["odee", "odeeeeee"]
"co cod code codee coding codeeeeee codingding".match(/ode{2,5}/g);
// 2개 이상 5개 이하의 "e"를 포함한 문자열을 검색합니다.
// ["odee", "odeeeee"]
| 는 or 조건으로 검색하여 | 의 왼쪽 또는 오른쪽의 검색 결과를 반환합니다.
"Cc Oo Dd Ee".match(/O|D/g); // ["O", "D"]
"Cc Oo Dd Ee".match(/c|e/g); // ["c", "e"]
"Cc Oo Dd Ee".match(/D|e/g); // ["D", "e"]
"Ccc Ooo DDd EEeee".match(/D+|e+/g); // + 는 1번 이상 반복을 의미하기 때문에
// ["DD", "eee"] 를 반환합니다.
대괄호 [] 안에 명시된 값을 검색합니다.
[abc] // a or b or c 를 검색합니다. or(|) Operator 로 작성한 a|b|c 와 동일하게 작동합니다.
[a-c] // [abc] 와 동일합니다. - 로 검색 구간을 설정할 수 있습니다.
"Ccc Ooo DDd EEeee".match(/[CD]+/g); // [] 에 + 등의 기호를 함께 사용할 수도 있습니다.
// C or D 가 한 번 이상 반복된 문자열을 반복 검색하기 때문에
// ["C", "DD"] 가 반환됩니다.
"Ccc Ooo DDd EEeee".match(/[co]+/g); // ["cc", "oo"]
"Ccc Ooo DDd EEeee".match(/[c-o]+/g); // - 때문에 c ~ o 구간을 검색하여
// ["cc", "oo", "d", "eee"] 가 반환됩니다.
"AA 12 ZZ Ad %% Az !# dd 54 zz".match(/[A-Za-z]+/g);
// a~z 또는 A~Z 에서 한 번 이상 반복되는 문자열을 반복 검색하기 때문에
// ["AA", "ZZ", "Ad", "Az", "dd", "zz"] 를 반환합니다.
"AA 12 ZZ Ad %% Az !# dd 54 zz".match(/[A-Z]+/gi);
// flag i 는 대소문자를 구분하지 않기 때문에 위와 동일한 결과를 반환합니다.
// ["AA", "ZZ", "Ad", "Az", "dd", "zz"]
"AA 12 ZZ Ad %% Az !# dd 54 zz".match(/[0-9]+/g);
// 숫자도 검색 가능합니다.
// ["12", "54"]
"aAbB$#67Xz@9".match(/[^a-zA-Z]+/g);
// [] 안에 ^ 를 사용하면 anchor 로서의 문자열의 처음을 찾는것이 아닌
// 부정을 나타내기 때문에 [] 안에 없는 값을 검색합니다.
// ["$#67", "@9"]
\d 의 d 는 digit 을 의미하며 0 ~ 9 사이의 숫자 하나를 검색합니다. [0-9] 와 동일합니다.
\D 는 not Digit 을 의미하며, 숫자가 아닌 문자 하나를 검색합니다. [^0-9] 와 동일합니다.
"abc34".match(/\d/); // ["3"]
"abc34".match(/[0-9]/) // ["3"]
"abc34".match(/\d/g); // ["3", "4"]
"abc34".match(/[0-9]/g) // ["3", "4"]
"abc34".match(/\D/); // ["a"]
"abc34".match(/[^0-9]/); // ["a"]
"abc34".match(/\D/g); // ["a", "b", "c"]
"abc34".match(/[^0-9]/g); // ["a", "b", "c"]
\w 는 알파벳 대소문자, 숫자, (underbar) 중 하나를 검색합니다. [a-zA-Z0-9]와 동일합니다.
\W 는 알파벳 대소문자, 숫자, (underbar)가 아닌 문자 하나를 검색합니다. [^a-zA-Z0-9]와 동일합니다.
"ab3_@A.Kr".match(/\w/); //["a"]
"ab3_@A.Kr".match(/[a-zA-Z0-9_]/) // ["a"]
"ab3_@A.Kr".match(/\w/g); //["a", "b", "3", "_", "A", "K", "r"]
"ab3_@A.Kr".match(/[a-zA-Z0-9_]/g) // ["a", "b", "3", "_", "A", "K", "r"]
"ab3_@A.Kr".match(/\W/); // ["@"]
"ab3_@A.Kr".match(/[^a-zA-Z0-9_]/); // ["@"]
"ab3_@A.Kr".match(/\W/g); // ["@", "."]
"ab3_@A.Kr".match(/[^a-zA-Z0-9_]/g); // ["@", "."]
()는 그룹으로 묶는다는 의미 이외에도 다른 몇 가지 의미가 더 있습니다. 하나하나 살펴보겠습니다.
표현식의 일부를 ()로 묶어주면 그 안의 내용을 하나로 그룹화할 수 있습니다. 아래 예시를 통해 그룹화와 그렇지 않은 결과의 차이를 비교해 봅시다.
let co = 'coco';
let cooo = 'cooocooo';
co.match(/co+/); // ["co", index: 0, input: "coco", groups: undefined]
cooo.match(/co+/); // ["cooo", index: 0, input: "cooocooo", groups: undefined]
co.match(/(co)+/); // ["coco", "co", index: 0, input: "coco", groups: undefined]
cooo.match(/(co)+/); // ["co", "co", index: 0, input: "cooocooo", groups: undefined]
co+ 는 "c"를 검색하고 + 가 "o"를 1회 이상 연속으로 반복되는 문자를 검색해 주기 때문에 "cooo"가 반환되었습니다. 하지만 (co)+ 는 "c" 와 "o" 를 그룹화하여 "co"를 단위로 1회 이상 반복을 검색하기 때문에 "coco"가 반환되었습니다. 여기서 특이한 점은 일치하는 문자열로 반환된 결과가 2개입니다. 이제 이 이유에 대해 알아봅니다.
() 로 그룹화한다고 하였고, 이를 캡처한다 라고 합니다. 그럼 아래 예시를 통해 캡처했을 경우의 작동방식을 확인합니다.
co.match(/(co)+/); // ["coco", "co", index: 0, input: "coco", groups: undefined]
한가지 예시를 더 살펴보겠습니다. 아래 예시 코드의 검색 및 결과 반환 순서는 다음과 같습니다.
"2021code".match(/(\d+)(\w)/);
// ["2021c", "2021", "c", index: 0, input: "2021code", groups: undefined]
캡처된 값은 replace() 메소드를 사용하여 문자 치환 시 참조 패턴으로 사용될 수 있습니다. 아래 예시를 살펴보세요. 우선 첫 번째 (\w+) 가 code 를 캡처하고, 두 번째 (\w+) 가 states 를 캡처합니다. (/(\w+)\ 와 (\w+)/\사이의 . 은 . 앞에 역슬래시가 사용되었기 때문에 '임의의 한 문자'가 아닌 기호로서의 온점 . 을 의미합니다.) 각 캡처된 값은 첫 번째는 $1 이 참조, 두 번째는 $2 이 참조하기 때문에 이 참조된 값을 "$2.$1" 이 대체하게 되어 code 와 states 가 뒤바뀐 "states.code" 가 반환됩니다.
"code.states".replace(/(\w+)\.(\w+)/, "$2.$1"); //states.code
() 를 사용하면 그룹화와 캡처를 한다고 하였습니다. 하지만 (?:)로 사용하면 그룹은 만들지만 캡처는 하지 않습니다.
let co = 'coco';
co.match(/(co)+/); // ["coco", "co", index: 0, input: "coco", groups: undefined]
co.match(/(?:co)+/);
// ["coco", index: 0, input: "coco", groups: undefined]
// 위 "캡처" 예시의 결괏값과 비교해 보시기 바랍니다.
(?=) 는 검색하려는 문자열에 (?=여기) 에 일치하는 문자가 있어야 (?=여기) 앞의 문자열을 반환합니다.
"abcde".match(/ab(?=c)/);
// ab 가 c 앞에 있기 때문에 ["ab"] 를 반환합니다.
"abcde".match(/ab(?=d)/);
// d 의 앞은 "abc" 이기 때문에 null을 반환합니다.
(?!) 는 (?=) 의 부정입니다.
"abcde".match(/ab(?!c)/); // null
"abcde".match(/ab(?!d)/); // ["ab"]
패턴 | 설명 |
---|---|
^ab | 줄(Line) 시작에 있는 ab와 일치 |
ab$ | 줄(Line) 끝에 있는 ab와 일치 |
ab? | b가 없거나 b와 일치 |
. | 임의의 한 문자와 일치 |
a|b | a 또는 b와 일치 |
{3} | 3개 연속 일치 |
{3,} | 3개 이상 연속 일치 |
{3,5} | 3개 이상 5개 이하(3~5개) 연속 일치 |
[abc] | a또는 b또는 c |
[a-z] | a부터 z사이의 문자 구간에 일치(영어 소문자) |
[A-Z] | a부터 Z사이의 문자 구간에 일치(영어 대문자) |
[0-9] | 0부터 9 사이의 문자 구간에 일치(숫자) |
[가-힣] | 가부터 힣 사이의 문자 구간에 일치(한글) |
\w | 63개의 문자(Word, 대소영문 52, 숫자 10 +_)에 일치. /[A-Za-z0-9]/ 와 동일 |
\b | 63개 문자에 일치 하지 않는 문자 경계(Boundary, 특수문자) |
\d | 숫자(Digit)에 일치 |
\s | \ 공백(Space, tab 등)에 일치 |
(?=) | 앞쪽 일치(Lookahead) |
(?<=) | 뒤쪽 일치(Lookbehind) |