[javascript] JavaScript 정규식에서 일치하는 그룹에 어떻게 액세스합니까?

정규 표현식을 사용하여 문자열의 일부를 일치시키고 괄호로 묶은 하위 문자열에 액세스 하고 싶습니다 .

var myString = "something format_abc"; // I want "abc"

var arr = /(?:^|\s)format_(.*?)(?:\s|$)/.exec(myString);

console.log(arr);     // Prints: [" format_abc", "abc"] .. so far so good.
console.log(arr[1]);  // Prints: undefined  (???)
console.log(arr[0]);  // Prints: format_undefined (!!!)

내가 무엇을 잘못하고 있지?


위의 정규 표현식 코드에 아무런 문제가 없음을 발견했습니다. 테스트 할 실제 문자열은 다음과 같습니다.

"date format_%A"

“% A”가 정의되지 않았다고보고하는 것은 매우 이상한 행동으로 보이지만이 질문과 직접 ​​관련이 없으므로 새로운 질문을 열었습니다. JavaScript에서 일치하는 하위 문자열이 “undefined”를 반환하는 이유는 무엇입니까? .


문제는 명령문 console.log과 같은 매개 변수 를 취하는 것이며 printf, 내가 로깅하는 문자열 ( "%A")에 특별한 값이 있으므로 다음 매개 변수의 값을 찾으려고했습니다.



답변

다음과 같이 캡처 그룹에 액세스 할 수 있습니다.

var myString = "something format_abc";
var myRegexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
var match = myRegexp.exec(myString);
console.log(match[1]); // abc

그리고 일치하는 항목이 여러 개인 경우 반복 할 수 있습니다.

var myString = "something format_abc";
var myRegexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
match = myRegexp.exec(myString);
while (match != null) {
  // matched text: match[0]
  // match start: match.index
  // capturing group n: match[n]
  console.log(match[0])
  match = myRegexp.exec(myString);
}

편집 : 2019-09-10

보시다시피 여러 경기를 반복하는 방법은 그리 직관적이지 않았습니다. 이것은 String.prototype.matchAll방법 의 제안으로 이어진다 . 이 새로운 방법은 ECMAScript 2020 사양으로 제공 될 것으로 예상됩니다 . 깨끗한 API를 제공하고 여러 문제를 해결합니다. Chrome 73+ / Node 12+ 및 Firefox 67+와 같은 주요 브라우저 및 JS 엔진에 착륙하기 시작했습니다 .

이 메소드는 반복자를 리턴하며 다음과 같이 사용됩니다.

const string = "something format_abc";
const regexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
const matches = string.matchAll(regexp);
    
for (const match of matches) {
  console.log(match);
  console.log(match.index)
}

반복자를 반환 할 때 게으름이라고 말할 수 있습니다. 특히 많은 수의 캡처 그룹 또는 매우 큰 문자열을 처리 할 때 유용합니다. 그러나 필요한 경우 스프레드 구문 이나 Array.from메소드를 사용하여 결과를 쉽게 배열로 변환 할 수 있습니다 .

function getFirstGroup(regexp, str) {
  const array = [...str.matchAll(regexp)];
  return array.map(m => m[1]);
}

// or:
function getFirstGroup(regexp, str) {
  return Array.from(str.matchAll(regexp), m => m[1]);
}

그 동안이 제안이 더 광범위하게 지원되는 동안 공식 shim 패키지를 사용할 수 있습니다 .

또한이 방법의 내부 작업은 간단합니다. 생성기 함수를 사용하는 동등한 구현은 다음과 같습니다.

function* matchAll(str, regexp) {
  const flags = regexp.global ? regexp.flags : regexp.flags + "g";
  const re = new RegExp(regexp, flags);
  let match;
  while (match = re.exec(str)) {
    yield match;
  }
}

원래 정규 표현식의 사본이 작성됩니다. 이것은 lastIndex다수의 일치를 통과 할 때 속성 의 돌연변이로 인한 부작용을 피하기위한 것입니다.

또한 무한 루프를 피하기 위해 정규 표현식에 전역 플래그 가 있는지 확인해야합니다 .

또한이 StackOverflow 질문조차도 제안서 토론 에서 참조되었음을 알게되어 기쁩니다 .


