[javaScript] 정규 표현식
정규식(정규 표현식, Regular Expression)이란
정규 표현식, 또는 정규식은 문자열에서 특정 문자 조합을 찾기 위한 패턴을 말한다 <mdn>
대표적인 예로, 아이디나 비밀번호가 일정조건에 들어오는지 체크할때 쓰일 수 있다.
아이디엔 영어 몇자리 이상이어야 한다던가, 비밀번호는 특수문자와 영어 대소문자를 포함해야한다던가...
이런것들을 체크하는 것을 "유효성 검사(validation check)"라고 하는데 이 정규식은 유효성 검사에서 요긴하게 쓸 수 있다.
정규표현식 생성하기
정규 표현식을 생성하는 두가지 방법은 아래와 같다.
//1️⃣리터럴 방식, 슬래시로 패턴을 감싸서 작성 => /패턴/
//바뀔일 없을 때, 고정적일때
const re = /abc/;
//2️⃣객체의 생성자 호출
//바뀔일 있을때, 가변적일때
const re = new RegExp("abc")
둘다 결과물은 같지만, 1️⃣리터럴 방식은 스크립트를 불러올 때 컴파일 되기 때문에, 바뀔 일이 없는 패턴의 경우에 좋으며,
2️⃣생성자 함수는 런타임에 컴파일 되기 때문에, 바뀔 수 있거나, 사용자 입력 등 외부 출처에서 가져오는 패턴의 경우에 좋다.
let tag = prompt("어떤 태그를 찾고 싶나요?", "h2");
let regexp = new RegExp(`<${tag}>`); // 프롬프트에서 "h2"라고 대답한 경우, /<h2>/와 동일한 역할을 합니다.
위와 같은 경우처럼 유저의 답변에 따라 다른 문자열을 찾고 싶을 때에 이런 식으로 작성할 수 있다.
플래그(flag)로 검색 조건 달기
플래그는 /패턴/ 끝에 붙어,
정규표현식을 생성할때 고급 검색을 위한 옵션을 설정할 수 있도록 하는 것이다.
하나만 찾을지, 모두 찾을지 등, 대소문자를 생각할지 말지 등등의 조건을 입력한다.
생성할때 리터럴 방식으로 작성했는가 생성자 호출 방식으로 작성했는가에 따라 적는 방법이 약간 다르다.
//flags에 플래그 문자열 들어감
const re1 = /abc/flags;
const re2 = new RegExp(/abc/,flags)
-플래그의 종류-
플래그 | 의미 | 설명 |
i | Ignore Case | 대소문자 구별하지 않고 검색 |
g | Global | 몇번 등장하던, 패턴에 일치하는 모든 문자 찾아냄 (안쓰면 매칭되는 첫 문자만 검색된다. 마치 querySelector처럼) |
m | Multi Line | 줄바뀜 있는 문자열에 대해서 검색 |
아래는 몇가지 사례이다.
const str1 = "bcabccabcabc";
str1.match(/a/);
//['a', index: 0, input: 'abcabccabcabc', groups: undefined]
//g옵션이 없어서 가장 처음 a하나만 찾음
str1.match(/a/g)
// ['a', 'a', 'a', 'a']
//g옵션이 있어서 등장하는 갯수대로 찾음
//------------------------------
const str2 = "AbcabccAbcabc";
str2.match(/a/);
//['a', index: 3, input: 'AbcabccAbcabc', groups: undefined]
//i옵션 없어서 대문자는 무시하고 소문자a를 찾음
str.match(/a/i)
//['A', index: 0, input: 'AbcabccAbcabc', groups: undefined]
//i옵션 있어서 대문자A 부터 찾음
str.match(/a/ig)
//['A', 'a', 'A', 'a']
//i옵션이 있어서 대소문자 구분없이 g옵션으로 있는대로 다 찾음
//------------------------------
//m플래그는 특별히 아래서 설명할 앵커 '^' '$'의 작동방식에만 영행을 준다.
const str3 = `1st place: Winnie
2nd place: Piglet
3rd place: Eeyore`;
str3.match(/^\d/gm)
//['1', '2', '3']
str3.match(/^\d/g)
//['1']
//\d는 숫자를 의미한다. 숫자만 찾는다.
//^기호로 텍스트 시작문자를 검색할 수 있는데,
//여기에 m을 써주면 줄바꿈이 있다고 알려줘서 ^앵커가 줄바꿈 직후의 문자를 모두 찾는다.
//m플래그가 있으면,각 줄의 첫문자
//m이 없으면, 전체의 첫문자
const str4 = `Winnie: 1
Piglet: 2
Eeyore: 3`;
str4.match(/\d$/gm)
//['1', '2', '3']
str4.match(/\d$/g)
//['3']
//$기호로 텍스트 끝문자를 검색할 수 있는데,
//여기에 m을 써주면 줄바꿈이 있다고 알려줘서 $앵커가 한줄의 마지막 문자를 모두 찾는다.
//m플래그가 있으면,각 줄의 끝문자
//m이 없으면, 전체의 끝문자
str4.match(/\d\n/gm)
//['1\n', '2\n']
//'행의 끝'을 의미하는 $와 '줄바꿈'을 의미하는 \n의 차이
//마지막 줄 Eeyore: 3`은 줄바꿈은 일어나지 않으나, 행의 끝은 맞다.
m옵션과 앵커^,$ 그리고 \n 에대해 더 자세히 알고 싶다면
정규표현식 매칭 패턴(문자,숫자,기호 등)
매칭패턴 === 이런 형식에 일치하는 텍스트를 찾아줘!
패턴 | 의미 |
a-zA-Z | 영어 알파벳(-으로 범위 지정) |
ㄱ-ㅎ가-힣 | 한글 문자(-으로 범위 지정) |
0-9 | 숫자(-으로 범위 지정) |
. | 모든 문자열(숫자,한글,영어,특수기호,공백 모두. 단 줄바꿈❌) |
\d | 숫자 |
\D | 숫자가 아닌 것 (↔️\d) |
\w | 영어 알파벳, 숫자, 언더스코어(_) |
\W | \w가 아닌 것(영어 알파벳, 숫자, 언더스코어가 아닌 것) (↔️\w) |
\s | space 공백 |
\S | space 공백이 아닌것 (↔️\s) |
\특수기호 | 특수기호 \*\^\&\!\?\등... |
정규표현식 검색 패턴(AND,OR,StartWith,EndWith 등)
검색 패턴 === 이런 조건에 걸리는 텍스트를 찾아줘
기호 | 의미 |
| | OR |
[] | 괄호안의 문자들 중 하나 |
[^문자] | 괄호안의 문자를 제외한 것 |
^문자열 | 특정 문자열로 시작 (주의! 괄호 없음) |
문자열$ | 특정 문자열로 끝남 |
() | 그룹 검색 및 분류(match메서드에서 그룹별로 묶어줌) |
(?:패턴) | 그룹 검색(분류 ❌) |
\b | 단어의 처음/끝 |
\B | 단어의 처음/끝이 아님(↔️\b) |
정규표현식 갯수(수량)패턴
갯수 패턴 === n개 반복되는지 찾아줘
기호 | 의미 |
? | 최대 한번(없음 || 한개) |
* | 없거나 있거나 (없음 || 있음): 여러개 포함 |
+ | 최소 한개(한개 || 여러개) |
{n} | n개 |
{Min,} | 최소 Min개 이상 (주의! 끝에 쉼표(,)적어야 함) |
{Min,Max} | 최소 Min개 이상, 최대 Max개 이하 |
정규표현식 메서드
아래 메서드들 안에 정규표현식을 인자로 넣으면 패턴을 이용해 검사하고, 결과 값을 리턴한다.
기호 | 의미 |
"문자열".match(/정규표현식/플래그) | "문자열"에서 "정규표현식"에 매칭되는 항목들을 배열로 변환 |
"문자열".replace(/정규표현식/플래그,"대체문자열") | /정규표현식/에 매칭되는 항목을 "대체문자열"로 변환 |
"문자열".split(/정규표현식/플래그) | "문자열"을 /정규표현식/에 매칭되는 항목으로 쪼개어 배열로 반환 |
/정규표현식/플래그.test("문자열") | "문자열"이 "정규표현식"과 매칭되는 부분이 있으면 true, 아니면 false 반환 |
/정규표현식/플래그.exec("문자열") | match메서드와 유사(단, 무조건 첫번째 매칭 결과만 반환) |
아래의 여러가지 코드들을 보면 이해가 더 쉽다.
let str = "I love JavaScript";
let result = str.match(/Java(Script)/g);
alert( result[0] ); // JavaScript
alert( result.length ); // 1
let str = "I love JavaScript";
let result = str.match(/HTML/);
alert(result); // null
alert(result.length); // Error: Cannot read property 'length' of null
let result = str.match(/HTML/) || []; //매칭되는게 없으면 빈배열로 반환
alert(result); // []
'12, 34, 56'.split(/,\s*/) // ['12', '34', '56'] 공백(\s)가 있거나 없거나 무시
'12, 34, 56'.split(/,/) //['12', ' 34', ' 56']
'12, 34, 56'.split(/,/) //['12', ' 34', ' 56'] 공백space도 같이 잘림
// replace a dash by a colon
alert('12-34-56'.replace("-", ":")) // 12:34-56
// replace all dashes by a colon
alert( '12-34-56'.replace( /-/g, ":" ) ) // 12:34:56
let str = "html and css";
let result = str.replace(/html|css/gi, str => str.toUpperCase());
alert(result); // HTML and CSS
let str = "John Smith";
let result = str.replace(/(?<name>\w+) (?<surname>\w+)/, (...match) => {
let groups = match.pop();
return `${groups.surname}, ${groups.name}`;
});
alert(result); // Smith, John
let str = "I love JavaScript";
// these two tests do the same
alert( /love/i.test(str) ); // true
alert( str.search(/love/i) != -1 ); // true
let str = 'Hello, world!';
let regexp = /\w+/g; // without flag "g", lastIndex property is ignored
regexp.lastIndex = 5; // search from 5th position (from the comma)
alert( regexp.exec(str) ); // world
사용예시
1. 비밀번호 유효성 검사
function onlyNumberAndEnglish(str) {
return /^[A-Za-z][A-Za-z0-9]*$/.test(str);
}
//첫글자는 영문자 + 영어나 숫자 가능
function strongPassword(str) {
return ^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/.test(str);
}
//최소 8자 이상 + 최소 한개의 영문자 + 최소 한개의 숫자
function strongerPassword(str) {
return /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/.test(str);
}
//최소 8자 이상 + 최소 한개의 영문자 + 최소 한개의 숫자 + 최소 한개 특수문자(@$!%*#?&)
function strongestPassword(str) {
return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,10}$/.test(str);
}
//최소 8자 이상 + 최대 10자 이하 + 최소 한개의 소문자 + 최소 한개의 대문자 + 최소 한개의 숫자 + 최소 한개의 특수 문자(@$!%*#?&)
2. 전화번호 형식 체크
function phoneNumber(str) {
return /^\d{2,3}-\d{3,4}-\d{4}$/.test(str);
}
//숫자 2or3개 - 숫자 3or4개 - 숫자 4개
문제에서 응용하기 01
*문제
문자열을 입력받아 문자열 내에 아래 중 하나가 존재하는지 여부를 리턴해야 합니다.
- 'a'로 시작해서 'b'로 끝나는 길이 5의 문자열
- 'b'로 시작해서 'a'로 끝나는 길이 5의 문자열
*입력
- string 타입의 알파벳 문자열
*출력
- boolean 타입을 리턴해야 합니다.
*주의사항
- 대소문자를 구분하지 않습니다.
- 공백도 한 글자로 취급합니다.
- 'a'와 'b'는 중복해서 등장할 수 있습니다.
*입출력예시
let output = ABCheck('lane Borrowed');
console.log(output); // --> true
*풀이
function ABCheck(str) {
let regexp1 = /a...b/gi
let regexp2 = /b...a/gi
if(regexp1.test(str)||regexp2.test(str)){
return true
}else{return false}
}
++ 다른 풀이(정규표현식 사용 안한 ver)
function ABCheck(str) {
if (str === undefined) {
return false;
}
str = str.toLowerCase();
for (let i = 4; i < str.length; i++) {
if (
(str[i] === 'a' && str[i - 4] === 'b') ||
(str[i] === 'b' && str[i - 4] === 'a')
) {
return true;
}
}
return false;
}
문제에서 응용하기 02
*문제
문자열을 입력받아 아이소그램인지 여부를 리턴해야 합니다.
아이소그램(isogram)은 각 알파벳을 한번씩만 이용해서 만든 단어나 문구를 말합니다.
*입력
- string 타입의 공백이 없는 알파벳 문자열
*출력
- boolean 타입을 리턴해야 합니다.
*주의사항
- 빈 문자열을 입력받은 경우, true를 리턴해야 합니다.
- 대소문자는 구별하지 않습니다.
*입출력예시
let output = isIsogram('aba');
console.log(output); // false
output = isIsogram('Dermatoglyphics');
console.log(output); // true
output = isIsogram('moOse');
console.log(output); // false
*풀이
function isIsogram(str) {
return !/(\w).*\1/i.test(str)
}
정규식 뜯어보기
(\w) 문자 뒤에 . 줄바꿈 제외한 모든 문자가 * 있든 없든 많든 \1 그룹1과 같은게 또나오는 형식이 .test 있다면 true리턴하는데
그럼 아이소그램이 아니므로 이를 ! 반전 시켜준다.
여기서 \w를 그룹화()시킨것은 \1로 첫번째 그룹을 group1로 기억하고 참조할 수 있기 때문!
이 정규식이 이해가 가지 않아서 추가적으로 여러 자료들을 찾아봤는데,
그중 가장 도움이 되었던 엘리님의 강의 + 정리를 첨부한다. (https://github.com/dream-ellie/regex)
++ 다른 풀이(정규표현식 사용 안한 ver)
function isIsogram(str) {
if (str.length === 0) {
return true;
}
let cache = {};
let strLowered = str.toLowerCase();
for (let i = 0; i < strLowered.length; i++) {
if (cache[strLowered[i]]) {
return false;
}
cache[strLowered[i]] = true;
}
return true;
}
쨌든... 이걸 어차피 다 외울수는 없고 대충 이정도로 돌아간다는 걸 알아두고 그때그때 찾아서 써야 할듯 싶다
끝으로 정규식 검사하고 뜯어보기에 유명한 사이트 하나 첨부한다.
RegExr: Learn, Build, & Test RegEx
RegExr is an online tool to learn, build, & test Regular Expressions (RegEx / RegExp).
regexr.com