Programming/JavaScript

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

고고마코드 2022. 8. 2. 14:54
반응형

javascript 기반으로 작성합니다만, 플래그는 모든 언어에서 비슷하므로 플래그에 대한 이해에는 도움이 될 것입니다.
하단으로 내려갈수록 기존 플래그를 활용하는 부분도 있습니다. 원활한 이해를 위해 플래그 순서대로 읽어주세요!
정규식 패턴에 대한 내용이 궁금하다면 아래 글을 참고하세요.

예제로 정리한 정규식 패턴


1. 정규 표현식을 사용하는 방법

정규 표현식 선언 방법

// new RegExp('정규식', '플래그');
let regExp1 = new RegExp('hello', 'g');

// /정규식/플래그;
let regExp2 = /hello/g;
  • regExp1
    new RegExp('정규식', '플래그')는 Javascript에서 사용할 수 있는 객체입니다.

  • regExp2
    어느 곳에서나 사용 가능한 정규식을 사용하는 기본 방법은 /정규식/플래그입니다.
    정규식에는 우리가 지정할 조건(패턴)이 입력되고,
    정규식을 생성할 때 옵션을 지정하는 것이 플래그입니다.
    플래그에 대해서는 3번 순서에서 자세히 알아보겠습니다.


    간단한 예시로 사용하는 방법에 대해 추가적으로 설명합니다.

예시1) RegExp.test()

let regExp1 = new RegExp('hello', 'g');

var test1 = regExp1.test('Hello, world'); 
var test2 = regExp1.test('hello, world'); 
console.log('regExp1 : '+ test1 + ',', test2); 
// regExp1 : false, true

var test1 = regExp2.test('Hello, world'); 
var test2 = regExp2.test('hello, world'); 
console.log('regExp2 : '+ test1 + ',', test2);
// regExp2 : false, true

해당 정규식을 검색 용도로 활용한 예시입니다. 정규식 조건이 참인지 거짓인지 판별할 수 있습니다.

예시2) String.replace()

let regExp1 = new RegExp('hello', 'g');

var test1 = 'Hello world';
var test2 = 'hello world';
test1 = test1.replace(regExp1, '헬로우');
test2 = test2.replace(regExp1, '헬로우');

console.log(test1); // Hello world
console.log(test2); // 헬로우 world

해당 정규식을 치환 용도로 활용한 예시입니다. 정규식 조건이 참인 경우 지정한 문자열로 치환합니다.

예시3) String.match()

let regExp1 = new RegExp('hello', 'g');

var test1 = 'Hello world';
var test2 = 'hello world';
test1 = test1.match(regExp1);
test2 = test2.match(regExp1);

console.log(test1); // null
console.log(test2); // [ 'hello' ]

정규식 패턴과 일치하면 해당 패턴을 배열에 담아 반환합니다. 만약 일치하는 패턴이 없다면 null을 반환합니다.


2. 플래그란?

여러 프로그래밍 언어에서 정규 표현식을 사용할 때 플래그를 줄 수 있습니다. (언어별로 조금씩 달라요!)

좀 더 다양한 방법으로 정규 표현식의 검색 방식을 설정하기 위해 사용하는 것인데요, Javascript 외의 다른 언어를 사용하시면 개념만 이해하고 각 언어의 사용법에 따라 활용하면 될 것 같습니다.

예제로 좀 더 자세히 알아보겠습니다.


3. 플래그 i

i (Ignore Case) : 대소문자를 구별하지 않는다.

let regExp1 = /hello/;
let regExp2 = /hello/i;

var test1 = regExp1.test('Hello world');
var test2 = regExp2.test('Hello world');

console.log(test1); // false
console.log(test2); // true

test1은 'hello' 와 'Hello'는 일치하지 않기 때문에 결과는 false 입니다.

test2는 플래그(i)를 주었기 때문에 대소문자를 구분하지 않습니다. 그래서 결과는 true 입니다.


4. 플래그 - g

g (Global) : 전역 검색, 문자열 전체에서 패턴을 검색한다.

let regExp1 = /hello/;
let regExp2 = /hello/g;

var test1 = 'hello hello world';
var test2 = 'hello hello world';

test1 = test1.replace(regExp1, '헬로우');
test2 = test2.replace(regExp2, '헬로우');

console.log(test1); // 헬로우 hello world
console.log(test2); // 헬로우 헬로우 world

test1은 처음 발견한 'hello'를 '헬로우'로 치환했습니다.

test2는 플래그(g)를 주었기 때문에 문자열 전체에서 패턴을 검색합니다. 그러므로 문자열 내의 모든 'hello'를 '헬로우'로 치환했습니다.

플래그 여러개를 한 번에 쓸 수도 있어요!

  • ig (Ignore Case + Global) : 대소문자를 구별하지 않고, 전역 검색
let regExp1 = /hello/g;
let regExp2 = /hello/gi;

var test1 = 'hello Hello world';
var test2 = 'hello Hello world';

test1 = test1.replace(regExp1, '헬로우');
test2 = test2.replace(regExp2, '헬로우');

console.log(test1); // 헬로우 Hello world
console.log(test2); // 헬로우 헬로우 world