답변

다음 은 각 경기에 대해 n 번째 캡처 그룹 을 얻는 데 사용할 수있는 방법입니다 .

function getMatches(string, regex, index) {
  index || (index = 1); // default to the first capturing group
  var matches = [];
  var match;
  while (match = regex.exec(string)) {
    matches.push(match[index]);
  }
  return matches;
}


// Example :
var myString = 'something format_abc something format_def something format_ghi';
var myRegEx = /(?:^|\s)format_(.*?)(?:\s|$)/g;

// Get an array containing the first capturing group for every match
var matches = getMatches(myString, myRegEx, 1);

// Log results
document.write(matches.length + ' matches found: ' + JSON.stringify(matches))
console.log(matches);


답변

var myString = "something format_abc";
var arr = myString.match(/\bformat_(.*?)\b/);
console.log(arr[0] + " " + arr[1]);

\b똑같은 일이 아니다. ( --format_foo/에서는 작동 format_a_b하지만 작동하지 않습니다 ) 그러나 나는 당신의 표현에 대한 대안을 보여주고 싶었습니다. 물론 match전화는 중요한 것입니다.


답변

위의 다중 일치 괄호 예제와 관련하여 원하는 것을 얻지 못한 후 여기에서 답변을 찾고있었습니다.

var matches = mystring.match(/(?:neededToMatchButNotWantedInResult)(matchWanted)/igm);

위의 while 및 .push ()를 사용하여 약간 복잡한 함수 호출을 살펴본 후 mystring.replace ()를 사용하여 문제를 매우 우아하게 해결할 수 있음을 알게되었습니다 (바꾸기가 중요하지 않으며 심지어 수행되지도 않습니다) , 두 번째 매개 변수에 대한 CLEAN, 내장 재귀 함수 호출 옵션은 다음과 같습니다!) :

var yourstring = 'something format_abc something format_def something format_ghi';

var matches = [];
yourstring.replace(/format_([^\s]+)/igm, function(m, p1){ matches.push(p1); } );

이 후, 나는 거의 다시는 거의 .match ()를 사용하지 않을 것이라고 생각합니다.


답변

마지막으로, 저에게 잘 맞는 한 줄의 코드를 발견했습니다 (JS ES6).

let reg = /#([\S]+)/igm; // Get hashtags.
let string = 'mi alegría es total! ✌?\n#fiestasdefindeaño #PadreHijo #buenosmomentos #france #paris';

let matches = (string.match(reg) || []).map(e => e.replace(reg, '$1'));
console.log(matches);

이것은 다음을 반환합니다 :

['fiestasdefindeaño', 'PadreHijo', 'buenosmomentos', 'france', 'paris']


답변

이 답변에 사용 된 용어 :

  • Match 는 다음과 같이 문자열에 대해 RegEx 패턴을 실행 한 결과를 나타냅니다 someString.match(regexPattern).
  • 일치 패턴 은 입력 문자열에서 일치하는 모든 부분을 나타내며, 모두 일치 배열 안에 있습니다. 이들은 입력 문자열 내부의 모든 패턴 인스턴스입니다.
  • 일치 그룹 은 RegEx 패턴에 정의 된 모든 그룹을 포착합니다. (괄호 안의 패턴과 같이 : /format_(.*?)/g여기서 (.*?)정합 기일 수있다.) 이러한 내에 상주 유사한 패턴 .

기술

받는 액세스 얻으려면 일치하는 그룹 의 각 일치 패턴 , 당신은 기능 또는 반복하는 비슷한 필요 일치 . 다른 많은 답변에서 볼 수 있듯이 여러 가지 방법으로이 작업을 수행 할 수 있습니다. 대부분의 다른 답변은 while 루프를 사용하여 일치하는 모든 패턴 을 반복 하지만, 우리는 그 접근 방식의 잠재적 위험을 모두 알고 있다고 생각합니다. new RegExp()주석에만 언급 된 패턴 자체 대신에 일치해야합니다 . 이것은 때문이다 .exec()방법은 유사 동작 생성 기능일치하는 항목이있을 때마다 중지 ,하지만 유지 .lastIndex다음에 거기에서 계속 .exec()호출.

코드 예

