• 레퍼런스
  • 자습서
  • 정규 표현식

    문자열은 카카오톡봇 뿐만 아니라 모든 개발에서 중요한 데이터 타입입니다. 문자열은 문자들의 집합이 되는 타입으로, 일련의 문자들을 다루는데 중심이 됩니다. 이번 편에서는 문자열을 다루는 강력한 도구인 정규 표현식(regular expression)에 대해 배웁니다.

    정규 표현식은 문자열에서 특정 패턴을 찾거나, 대체하거나, 분리하는 등의 작업을 수행할 수 있게 해줍니다. 정규 표현식을 사용하지 않더라도 해당 기능을 구현할 수 있지만, 다소 복잡해집니다. 정규 표현식을 사용하면 이러한 작업을 간결하고 효율적으로 수행할 수 있습니다.

    문자열이 유효한 주민등록번호의 형식인지를 검사하는 예제를 통해 설명하겠습니다. 주민등록번호는 6자리 숫자와 7자리 숫자가 하이픈(-)으로 구분된 패턴의 문자열입니다.

    123456-1234567

    우리가 정규 표현식 없이 패턴을 검사하려면 문자열을 분리하고 각 부분이 숫자인지 확인해야 합니다.

    function isVaildRRN(rrn) {
      // 앞 6자리 검사
      for (let i = 0; i < 6; i++) {
        if (!Number.isInteger(rrn[i])) return false;
      }
    
      // 하이픈(-) 검사
      if (rrn[6] !== '-') return false;
    
      // 뒤 7자리 검사
      for (let i = 7; i < 14; i++) {
        if (!Number.isInteger(rrn[i])) return false;
      }
    
      return True;
    }

    위 방법은 정규 표현식을 제외한 방법 중에서도 최선의 방법은 아니지만, 정규 표현식에 비해 코드가 길고 복잡합니다. 정규 표현식을 사용하면 아래와 같이 간단하게 구현할 수 있습니다.

    function isVaildRRN(rrn) {
      const regex = /^\d{6}-\d{7}$/;
      return regex.test(rrn);
    }

    정규 표현식 생성하기

    JavaScript에서 정규 표현식은 RegExp 객체입니다. 그러므로 아래와 같이 정규 표현식을 만들 수 있습니다.

    const regex = new RegExp("^\\d{6}-\\d{7}$");

    하지만 이보다 더 좋은 방법이 있습니다. 우리가 문자열을 만들 때 new String() 대신 ""''을 사용하는 것처럼, 정규 표현식도 더 간단한 리터럴을 제공합니다. 슬래시(/ /) 사이에 정규 표현식의 패턴을 작성합니다.

    const regex = /^\d{6}-\d{7}$/;

    첫번째 방법에서는 정규 표현식의 패턴을 문자열 리터럴로 표현해 \d를 표현할 때 역슬래시를 두 번 입력해야 했습니다. 하지만 두번째 방법은 정규 표현식을 위한 리터럴로 표현하므로 역슬래시를 한 번만 입력해도 됩니다. 정규 표현식에서 \d의 의미는 아래에서 확인할 수 있습니다.

    정규 표현식의 패턴 구문

    정규 표현식의 패턴은 다양한 구문을 사용하여 문자열의 특정 패턴을 정의합니다. 그러므로 각 구문이 어떤 의미를 가지는지 알아야 합니다. 하지만 이를 암기하려고 하지는 마세요. 필요할 때마다 참고하며 자연스럽게 숙지하는 것을 추천합니다.

    문자 클래스

    문자 클래스(character class)는 숫자, 알파벳 등 특정한 종류의 문자를 가리키는 구문입니다.

    정규 표현식의 문자 클래스
    [...]

    괄호 안에 있는 각 문자들을 가리킵니다.

    • /[1234]/는 "version 1.0-alpha"라는 문자열의 "1"을 가리킵니다.

    문자 사이에 하이픈(-)을 넣을 경우 문자 범위를 지정할 수 있습니다.

    • /[a-z]/는 "version 1.0-alpha"라는 문자열의 각 소문자 알파벳들을 가리킵니다.

    하이픈을 가장 처음 또는 가장 끝에 위치시킬 경우, 하이픈은 범위 지정을 위한 기호가 아닌 하이픈 문자 자체로 취급합니다.

    • /[-1234]/ 또는 /[1234-]/는 "version 1.0-alpha"라는 문자열의 "1", "-"을 가리킵니다.

    괄호 안의 맨 처음 글자가 ^일 경우 괄호 안에 있는 문자들을 제외한 모든 글자를 가리키게 됩니다.

    • /[^a-z]/는 "version 1.0-alpha"라는 문자열의 " ", "1", ".", "0", "-"을 가리킵니다.
    .

    텍스트 줄의 끝을 나타내는 문자(\n, \r, \u2028, \u2029)를 제외한 모든 단일 문자를 가리킵니다. 문자 클래스 [...] 내에 있을 경우 마침표 문자 자체로 취급됩니다.

    • /.e/는 "Operating System"라는 문자열의 "pe", "te"을 가리킵니다.
    \d

    아라비아 숫자 문자를 가리킵니다. /[0-9]/와 동일합니다.

    • /\d/는 "July, 08"라는 문자열의 "0", "8"을 가리킵니다.
    \D

    아라비아 숫자 문자를 제외한 문자를 가리킵니다. /[0-9]/와 동일합니다.

    • /\d/는 "July, 08"라는 문자열의 "0", "8"을 제외한 문자들을 가리킵니다.
    \w

    기본 라틴 알파벳과 아라비아 숫자, 언더바(_)를 가리킵니다. /[A-Za-z0-9_]/와 동일합니다.

    • /\w/는 "July, 08"라는 문자열의 ",", " "을 제외한 문자들을 가리킵니다.
    \W

    기본 라틴 알파벳과 아라비아 숫자, 언더바(_)를 제외한 문자를 가리킵니다. /[^A-Za-z0-9_]/와 동일합니다.

    • /\W/는 "July, 08"라는 문자열의 ",", " "을 가리킵니다.
    \s

    공백 문자(스페이스, 탭, 폼 피드, 라인 피드, 그 외 유니코드 공백 문자)를 가리킵니다. /[\f\n\r\t\v\u0020\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/와 동일합니다.

    • /\s./는 "Android OS"라는 문자열의 " O"을 가리킵니다.
    \S

    공백 문자(스페이스, 탭, 폼 피드, 라인 피드, 그 외 유니코드 공백 문자)를 제외한 문자를 가리킵니다. /[^\f\n\r\t\v\u0020\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/와 동일합니다.

    • /\S./는 "Android OS"라는 문자열의 "An"을 가리킵니다.
    \t

    탭 문자를 가리킵니다.

    \r

    캐리지 리턴을 가리킵니다.

    \n

    줄바꿈 문자를 가리킵니다.

    \v

    수직 탭 문자를 가리킵니다.

    \f

    폼 피드를 가리킵니다.

    [\b]

    백스페이스(backspace) 문자를 가리킵니다.

    \0

    NUL 문자를 가리킵니다. (오타가 아닙니다!)

    \cX

    캐럿 표기법을 사용해 제어 문자를 가리킵니다. X 는 알파벳 대문자(A-Z; U+0001U+001A)입니다.

    \xhh

    2자리 16진수 코드 hh 에 대응하는 문자를 가리킵니다.

    \uhhhh

    4자리 16진수 코드 hhhh 에 대응하는 UTF-16 코드 유닛을 가리킵니다.

    \

    \b, \w와 같이 특정한 문자를 가리키는 코드 클래스를 만드는데 사용됩니다. 한편, *와 같이 그 자체로 정규 표현식에서 특정 문자를 가리키는 경우, \*와 같이 씀으로써 "*" 문자 자체를 표현하는 이스케이프 문자로 사용할 수 있습니다.

    • /\d \* \d/는 "8 * 2"와 일치합니다.
    x|y

    문자열 x 또는 y를 가리킵니다.

    • /Apple|Banana/는 "Apple juice"의 "Apple", "Banana milk"의 "Banana"와 일치합니다.

    Assertion

    Assertion은 특정 위치에 있는 문자열을 가리키는 구문입니다.

    정규 표현식의 assertion
    ^

    문자열의 맨 처음에 존재하는 문자열를 가리킵니다.

    • /^i/는 "identity"라는 문자열의 맨 처음 글자 "i"를 가리킵니다.
    • /^i/는 "apple"라는 문자열에서 아무것도 가리키지 않습니다. 맨 처음의 글자가 "i"가 아니기 때문입니다.
    $

    문자열의 맨 끝에 존재하는 문자열을 가리킵니다.

    • /r$/는 "doctor"라는 문자열의 맨 끝 글자 "r"을 가리킵니다.
    • /r$/는 "student"라는 문자열에서 아무것도 가리키지 않습니다. 맨 끝의 글자가 "r"이 아니기 때문입니다.
    \b

    단어의 경계에 존재하는 문자열을 가리킵니다.

    • /\bd/는 "I am developer"라는 문자열에서 "developer" 단어의 맨 처음 글자 "d"를 가리킵니다.
    • /er\b/는 "I am developer"라는 문자열에서 "developer" 단어의 맨 끝 글자 "er"를 가리킵니다.
    • /vel\b/는 "I am developer"라는 문자열에서 아무것도 가리키지 않습니다. "vel"이란 글자는 해당 문자열에서 어떤 단어의 끝에 위치하지 않기 때문입니다.
    • /z\b/는 "I am developer"라는 문자열에서 아무것도 가리키지 않습니다. 문자열의 어떤 단어("I", "am", "developer")에서도 z로 끝나는 경우가 없기 때문입니다.
    \B

    단어의 경계가 아닌 곳에 존재하는 문자열을 가리킵니다.

    • /c\B/는 "A dog is cute"라는 문자열에서 "cute" 단어의 맨 처음 글자 "c"를 가리킵니다. "c" 다음이 단어의 경계가 아니기 때문입니다.
    • /\Bs/는 "A dog is cute"라는 문자열에서 "is" 단어의 맨 끝 글자 "s"를 가리킵니다. "s" 앞이 단어의 경계가 아니기 때문입니다.
    • /\Bd/는 "A dog is cute"라는 문자열에서 아무것도 가리키지 않습니다. 해당 문자열에 유일하게 존재하는 ("dog"의) "d"의 앞은 단어의 경계이기 때문입니다.
    x(?=y)

    "y"가 바로 다음 글자로 오는 "x"를 가리킵니다.

    • /t(?=i)/는 "identity"라는 문자열에서 5번째 글자 "t"를 가리킵니다.
    • /p(?=lication)/는 "application"라는 문자열에서 3번째 글자 "p"를 가리킵니다.
    x(?!y)

    "y"가 바로 다음 글자로 오지 않는 "x"를 가리킵니다.

    • /t(?!i)/는 "identity"라는 문자열에서 7번째 글자 "t"를 가리킵니다.
    • /p(?!lication)/는 "application"라는 문자열에서 2번째 글자 "p"를 가리킵니다.
    (?<=y)x

    "y"가 바로 앞 글자인 "x"를 가리킵니다.

    • /(?<=j)e/는 "objective"라는 문자열에서 4번째 글자 "e"를 가리킵니다.
    • /(?<=2)8/는 "2.71828"라는 문자열에서 7번째 글자 "8"를 가리킵니다.
    (?<!y)x

    "y"가 바로 앞 글자로 오지 않는 "x"를 가리킵니다.

    • /(?<!j)e/는 "objective"라는 문자열에서 9번째 글자 "e"를 가리킵니다.
    • /(?<!2)8/는 "2.71828"라는 문자열에서 5번째 글자 "8"를 가리킵니다.

    그룹/역참조

    그룹은 정규 표현식의 특정한 패턴을 구분하는 장치입니다. 그룹의 패턴과 일치하는 값을 기억(캡쳐)해 사용하기도, 정규 표현식 내에서 역참조할 수도 있습니다.

    정규 표현식의 그룹/역참조
    (x)

    x 를 가리키며 기억합니다(그룹을 캡쳐합니다). RegExp.prototype.exec() 또는 String.prototype.match() 등의 반환값(일치 결과)에 양의 정수 인덱스([1], [2], ...)를 사용해 캡쳐된 결과를 참조할 수 있습니다.

    주의

    그룹을 캡쳐할 경우 성능 저하가 있을 수 있습니다. 캡쳐할 필요가 없을 경우 비캡쳐 그룹을 사용하세요.

    • /(\d)-(\w)/는 "6-A"와 일치합니다. 첫번째 그룹은 "6", 두번째 그룹은 "A"입니다. 다음과 같이 캡쳐된 값을 참조할 수 있습니다.
    /(\d)-(\w)/.exec("6-A")[1]; // "6"
    /(\d)-(\w)/.exec("6-A")[2]; // "A"
    "6-A".match(/(\d)-(\w)/)[1]; // "6"
    "6-A".match(/(\d)-(\w)/)[2]; // "A"
    (?<Name>x)

    x 를 가리키며 Name 을 이름으로 기억합니다(명명된 캡쳐). 일치 결과에 양의 정수 인덱스를 사용하거나, 일치 결과의 .groups 프로퍼티에 접근에 참조할 수 있습니다.

    • /(?<number>\d)-(?<char>\w)/는 "6-A"와 일치합니다. 첫번째 그룹(number)은 "6", 두번째 그룹(char)은 "A"입니다. 다음과 같이 캡쳐된 값을 참조할 수 있습니다.
    /(?<number>\d)-(?<char>\w)/.exec("6-A").groups.number; // "6"
    /(\d)-(\w)/.exec("6-A").groups.char; // "A"
    "6-A".match(/(\d)-(\w)/).groups.number; // "6"
    "6-A".match(/(\d)-(\w)/).groups.char; // "A"
    (?:x)

    x 를 가리키고 기억하지 않습니다. 그러므로 일치 결과를 통해 그룹의 값을 참조할 수 없습니다.

    • /(?:\d)-(\w)/는 "6-A"와 일치합니다. 캡쳐된 그룹은 "A" 하나입니다.
    (?flags:x)

    그룹 내의 x 에 한해 플래그 flags 를 적용합니다. 플래그를 적용 해제하고자 할 경우 (?-flags:x)와 같이 사용합니다. 특정 플래그는 적용하고 특정 플래그는 적용 해제하고자 할 경우 (?flags_enabled-flags_disabled:x)와 같이 사용합니다.

    \n

    그룹을 역참조합니다. n 은 양의 정수(1, 2, ...)이며, 역참조할 그룹의 인덱스입니다.

    • /(\d)-\1/은 "2-2"와 일치합니다.
      • (\d)가 문자열의 첫번째 "2"를 캡쳐합니다.
      • \1은 캡쳐된 문자열 "2"가 됩니다. 그러므로 "2-2"와 일치합니다. 반면, "2-5"와는 일치하지 않습니다.
    \k<Name>

    명명된 그룹을 역참조합니다. Name 은 역참조할 그룹의 이름입니다.

    수량자

    수량자는 여러 번 반복되는 패턴을 찾기 위한 정규 표현식의 요소입니다.

    정규 표현식의 수량자
    x*

    x 가 0번 이상 반복되는 문자열을 가리킵니다.

    • /yo*u/는 "yu", "you", "yoooooooooou" 모두와 일치합니다.
    x+

    x 가 1번 이상 반복되는 문자열을 가리킵니다.

    • /yo+u/는 "you", "yoooooooooou" 모두와 일치합니다. 반면, "yu"과는 일치하지 않습니다.
    x?

    x 가 0번 또는 1번 반복되는 문자열을 가리킵니다.

    • /yo?u/는 "yu", "you"와 일치합니다. 반면, "yoooou"과는 일치하지 않습니다.
    x?

    x 가 0번 또는 1번 반복되는 문자열을 가리킵니다.

    • /yo?u/는 "yu", "you"와 일치합니다. 반면, "yoooou"과는 일치하지 않습니다.
    x{n}

    xn 번 반복되는 문자열을 가리킵니다. n 은 0 이상의 정수입니다.

    • /yo{2}/는 "yooooooooou" 문자열의 "yoo"와 일치합니다. 반면, "you" 문자열에는 일치하는 결과가 없습니다.
    x{n,}

    xn 번 이상 반복되는 문자열을 가리킵니다. n 은 0 이상의 정수입니다.

    • /yo{2,}/는 "yooooooooou" 문자열의 "yooooooooo"와 일치합니다. 반면, "you" 문자열에는 일치하는 결과가 없습니다.
    x{n,m}

    xn 번 이상 m 번 이하 반복되는 문자열을 가리킵니다. nm 은 0 이상의 정수이며, nm 보다 작거나 같아야 합니다.

    • /yo{2,5}u/는 "yooooou" 문자열의 "yooooo"와 일치합니다. 반면, "yoooooooooou" 문자열에는 일치하는 결과가 없습니다.

    수량자 옆에 ?를 붙이면 수량자의 "탐욕적인" 특성을 지웁니다.

    x+ 수량자를 예로 들겠습니다. /b+/는 "aaabbbbbbbbbbcc" 문자열에 대해 모든 "b"가 이어져있는 "bbbbbbbbbb"와 일치합니다. 이는 패턴을 만족하는 최대한 긴 문자열을 찾으려는 특성, 즉 "탐욕적인" 특성 때문입니다.

    하지만 ?는 패턴을 만족하는 즉시 탐색을 중지합니다. /b+?/는 "aaabbbbbbbbbbcc" 문자열에 대해 "b"와 일치합니다. /b+/는 1개 이상의 "b"를 가리키는 패턴이므로 /b+?/는 1개의 "b"를 찾은 즉시 탐색을 중단하기 때문입니다.

    플래그

    플래그(flag)는 정규 표현식의 탐색에 추가적인 옵션을 부여합니다.

    정규 표현식의 플래그
    d메신저봇R 0.7.40-alpha.03 이상

    일치 결과의 indices 프로퍼티에 일치하는 패턴에 대한 인덱스 정보를 추가합니다.

    g

    패턴과 일치하는 모든 결과를 탐색합니다.

    i

    대소문자를 구분하지 않습니다.

    m

    여러 줄에 걸쳐 탐색합니다. 여러 줄의 문자열일 경우 줄바꿈을 기준으로 별개의 문자열로 나누어 탐색합니다.

    s

    줄바꿈 문자와 .이 일치합니다.

    u메신저봇R 0.7.40-alpha.03 이상

    패턴을 유니코드 코드 포인트의 시퀀스로 간주합니다.

    y메신저봇R 0.7.34a 이상

    탐색 대상이 되는 문자열의 특정 위치에서 탐색을 시작합니다.

    플래그는 위와 같이 하나의 소문자 알파벳 형태를 가집니다. 플래그를 적용하는 방법은 아래와 같습니다.

    new RegExp("\\d{2,6}", "g"); // g 플래그 적용
    new RegExp("\\d{2,6}", "gim"); // g, i, m 플래그 적용
    // 또는
    /\d{2,6}/g; // g 플래그 적용
    /\d{2,6}/gim; // g, i, m 플래그 적용

    정규 표현식 사용하기

    정규 표현식(RegExp 객체)은 문자열(String 객체)과 함께 사용하는 경우가 많습니다. RegExp 객체의 메소드와 정규 표현식과 관련된 String 객체의 메소드를 익히면 정규 표현식을 더 잘 활용할 수 있습니다.

    RegExp 객체의 메소드

    RegExp 객체의 메소드
    exec()

    인자로 전달된 문자열에서 정규 표현식의 패턴에 대한 일치 결과를 배열로 반환합니다. 일치하는 부분이 없는 경우 null을 반환합니다.

    /.{4}/.exec("test case"); // ["test"]
    /\d+/.exec("hello"); // null
    test()

    인자로 전달된 문자열이 정규 표현식의 패턴과 일치하는 부분이 존재하는지의 여부를 반환합니다.

    /.{4}/.exec("test case"); // True
    /\d+/.test("hello"); // false

    String 객체의 유용한 메소드

    정규 표현식과 관련된 String 객체의 메소드
    match()

    문자열에서 인자로 전달된 정규 표현식의 패턴과 일치하는 부분을 배열 형태의 일치 결과로 반환합니다. 일치하는 부분이 없는 경우 null을 반환합니다.

    "test case".match(/.{4}/); // ["test"]
    "hello".match(/\d+/); // null
    search()

    문자열에서 인자로 전달된 패턴과 일치하는 위치(인덱스)를 반환합니다. 일치하는 부분이 없는 경우 -1을 반환합니다.

    "test case".search(/.{4}/); // 0
    "hello".search(/\d+/); // -1
    replace()

    문자열의 특정 부분을 다른 문자열로 치환합니다. 인자로 문자열 뿐만 아니라 정규 표현식도 전달할 수 있습니다.

    "test case".replace(/.{4}/, "special"); // "special case"
    replaceAll()

    문자열의 모든 특정 부분을 다른 문자열로 치환합니다. 인자로 문자열 뿐만 아니라 정규 표현식도 전달할 수 있습니다.

    정보

    첫번째 인자에 정규 표현식을 사용할 때 g 플래그를 반드시 함께 사용해야 합니다.

    "test case".replace(/.e/g, "@"); // "@st ca@"