test1은 플래그(g)만 주었기 때문에 두 번째 Hello는 대소문자 구분으로 인해 치환하지 않습니다.

test2는 플래그(ig)를 주었기 때문에 대소문자를 구별하지 않고 모든 hello를 치환했습니다.


5. 플래그 m

m (Multi Line) : 문자열의 행이 여러개일 경우 행 별로 구분해서 패턴을 검색 (각 행 별로 패턴이 있는 경우 사용)

주로 행마다 특정 패턴이 있는 경우 사용합니다.
주로 앵커(`^`, `$`) 또는 전역 플래그(`g`)와 같이 쓰입니다.
앵커(`^`)는 문자열의 시작을 의미합니다. 
var test = `
hello hello
hello hello world`;

줄바꿈 되어 있는 문자열 test가 있습니다.

행 마다 문자열의 시작이 'hello'인 경우 '헬로우'로 변경하는 것이 목표입니다.

  • 예시1
let regExp1 = /^hello/;

test = test.replace(regExp1, '헬로우');
console.log(test);
hello hello
hello hello world

문자열의 시작이 'hello'인 경우 '헬로우'로 치환하고 싶었습니다만 아무것도 치환되지 않았습니다.

왜냐하면 문자열 test의 시작은 개행이기 때문입니다.

  • 예시2
let regExp2 = /^hello/m;

test = test.replace(regExp2, '헬로우');
console.log(test);
헬로우 hello
hello hello world

플래그(m)을 옵션으로 지정했습니다.

이번에는 '헬로우'로 치환됐지만 한 행만 치환되고 다음 행은 치환되지 않았어요.

  • 예시3
let regExp3 = /^hello/mg;

test = test.replace(regExp3, '헬로우');
console.log(test);
헬로우 hello
헬로우 hello world

플래그(mg)를 옵션으로 지정했습니다.

문자열의 행 마다 'hello'로 시작하는 문자열이 '헬로우'로 치환됐어요.

예시들을 보면 왜 주로 앵커 또는 전역 플래그(g)와 함께 쓰이는지 알 수 있어요.


6. 플래그 s

s (dotAll) : dot(.) 문자가 개행(\n)을 포함한 모든 문자에 패턴이 되도록 'dotAll' 모드 활성화

플래그(s)는 반드시 dot(.)과 함께 쓰여요!

dot(.)은 모든 문자를 뜻하는데, 아래 예를 들어 설명할게요.

  • dot(.) 예시
var test = 'hello';
let regExp = /./g;

test = test.replace(regExp, '헬로우 ');
console.log(test);
// 헬로우 헬로우 헬로우 헬로우 헬로우

dot(.)은 모든 문자를 나타낸다고 했어요. 즉, h, e, l, l, o 각 문자들이 dot(.) 패턴에 일치하기 때문에 모두 '헬로우'로 치환됩니다.

이제 플래그(s)를 살펴보면

var test = 'hello hello';

이런 문자열 test가 있다고 가정할게요.

첫 번째 "hello"랑 두 번째 "hello" 사이에 어떤 문자가 올 지 모르는 상황이에요.

그럼 이때 사용하기 좋은 패턴이 바로 dot(.)이겠죠?

  • 예시1) "hello hello"
var test = 'hello hello';
let regExp = /hello.hello/;

test = test.replace(regExp, '헬로우');
console.log(test);
// 헬로우

그런데 이번에는 hello 사이에 개행문자를 넣으면 어떻게 될까요?

  • 예시2) "hello\nhello"
var test = 'hello\nhello';
let regExp = /hello.hello/;

test = test.replace(regExp, '헬로우');
console.log(test);
hello
hello

패턴에 일치하지 않아 변환이 되지 않아요.

dot(.)은 모든 문자에 일치하지만, 개행문자에는 일치하지 않아요.

그러나 플래그(s) 옵션을 주면 개행문자도 포함시켜준답니다.

  • 예시3) 플래그(s)
var test = 'hello\nhello';
let regExp = /hello.hello/s;

test = test.replace(regExp, '헬로우');
console.log(test);
// 헬로우

플래그(s)는 반드시 dot(.)과 함께 쓰인다는 점을 알고 있으면 될 것 같네요!


7. 플래그 u

u (unicode) : 유니코드 패턴을 사용

4바이트 문자를 2바이트 문자 2개로 처리하지 않고, 문자 1개로 올바르게 처리할 수 있습니다.
그리고 `\p{...}`를 이용해 유니코드 프로퍼티를 패턴으로 사용할 수 있습니다.
예제를 보면 이해하기 쉬울 거예요.
우리가 흔히 쓰는 이모지를 패턴에 사용할 수 있을까요?
  • 예시1) 4바이트 이모지를 치환
var test = '😍';
let regExp = /./;

test = test.replace(regExp, '헬로우');
console.log(test);
// 헬로우�

일반적으로 정규식 패턴은 2바이트 기준으로 되어 있습니다.

그런데 문자열 test에 입력된 이모지는 4바이트에요.