아래는 모든 일치하는 패턴searchString 을 반환하는 함수의 예입니다. 여기서 각각 은 모든 포함 된 일치하는 그룹 과 함께 있습니다 . while 루프를 사용하는 대신 일반 루프를 사용하여 기능과 성능 을 모두 향상시키는 예제를 제공 했습니다.ArraymatchArrayArray.prototype.map()for

간결한 버전 (더 적은 코드, 더 많은 구문 설탕)

기본적으로 forEach더 빠른 for-loop 대신 -loop를 구현하므로 성능이 떨어 집니다.

// Concise ES6/ES2015 syntax
const searchString =
    (string, pattern) =>
        string
        .match(new RegExp(pattern.source, pattern.flags))
        .map(match =>
            new RegExp(pattern.source, pattern.flags)
            .exec(match));

// Or if you will, with ES5 syntax
function searchString(string, pattern) {
    return string
        .match(new RegExp(pattern.source, pattern.flags))
        .map(match =>
            new RegExp(pattern.source, pattern.flags)
            .exec(match));
}

let string = "something format_abc",
    pattern = /(?:^|\s)format_(.*?)(?:\s|$)/;

let result = searchString(string, pattern);
// [[" format_abc", "abc"], null]
// The trailing `null` disappears if you add the `global` flag

퍼포먼스 버전 (더 많은 코드, 적은 구문 설탕)

// Performant ES6/ES2015 syntax
const searchString = (string, pattern) => {
    let result = [];

    const matches = string.match(new RegExp(pattern.source, pattern.flags));

    for (let i = 0; i < matches.length; i++) {
        result.push(new RegExp(pattern.source, pattern.flags).exec(matches[i]));
    }

    return result;
};

// Same thing, but with ES5 syntax
function searchString(string, pattern) {
    var result = [];

    var matches = string.match(new RegExp(pattern.source, pattern.flags));

    for (var i = 0; i < matches.length; i++) {
        result.push(new RegExp(pattern.source, pattern.flags).exec(matches[i]));
    }

    return result;
}

let string = "something format_abc",
    pattern = /(?:^|\s)format_(.*?)(?:\s|$)/;

let result = searchString(string, pattern);
// [[" format_abc", "abc"], null]
// The trailing `null` disappears if you add the `global` flag

나는이 대안들을 다른 답변들에서 이전에 언급 한 대안들과 비교하지는 않았지만,이 접근법이 다른 방법들보다 성능이 낮고 고장이 적다는 것은 의심 스럽다.


답변

String#matchAll( 단계 3 초안 / 2018 년 12 월 7 일 제안 참조 )은 일치 오브젝트의 모든 그룹에 대한 액세스를 단순화합니다 (그룹 0은 전체 일치이며 추가 그룹은 패턴의 캡처 그룹에 해당함).

matchAll사용할 수, 당신은 피할 수 while루프와 exec함께 /g… 대신 사용하여 matchAll, 당신은 당신이 더 편리하게 사용할 수있는 반복자 돌아가 for...of, 배열 확산 , 또는 Array.from()구조를

이 메소드 Regex.Matches는 C #, re.finditerPython, preg_match_allPHP 와 비슷한 결과를 냅니다.

JS 데모 (Chrome 73.0.3683.67 (공식 빌드), 베타 (64 비트)에서 테스트)를 참조하십시오.

var myString = "key1:value1, key2-value2!!@key3=value3";
var matches = myString.matchAll(/(\w+)[:=-](\w+)/g);
console.log([...matches]); // All match with capturing group values

console.log([...matches])

여기에 이미지 설명을 입력하십시오

다음을 사용하여 일치 값 또는 특정 그룹 값을 얻을 수도 있습니다.

let matchData = "key1:value1, key2-value2!!@key3=value3".matchAll(/(\w+)[:=-](\w+)/g)
var matches = [...matchData]; // Note matchAll result is not re-iterable

console.log(Array.from(matches, m => m[0])); // All match (Group 0) values
// => [ "key1:value1", "key2-value2", "key3=value3" ]
console.log(Array.from(matches, m => m[1])); // All match (Group 1) values
// => [ "key1", "key2", "key3" ]

참고 : 브라우저 호환성 정보를 참조하십시오.