RegExp.escape() 마스터하기: 정규표현식 이스케이프 처리 완벽 가이드
오늘은 ECMAScript의 새로운 제안인 "RegExp escaping"에 대해 자세히 알아보겠습니다.
Jordan Harband와 Kevin Gibbons가 제안한 이 기능은 현재 stage 3 단계에 있는데요.
RegExp.escape() 함수를 사용하면 주어진 문자열을 정규표현식에서 안전하게 사용할 수 있도록 이스케이프 처리할 수 있습니다.
RegExp.escape()는 어떻게 동작하는가?
RegExp.escape(text)는 주어진 text 문자열과 정확히 일치하는 정규표현식 패턴을 생성합니다.
정규표현식에서 특별한 의미를 가지는 문자들은 그대로 사용할 수 없어서 이스케이프 처리가 필요한데요.
예를 들어보겠습니다:
> RegExp.escape('(*)')
'\\(\\*\\)'
여기서 각각의 정규표현식 백슬래시가 두 번 나타나는 것을 볼 수 있습니다.
하나는 실제 백슬래시이고, 다른 하나는 문자열 리터럴 내에서 이를 이스케이프 처리하는 용도입니다:
> '\\(\\*\\)' === String.raw`\(\*\)`
true
특별한 의미가 없는 일반 문자들은 이스케이프 처리가 필요 없습니다:
> RegExp.escape('_abc123')
'_abc123'
RegExp.escape()의 활용 사례
예제 1: 모든 텍스트 발생 교체하기
텍스트 검색과 교체는 이스케이프의 고전적인 사용 사례입니다:
function replacePlainText(str, searchText, replace) {
const searchRegExp = new RegExp(
RegExp.escape(searchText),
'gu'
);
return str.replace(searchRegExp, replace)
}
assert.equal(
replacePlainText('(a) and (a)', '(a)', '@'),
'@ and @'
);
다만 ES2021부터는 .replaceAll()을 사용할 수 있게 되었는데요:
assert.equal(
'(a) and (a)'.replaceAll('(a)', '@'),
'@ and @'
);
예제 2: 정규표현식의 일부가 주어진 텍스트와 일치해야 하는 경우
function removeUnquotedText(str, text) {
const regExp = new RegExp(
`(?<!")${RegExp.escape(text)}(?!")`,
'gu'
);
return str.replaceAll(regExp, '•');
}
assert.equal(
removeUnquotedText('"yes" and yes and "yes"', 'yes'),
'"yes" and • and "yes"'
);
이러한 방식은 따옴표로 둘러싸이지 않은 텍스트를 찾거나 개수를 세는 데도 활용할 수 있습니다.
이스케이프 처리 시 고려사항
RegExp.escape()가 반환하는 패턴은 오랫동안 유지될 수 있습니다.
따라서 미래의 정규표현식 기능들이 이 패턴의 작동을 방해하지 않도록 하는 것이 중요한데요.
이런 이유로 RegExp.escape()는 현재 특별한 문법으로 사용되는 문장부호만 이스케이프 처리하는 것이 아니라, 미래에 문법으로 사용될 수 있는 문자들도 이스케이프 처리합니다.
모든 플래그에서 작동하는 이스케이프
앞으로 도입될 /x 플래그는 이스케이프되지 않은 공백을 무시하게 되는데요.
따라서 공백도 이스케이프 처리가 필요합니다:
> RegExp.escape(' \t')
'\\x20\\t'
이스케이프된 문자는 최대한 짧게 표현하고 싶지만, /u와 /v 플래그로 활성화되는 기능은 사용할 수 없습니다.
따라서 다음과 같은 방식을 사용합니다:
- 일부 공백과 줄 종결자 문자는 다음과 같이 처리됩니다: \t \n \v \f \r
- 0xFF까지의 유니코드 코드 포인트는 ASCII 16진수 이스케이프를 사용합니다 (예: \x41은 A와 매칭)
- BMP(Basic Multilingual Plane)의 코드 포인트(0xFFFF까지)는 유니코드 코드 유닛 이스케이프를 사용합니다 (예: \u2028은 LINE SEPARATOR 문자와 매칭)
- 더 높은 코드 포인트는 두 개의 코드 유닛 이스케이프를 사용해야 합니다
모든 구문 컨텍스트에서 작동하는 이스케이프
정규표현식에는 여러 "컨텍스트"(중첩된 범위라고 생각하면 됩니다)가 있습니다:
- 최상위 레벨에서는 *, $ 같은 구문 문자를 이스케이프 해야 합니다.
- 문자 클래스([abc] 같은)에서는:
- 대부분의 최상위 레벨 구문은 이스케이프가 필요 없습니다 (*, $ 등)
- 다른 구문은 이스케이프가 필요합니다 (예: -(하이픈))
- /v 플래그에서는 &&, -- 같은 이중 문장부호도 이스케이프 해야 합니다
> RegExp.escape('$')
'\\$'
> RegExp.escape('=>')
'\\x3d\\x3e'
이스케이프된 텍스트 앞뒤의 구문 고려사항
정규표현식을 다음과 같이 구성하는 경우가 있습니다:
new RegExp('<regex pattern>' + RegExp.escape(text))
이때 RegExp.escape()의 결과가 앞에 오는 정규표현식 패턴에 영향을 주지 않도록 해야 하는데요. 몇 가지 중요한 고려사항이 있습니다:
- \0은 NULL 문자(U+0000)를 나타내며 십진수 숫자가 뒤따라오면 안 됩니다. 그래서 시작하는 십진수 숫자는 이스케이프 처리가 필요합니다:
> RegExp.escape('123')
'\\x3123'
- \1, \2 등은 번호가 매겨진 캡처 그룹에 대한 역참조입니다. 이스케이프된 텍스트가 이러한 역참조에 십진수를 추가하지 않도록 시작 숫자를 이스케이프 처리합니다.
- 제어 문자는 \cA (Ctrl-A)에서 \cZ (Ctrl-Z)까지 이런 방식으로 표현할 수 있습니다. 하지만 \c는 단독으로도 사용될 수 있어서 그대로 해석됩니다. 따라서 이스케이프된 텍스트는 ASCII 문자로 시작하면 안 됩니다:
> RegExp.escape('abc')
'\\x61bc'
- /u와 /v 플래그는 서로게이트 쌍을 하나의 단위로 처리합니다. 따라서 단독 서로게이트가 정규표현식 패턴의 앞뒤에 있는 단독 서로게이트와 결합되지 않도록 이스케이프 처리해야 합니다.
RegExp.escape() 구현체
- 다양한 JavaScript 플랫폼의 지원 현황은 MDN에서 확인할 수 있습니다
- Jordan Harband가 만든 npm의 폴리필을 사용할 수 있습니다
- 교육 목적으로 작성된 구현체도 있는데, 실용성보다는 가독성에 중점을 둔 버전입니다
이렇게 RegExp.escape()의 모든 측면을 자세히 살펴보았는데요.
정규표현식에서 특수 문자를 안전하게 처리하는 것이 얼마나 복잡한 작업인지 이해하실 수 있었을 것 같습니다.
이 기능은 아직 제안 단계에 있지만, 정규표현식을 더욱 안전하고 예측 가능하게 만들어줄 것으로 기대됩니다.
'Javascript' 카테고리의 다른 글
TypeScript 튜플의 모든 것: 실전 예제로 풀어보는 타입 활용법 (1) | 2025.02.13 |
---|---|
TypeScript의 템플릿 리터럴 타입: 타입 검사 중 파싱 및 활용 방법 (0) | 2025.02.13 |
리액트 폼 데이터 저장, 이제 Nuqs로 완벽하게 해결하세요! (0) | 2025.02.09 |
URL 쿼리 스트링과 React 리액트 상태 관리의 만남: `nuqs` 사용법 가이드 (0) | 2025.02.09 |
Vite React vs Next.js: 더 나은 선택은? Hono와 함께하는 최신 웹 개발 트렌드 (0) | 2025.02.09 |