그래서 이모지가 2바이트/2바이트 로 분해되어 "헬로우"로 치환되었지만 남은 2바이트는 저렇게 이상한 문자를 표현하게 되는 거죠. 마치 에러처럼요.

이런 문제를 해결하기 위해 플래그(u)를 사용합니다.

  • 예시2) 플래그(u)를 사용해 이모지 치환
var test = '😍';
let regExp = /./u;

test = test.replace(regExp, '헬로우');
console.log(test);
// 헬로우

이상한 문자가 남지 않고 이모지가 올바르게 치환된 것을 볼 수 있습니다.

  • 예시3-1) 유니코드 프로퍼티에 대한 이해
var test = '$, €, ¥';

이러한 통화($, €, ¥)를 나타내는 문구들이 있죠?

이 통화를 모두 '돈' 이라는 문자로 바꾸려면 어떻게 해야 할까요?

이 때 사용하는 것이 유니코드 프로퍼티입니다.

통화를 나타내는 유니코드 프로퍼티는 \p{Sc} 입니다.

  • 예시3-2) 유니코드 프로퍼티를 패턴에 사용
var test = '$, €, ¥';
let regExp = /\p{Sc}/g;

test = test.replace(regExp, '돈');
console.log(test);
// $, €, ¥

통화 유니코드 프로퍼티 \p{Sc}를 패턴으로 사용하려고 합니다.

그러나 유니코드 프로퍼티를 사용해도 플래그(u)를 옵션을 주지 않으면 패턴으로 사용할 수 없어요.

  • 예시3-3) 플래그(u)
var test = '$, €, ¥';
let regExp = /\p{Sc}/gu;

test = test.replace(regExp, '돈');
console.log(test);
// 돈, 돈, 돈

유니코드 프로퍼티에 대한 자세한 설명은 아래 참고자료 링크를 확인하세요.


8. 플래그 y

y (sticky) : 문자 내 특정 위치에서 검색을 진행하는 'sticky' 모드 활성화

플래그(y)는 반드시 lastIndex 속성과 함께 쓰입니다.

  • 예시1 - 문자 내 특정위치를 조회하고 싶은 상황
var test = 'ab?de';
let regExp = /./;

test = test.replace(regExp, '#');
console.log(test);
// #b?de

문자열 test에 3번째 문자에는 무엇이 올 지 모르는 상황이라고 가정할게요.

근데 저는 그 3번째 문자를 #으로 치환하고 싶어요. 그럼 어떻게 해야 할까요?

다행히도 패턴을 찾을 때 시작 위치를 정해줄 수 있는 방법이 있어요.

  • 예시2 - 플래그(y) 활용
var test = 'ab?de';
let regExp = /./y;
regExp.lastIndex = 2;

test = test.replace(regExp, '#');
console.log(test);
// ab#de

플래그(y) 옵션을 주고, 정규식 패턴에 lastIndex 속성에 인덱스 값을 입력하면 해당 인덱스의 문자부터 패턴을 찾습니다.

플래그(y)의 단점들

  • 단점1 - 플래그(y)는 플래그(g)와 함께 쓰면 플래그(g)와 lastIndex 속성 모두 무시됩니다.
    즉, 올바른 결과를 낼 수 없습니다.
var test = 'abcabc';
let regExp = /a/gy; 
regExp.lastIndex = 3;

test = test.replace(regExp, '#');
console.log(test);
// #bcabc

보다시피 플래그(g)도 무시되었고, lastIndex도 무시되었습니다.

  • 단점2 - 플래그(y)는 해당 위치부터 패턴에 일치하는 것 1개만 찾고 동작을 멈춥니다.
var test = 'abcabcabcabc';
let regExp = /a/y; 
regExp.lastIndex = 3;

test = test.replace(regExp, '#');
console.log(test);
// abc#bcabcabc

당연하지만 플래그(g) 옵션이 무시되므로 전역에서 검색할 수도 없어서 특정 위치부터 패턴에 일치하는 것 1개만 찾을 수 있습니다.

  • 단점3 - lastIndex 속성은 일회성입니다.
var test = 'abcabcabcabc';
let regExp = /a/y; 
regExp.lastIndex = 3;

var test1 = test.replace(regExp, '#');
console.log(test1);
// abc#bcabcabc

var test2 = test.replace(regExp, '#');
console.log(test2);
// abcabcabcabc

regExp.lastIndex = 3;
var test3 = test.replace(regExp, '#');
console.log(test3);
// abc#bcabcabc

보다시피 똑같은 regExp를 사용해서 치환했지만 두 번째 문자열 test2는 모든 문자 'a'가 치환되지 않았습니다. 일회성이므로 한 번 사용 후 lastIndex 속성은 제거되었기 때문에 아무런 문자도 치환되지 않았습니다.

그러므로 test3처럼 치환하기 전에 다시 lastIndex 속성을 주어야 하는 불편함이 있습니다.

플래그(y)의 문제를 해결하려면?

전후방탐색을 잘 활용하면 플래그(y)의 문제들을 해결할 수 있습니다.

관련 내용은 추후 작성하도록 하겠습니다.


9. 참고자료

  1. 패턴과 플래그

  2. 유니코드 관련


반응형