JavaScript에서 긴 정규 표현식을 여러 줄로 나누는 방법은 무엇입니까?


138

JSLint 규칙에 따라 각 줄 길이를 80 자로 유지하기 위해 JavaScript 코드에서 여러 줄로 분할하려는 매우 긴 정규 표현식이 있습니다. 읽기에 더 좋습니다. 패턴 샘플은 다음과 같습니다.

var pattern = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

4
전자 메일 주소의 유효성을 검사하려는 것 같습니다. 왜 그렇게하지 /\S+@\S+\.\S+/않습니까?
바트 키어

1
정규 표현식없이 또는 여러 개의 작은 정규 표현식으로이를 수행 할 수있는 방법을 찾아야 할 것입니다. 그것은 정규 표현식보다 훨씬 더 읽기 쉽습니다. 정규식이 약 20자를 초과하면 더 나은 방법 일 것입니다.
ForbesLindesay

2
요즘 와이드 모니터에서는 80 자 정도 쓸모 없습니까?
Oleg V. Volkov

7
@ OlegV.Volkov 아니요. 서버 룸의 가상 터미널 인 vim에서 분할 창을 사용하고있을 수 있습니다. 모두가 같은 뷰포트에서 코딩한다고 가정하는 것은 잘못입니다. 또한 줄을 80 자로 제한하면 코드를 더 작은 함수로 나눌 수 있습니다.
synic

글쎄, 나는 분명히 여기에서 이것을하고 싶어하는 동기를 본다. 일단이 정규 표현식이 Koolilnc에 의해 입증 된 것처럼 여러 줄로 나뉘어지면 즉시 읽을 수있는 자체 문서화 코드의 완벽한 예가됩니다. ¬_¬
Mark Amery

답변:


115

다음을 호출하여 문자열로 변환하고 표현식을 작성할 수 있습니다 new RegExp().

var myRE = new RegExp (['^(([^<>()[\]\\.,;:\\s@\"]+(\\.[^<>(),[\]\\.,;:\\s@\"]+)*)',
                        '|(\\".+\\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                        '[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\\.)+',
                        '[a-zA-Z]{2,}))$'].join(''));

노트:

  1. 식 리터럴 을 문자열 로 변환 할 때는 문자열 리터럴을 평가할 때 백 슬래시가 사용되므로 모든 백 슬래시를 이스케이프해야합니다 . (자세한 내용은 Kayo의 의견을 참조하십시오.)
  2. RegExp 수정자를 두 번째 매개 변수로 허용

    /regex/g => new RegExp('regex', 'g')

[ 추가 ES20xx (태그 된 템플릿)]

ES20xx에서는 태그가 지정된 템플릿을 사용할 수 있습니다 . 스 니펫을 참조하십시오.

노트 :

  • 여기 단점은 (항상 사용하는 정규 표현식 문자열에 일반 공백을 사용할 수 없다는 것입니다 \s, \s+, \s{1,x}, \t, \n등).

(() => {
  const createRegExp = (str, opts) => 
    new RegExp(str.raw[0].replace(/\s/gm, ""), opts || "");
  const yourRE = createRegExp`
    ^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|
    (\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|
    (([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`;
  console.log(yourRE);
  const anotherLongRE = createRegExp`
    (\byyyy\b)|(\bm\b)|(\bd\b)|(\bh\b)|(\bmi\b)|(\bs\b)|(\bms\b)|
    (\bwd\b)|(\bmm\b)|(\bdd\b)|(\bhh\b)|(\bMI\b)|(\bS\b)|(\bMS\b)|
    (\bM\b)|(\bMM\b)|(\bdow\b)|(\bDOW\b)
    ${"gi"}`;
  console.log(anotherLongRE);
})();


4
A new RegExp는 여러 줄 정규 표현식에 좋은 방법입니다. 배열을 결합하는 대신 문자열 연결 연산자를 사용할 수 있습니다.var reg = new RegExp('^([a-' + 'z]+)$','i');
dakab

43
주의 : 위의 대답을 사용하여 긴 정규 표현식 리터럴 을 여러 줄로 나눌 수 있습니다. 그러나 정규 표현식 리터럴 (으로 정의 됨 //)을 단순히 복사 하여 RegExp 생성자에 문자열 인수로 붙여 넣을 수 없기 때문에주의가 필요합니다 . 문자열 리터럴을 평가할 때 백 슬래시 문자가 사용되기 때문 입니다. 예 : /Hey\sthere/로 대체 할 수 없습니다 new RegExp("Hey\sthere"). 대신 new RegExp("Hey\\sthere")여분의 백 슬래시 를 주목하십시오! 따라서 나는 긴 정규 표현식 리터럴을 하나의 긴 줄에 남겨 두는 것을 선호합니다
Kayo

5
더 명확한 방법은 의미있는 하위 섹션을 보유하고 변수 를 문자열 또는 배열로 결합 하는 명명 된 변수를 만드는 것 입니다. 이를 통해 RegExp훨씬 이해하기 쉬운 방식으로 구성 할 수 있습니다 .
Chris Krycho

117

@KooiInc 응답을 확장 source하면 RegExp객체 의 속성을 사용하여 모든 특수 문자를 수동으로 이스케이프하지 않아도 됩니다.

예:

var urlRegex= new RegExp(''
  + /(?:(?:(https?|ftp):)?\/\/)/.source     // protocol
  + /(?:([^:\n\r]+):([^@\n\r]+)@)?/.source  // user:pass
  + /(?:(?:www\.)?([^\/\n\r]+))/.source     // domain
  + /(\/[^?\n\r]+)?/.source                 // request
  + /(\?[^#\n\r]*)?/.source                 // query
  + /(#?[^\n\r]*)?/.source                  // anchor
);

또는 .source속성을 반복하지 않으려면Array.map() 함수를 .

var urlRegex= new RegExp([
  /(?:(?:(https?|ftp):)?\/\/)/      // protocol
  ,/(?:([^:\n\r]+):([^@\n\r]+)@)?/  // user:pass
  ,/(?:(?:www\.)?([^\/\n\r]+))/     // domain
  ,/(\/[^?\n\r]+)?/                 // request
  ,/(\?[^#\n\r]*)?/                 // query
  ,/(#?[^\n\r]*)?/                  // anchor
].map(function(r) {return r.source}).join(''));

ES6에서는 맵 기능을 다음과 같이 줄일 수 있습니다. .map(r => r.source)


3
내가 찾던 것, 매우 깨끗합니다. 감사!
Marian Zagoruiko

10
이것은 긴 정규 표현식에 주석을 추가하는 데 정말 편리합니다. 그러나 동일한 행에 일치하는 괄호를 사용하면 제한됩니다.
Nathan S. Watson-Haigh

확실히, 이것! 각 하위 정규 표현식에 주석을 달 수있는 기능이 좋습니다.
GaryO

고맙게도, 소스를 정규식 함수로 만드는 데 도움이되었습니다
Code

매우 영리한. 고마워,이 아이디어는 많은 도움이되었습니다. 부수적으로 : 나는 모든 것을 더 깨끗하게하기 위해 함수로 캡슐화했습니다. combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))사용법 :combineRegex(/regex1/, /regex2/, ...)
Scindix

25

new RegExp모든 백 슬래시를 이스케이프해야하므로 문자열을 입력하는 것은 어색합니다. 더 작은 정규식을 작성하고 연결할 수 있습니다.

이 정규식을 분할합시다

/^foo(.*)\bar$/

우리는 나중에 더 아름답게 만드는 기능을 사용할 것입니다

function multilineRegExp(regs, options) {
    return new RegExp(regs.map(
        function(reg){ return reg.source; }
    ).join(''), options);
}

그리고 이제 록하자

var r = multilineRegExp([
     /^foo/,  // we can add comments too
     /(.*)/,
     /\bar$/
]);

비용이 들기 때문에 실제 정규 표현식을 한 번만 빌드 한 다음 사용하십시오.


추가로 이스케이프 처리 할 필요가 없을뿐만 아니라 하위 정규 표현식에 대한 특수 구문 강조 표시를 유지하는 것이 매우 좋습니다!
quezak

한 가지주의 사항 : 하위 정규식이 자체 포함되어 있는지 확인하거나 각각을 새 대괄호 그룹으로 묶어야합니다. 예 : multilineRegExp([/a|b/, /c|d])결과는 /a|bc|d/이지만 의미는 (a|b)(c|d)입니다.
quezak

6

여기에는 좋은 답변이 있지만, 완전성을 위해 누군가 프로토 타입 체인 을 통해 Javascript의 상속 상속 기능을 언급해야합니다 . 이 같은 아이디어는 아이디어를 보여줍니다.

RegExp.prototype.append = function(re) {
  return new RegExp(this.source + re.source, this.flags);
};

let regex = /[a-z]/g
.append(/[A-Z]/)
.append(/[0-9]/);

console.log(regex); //=> /[a-z][A-Z][0-9]/g


이것이 가장 좋은 대답입니다.
parttimeturtle

6

템플릿 리터럴 의 놀라운 세계 덕분에 이제 ES6에서 크고 여러 줄로 구성되며 주석이 포함 된 의미있는 중첩 정규식을 작성할 수 있습니다 .

//build regexes without worrying about
// - double-backslashing
// - adding whitespace for readability
// - adding in comments
let clean = (piece) => (piece
    .replace(/((^|\n)(?:[^\/\\]|\/[^*\/]|\\.)*?)\s*\/\*(?:[^*]|\*[^\/])*(\*\/|)/g, '$1')
    .replace(/((^|\n)(?:[^\/\\]|\/[^\/]|\\.)*?)\s*\/\/[^\n]*/g, '$1')
    .replace(/\n\s*/g, '')
);
window.regex = ({raw}, ...interpolations) => (
    new RegExp(interpolations.reduce(
        (regex, insert, index) => (regex + insert + clean(raw[index + 1])),
        clean(raw[0])
    ))
);

이것을 사용하여 다음과 같이 정규 표현식을 작성할 수 있습니다.

let re = regex`I'm a special regex{3} //with a comment!`;

출력

/I'm a special regex{3}/

아니면 여러 줄은 어떻습니까?

'123hello'
    .match(regex`
        //so this is a regex

        //here I am matching some numbers
        (\d+)

        //Oh! See how I didn't need to double backslash that \d?
        ([a-z]{1,3}) /*note to self, this is group #2*/
    `)
    [2]

hel깔끔한 출력 !
"내가 실제로 줄 바꿈을 검색해야하는 경우?", 그럼 사용 \n바보!
내 Firefox 및 Chrome에서 작업 중입니다.


"좀 더 복잡한 것은 어떻습니까?"
물론, 내가 작업하고있는 JS 파서를 파괴하는 객체가 있습니다 .

regex`^\s*
    (
        //closing the object
        (\})|

        //starting from open or comma you can...
        (?:[,{]\s*)(?:
            //have a rest operator
            (\.\.\.)
            |
            //have a property key
            (
                //a non-negative integer
                \b\d+\b
                |
                //any unencapsulated string of the following
                \b[A-Za-z$_][\w$]*\b
                |
                //a quoted string
                //this is #5!
                ("|')(?:
                    //that contains any non-escape, non-quote character
                    (?!\5|\\).
                    |
                    //or any escape sequence
                    (?:\\.)
                //finished by the quote
                )*\5
            )
            //after a property key, we can go inside
            \s*(:|)
      |
      \s*(?={)
        )
    )
    ((?:
        //after closing we expect either
        // - the parent's comma/close,
        // - or the end of the string
        \s*(?:[,}\]=]|$)
        |
        //after the rest operator we expect the close
        \s*\}
        |
        //after diving into a key we expect that object to open
        \s*[{[:]
        |
        //otherwise we saw only a key, we now expect a comma or close
        \s*[,}{]
    ).*)
$`

출력 /^\s*((\})|(?:[,{]\s*)(?:(\.\.\.)|(\b\d+\b|\b[A-Za-z$_][\w$]*\b|("|')(?:(?!\5|\\).|(?:\\.))*\5)\s*(:|)|\s*(?={)))((?:\s*(?:[,}\]=]|$)|\s*\}|\s*[{[:]|\s*[,}{]).*)$/

그리고 작은 데모로 실행합니까?

let input = '{why, hello, there, "you   huge \\"", 17, {big,smelly}}';
for (
    let parsed;
    parsed = input.match(r);
    input = parsed[parsed.length - 1]
) console.log(parsed[1]);

성공적으로 출력

{why
, hello
, there
, "you   huge \""
, 17
,
{big
,smelly
}
}

인용 된 문자열을 성공적으로 캡처합니다.
나는 그것을 Chrome과 Firefox에서 테스트했으며, 대접을합니다!

경우 호기심 당신은 내가 무엇을하고 있었는지 체크 아웃 할 수 있습니다 , 그리고 그것의 시범 .
Firefox는 역 참조 또는 명명 된 그룹을 지원하지 않기 때문에 Chrome에서만 작동합니다. 따라서이 답변에 제공된 예는 실제로 중성 버전이며 유효하지 않은 문자열을 받아들이는 데 쉽게 속일 수 있습니다.


1
이것을 NodeJS 패키지로 내보내는 것을 생각해야합니다. 놀라운 일입니다
rmobis

1
내가 직접 한 적이 없지만 zellwk.com/blog/publish-to-npm과 같은 철저한 자습서가 있습니다 . 페이지 끝에서 np를 확인하는 것이 좋습니다. 나는 그것을 사용한 적이 없지만 Sindre Sorhus는 이런 것들을 가진 마술사이므로 그것을 지나치지 않을 것입니다.
rmobis

4

위의 정규 표현식에 검은 슬래시가 누락되어 제대로 작동하지 않습니다. 그래서 정규식을 편집했습니다. 이메일 유효성 검사에 99.99 % 작동하는이 정규식을 고려하십시오.

let EMAIL_REGEXP = 
new RegExp (['^(([^<>()[\\]\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\.,;:\\s@\"]+)*)',
                    '|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                    '[0-9]{1,3}\])|(([a-zA-Z\\-0-9]+\\.)+',
                    '[a-zA-Z]{2,}))$'].join(''));

1

Array를 피하기 위해 join다음 구문을 사용할 수도 있습니다.

var pattern = new RegExp('^(([^<>()[\]\\.,;:\s@\"]+' +
  '(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@' +
  '((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|' +
  '(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$');

0

개인적으로 덜 복잡한 정규 표현식을 사용합니다.

/\S+@\S+\.\S+/

물론 현재 패턴 보다 정확도 가 떨어지지 만 무엇을 달성하려고합니까? 사용자가 실수로 입력 한 오류를 포착하려고합니까, 아니면 사용자가 유효하지 않은 주소를 입력하려고 할까 걱정됩니까? 그것이 처음이라면 더 쉬운 패턴을 찾으러 갈 것입니다. 후자 인 경우 해당 주소로 전송 된 전자 메일에 응답하여 확인하는 것이 더 좋습니다.

그러나 현재 패턴을 사용하려면 다음과 같이 작은 하위 패턴에서 패턴을 작성하여 (IMO) 더 쉽게 읽고 유지 관리 할 수 ​​있습니다.

var box1 = "([^<>()[\]\\\\.,;:\s@\"]+(\\.[^<>()[\\]\\\\.,;:\s@\"]+)*)";
var box2 = "(\".+\")";

var host1 = "(\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])";
var host2 = "(([a-zA-Z\-0-9]+\\.)+[a-zA-Z]{2,})";

var regex = new RegExp("^(" + box1 + "|" + box2 + ")@(" + host1 + "|" + host2 + ")$");

21
Downvoting-정규식 복잡성 감소에 대한 귀하의 의견은 유효하지만 OP는 특히 "여러 줄에 긴 정규식을 분할"하는 방법을 묻고 있습니다. 따라서 귀하의 조언은 유효하지만 잘못된 이유로 제시되었습니다. 프로그래밍 언어를 다루기 위해 비즈니스 로직 변경 또한 코드 예제는 매우 추악합니다.
sleepycal

4
@sleepycal Bart이 질문에 대답했다고 생각합니다. 그의 대답의 마지막 부분을보십시오. 그는 그 질문에 대한 답을 제시하고 대안을 제시했다.
Nidhin David

0

단순히 문자열 연산을 사용할 수 있습니다.

var pattenString = "^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|"+
"(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|"+
"(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$";
var patten = new RegExp(pattenString);

0

나는 모든 것을 캡슐화하고 캡처 그룹과 문자 세트를 나누는 지원을 구현하여 korun의 대답을 개선하려고 시도했습니다.이 방법은 훨씬 더 다양합니다.

이 스 니펫을 사용하려면 variadic 함수를 호출해야합니다. combineRegex 인수가 결합해야하는 정규식 객체 인 해야합니다. 구현은 하단에서 찾을 수 있습니다.

하나의 괄호만으로 일부 부분을 남기는 것처럼 그룹을 캡처하면 그러한 방식으로 직접 분할 할 수 없습니다. 브라우저가 예외로 실패합니다.

대신 단순히 캡처 그룹의 내용을 배열 안에 전달합니다. 괄호는 자동으로 추가됩니다combineRegex배열이 발생하면 .

또한 정량자는 무언가를 따라야합니다. 어떤 이유로 정규 표현식을 수량 자 앞에서 분할해야하는 경우 괄호 쌍을 추가해야합니다. 이들은 자동으로 제거됩니다. 요점은 빈 캡처 그룹이 무용지물이며이 방법으로 수량 화자가 참조 할 것이 있습니다. 캡처하지 않는 그룹과 같은 항목에 동일한 방법을 사용할 수 있습니다 ( /(?:abc)/가 됨 [/()?:abc/]).

이것은 간단한 예를 사용하여 가장 잘 설명됩니다.

var regex = /abcd(efghi)+jkl/;

될 것입니다 :

var regex = combineRegex(
    /ab/,
    /cd/,
    [
        /ef/,
        /ghi/
    ],
    /()+jkl/    // Note the added '()' in front of '+'
);

문자 집합을 분리해야하는 경우 {"":[regex1, regex2, ...]}배열 ( [regex1, regex2, ...]) 대신 객체 ( )를 사용할 수 있습니다 . 키의 내용은 객체에 키가 하나만있는 한 아무 것도 될 수 있습니다. 참고 대신의 것을 ()사용해야 할 ]첫 번째 문자가 한정 기호로 해석 될 수 있다면 더미 시작으로. 즉 /[+?]/이된다{"":[/]+?/]}

다음은 스 니펫과보다 완전한 예입니다.

function combineRegexStr(dummy, ...regex)
{
    return regex.map(r => {
        if(Array.isArray(r))
            return "("+combineRegexStr(dummy, ...r).replace(dummy, "")+")";
        else if(Object.getPrototypeOf(r) === Object.getPrototypeOf({}))
            return "["+combineRegexStr(/^\]/, ...(Object.entries(r)[0][1]))+"]";
        else 
            return r.source.replace(dummy, "");
    }).join("");
}
function combineRegex(...regex)
{
    return new RegExp(combineRegexStr(/^\(\)/, ...regex));
}

//Usage:
//Original:
console.log(/abcd(?:ef[+A-Z0-9]gh)+$/.source);
//Same as:
console.log(
  combineRegex(
    /ab/,
    /cd/,
    [
      /()?:ef/,
      {"": [/]+A-Z/, /0-9/]},
      /gh/
    ],
    /()+$/
  ).source
);


0

@Hashbrown의 훌륭한 답변 이 올바른 길로 안내했습니다. 이 블로그에서 영감을 얻은 내 버전이 있습니다.

function regexp(...args) {
  function cleanup(string) {
    // remove whitespace, single and multi-line comments
    return string.replace(/\s+|\/\/.*|\/\*[\s\S]*?\*\//g, '');
  }

  function escape(string) {
    // escape regular expression
    return string.replace(/[-.*+?^${}()|[\]\\]/g, '\\$&');
  }

  function create(flags, strings, ...values) {
    let pattern = '';
    for (let i = 0; i < values.length; ++i) {
      pattern += cleanup(strings.raw[i]);  // strings are cleaned up
      pattern += escape(values[i]);        // values are escaped
    }
    pattern += cleanup(strings.raw[values.length]);
    return RegExp(pattern, flags);
  }

  if (Array.isArray(args[0])) {
    // used as a template tag (no flags)
    return create('', ...args);
  }

  // used as a function (with flags)
  return create.bind(void 0, args[0]);
}

다음과 같이 사용하십시오.

regexp('i')`
  //so this is a regex

  //here I am matching some numbers
  (\d+)

  //Oh! See how I didn't need to double backslash that \d?
  ([a-z]{1,3}) /*note to self, this is group #2*/
`

RegExp객체 를 만들려면

/(\d+)([a-z]{1,3})/i
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.