자바 스크립트 : 부정적인 lookbehind 동등?


142

자바 스크립트 정규 표현식에서 부정적인 룩백 을 달성하는 방법이 있습니까? 특정 문자 세트로 시작하지 않는 문자열을 일치시켜야합니다.

일치하는 부분이 문자열의 시작 부분에 있으면 실패하지 않고이 작업을 수행하는 정규식을 찾을 수없는 것 같습니다. 부정적인 lookbehinds가 유일한 답변 인 것 같지만 javascript에는 하나가 없습니다.

편집 : 이것은 내가하고 싶은 정규 표현식이지만 그렇지 않습니다.

(?<!([abcdefg]))m

따라서 'jim'또는 'm'의 'm'과 일치하지만 'jam'은 일치하지 않습니다.


부정적인 표정으로 보일 정규식 게시를 고려하십시오. 응답하기가 더 쉬울 수 있습니다.
Daniel LeCheminant 2016 년


@ WiktorStribiżew : Look-behinds가 2018 사양에 추가되었습니다. Chrome에서 지원하지만 Firefox는 여전히 사양을 구현하지 않았습니다 .
Lonnie Best

이것도 뒤를 봐야합니까? 무엇에 대해 (?:[^abcdefg]|^)(m)? 마찬가지로"mango".match(/(?:[^abcdefg]|^)(m)/)[1]
slebetman

답변:


58

Lookbehind Assertions 는 2018 년 ECMAScript 사양채택 되었습니다 .

긍정적 인 룩백 사용법 :

console.log(
  "$9.99  €8.47".match(/(?<=\$)\d+(\.\d*)?/) // Matches "9.99"
);

부정적인 룩백 사용법 :

console.log(
  "$9.99  €8.47".match(/(?<!\$)\d+(?:\.\d*)/) // Matches "8.47"
);

플랫폼 지원 :


2
폴리 필이 있습니까?
Killy

1
@Killy 내가 아는 한 거기까지는 없으며, 하나를 만드는 것이 잠재적으로 매우 비실용적 일 것이므로 (IE에서 전체 정규식 구현 작성)
Okku

babel 플러그인을 사용하는 것은 어떻습니까? ES5로 컴파일하거나 이미 지원되는 ES6으로 컴파일 할 수 있습니까?
Stefan J

1
@IlpoOksanen 나는 당신이 RegEx 구현을 확장하는 것을 의미한다고 생각한다. 이것은 polyfills가하는 일이다. ... 그리고 JavaScript로 로직을 작성하는 데 아무런 문제가 없다
neaumusic

1
무슨 소리 야? 거의 모든 제안은 다른 언어에서 영감을 얻었으며 관용 JS 및 이전 버전과의 호환성에서 의미가있는 다른 언어의 구문 및 의미를 항상 선호합니다. 나는 긍정적이고 부정적인 전망이 2017 년 2018 사양에 수용되었으며 출처에 대한 링크를 제공했다고 분명히 분명히 생각합니다. 또한, 어떤 플랫폼이 해당 사양을 구현하고 다른 플랫폼의 상태가 무엇인지 자세히 설명했으며 그 이후에도 업데이트되었습니다. 당연히 이것이 마지막 정규 표현식 기능은 아닙니다.
Okku

83

2018 년 이후 Lookbehind AssertionsECMAScript 언어 사양의 일부입니다 .

// positive lookbehind
(?<=...)
// negative lookbehind
(?<!...)

2018 년 이전 답변

Javascript는 부정적 lookahead를 지원하므로 한 가지 방법은 다음과 같습니다.

  1. 입력 문자열을 반대로

  2. 역 정규 표현식과 일치

  3. 경기를 뒤집고 다시 포맷


const reverse = s => s.split('').reverse().join('');

const test = (stringToTests, reversedRegexp) => stringToTests
  .map(reverse)
  .forEach((s,i) => {
    const match = reversedRegexp.test(s);
    console.log(stringToTests[i], match, 'token:', match ? reverse(reversedRegexp.exec(s)[0]) : 'Ø');
  });

예 1 :

@ andrew-ensley의 질문에 따라 :

test(['jim', 'm', 'jam'], /m(?!([abcdefg]))/)

출력 :

jim true token: m
m true token: m
jam false token: Ø

예 2 :

@neaumusic 주석 다음에 ( 토큰이 일치 max-height하지만 일치 하지는 않음 ) :line-heightheight

test(['max-height', 'line-height'], /thgieh(?!(-enil))/)

출력 :

max-height true token: height
line-height false token: Ø

36
이 접근 방식의 문제점은
예견

3
실제 예제를 보여 주시겠습니까? 일치하고 싶지만 일치 max-height하지 line-height않기를 원합니다height
neaumusic

일부 기호가 앞에 있지 않은 두 개의 연속 된 동일한 기호 (및 2 개 이하)를 작업으로 대체해야하는 경우에는 도움이되지 않습니다. 다른 쪽 끝에서 ''(?!\()아포스트로피를 대체하여 대신에 . ''(''test'''''''test(''test'NNNtest(''testNNN'test
Wiktor Stribiżew

61

int앞에 오지 않는 것을 모두 찾고 싶다고 가정 해 봅시다 unsigned.

부정적인 표정을 지원합니다.

(?<!unsigned )int

부정적인 표정을 지원하지 않는 경우 :

((?!unsigned ).{9}|^.{0,8})int

기본적으로 아이디어는 n 개의 선행 문자를 잡고 부정적인 미리보기와 일치하는 것을 제외하고 n 개의 선행 문자가없는 경우와 일치시키는 것입니다. (여기서 n은 look-behind 길이입니다.)

따라서 문제의 정규 표현식 :

(?<!([abcdefg]))m

다음과 같이 번역됩니다 :

((?!([abcdefg])).|^)m

관심있는 문자열의 정확한 지점을 찾거나 특정 부분을 다른 것으로 바꾸려면 캡처 그룹을 가지고 놀아야 할 수도 있습니다.


2
이것이 정답이어야합니다. 참조 : "So it would match the 'm' in 'jim' or 'm', but not 'jam'".replace(/(j(?!([abcdefg])).|^)m/g, "$1[MATCH]") 반환 "So it would match the 'm' in 'ji[MATCH]' or 'm', but not 'jam'" 그것은 매우 간단하고 작동합니다!
Asrail

41

Mijoja의 전략은 특정 사례에 적용되지만 일반적으로는 그렇지 않습니다.

js>newString = "Fall ball bill balll llama".replace(/(ba)?ll/g,
   function($0,$1){ return $1?$0:"[match]";});
Fa[match] ball bi[match] balll [match]ama

다음은 목표가 double-l과 일치하지만 "ba"가 앞에 오는 경우와 일치하지 않는 예입니다. "balll"이라는 단어에 주목하십시오. 진정한 lookbehind는 처음 2 개의 l을 억제했지만 두 번째 쌍과 일치해야합니다. 그러나 첫 번째 2 l을 일치시킨 다음 해당 일치를 오 탐지로 무시하면 정규 표현식 엔진 이 해당 일치 의 에서 진행 하여 오 탐지 내의 모든 문자를 무시합니다.


5
아 맞아. 그러나 이것은 이전보다 훨씬 더 가깝습니다. 자바 스크립트가 실제로 lookbehinds를 구현하는 것처럼 더 나은 것이 나올 때까지 이것을 받아 들일 수 있습니다.
앤드류 Ensley

33

사용하다

newString = string.replace(/([abcdefg])?m/, function($0,$1){ return $1?$0:'m';});

10
이것은 아무것도하지 않습니다 : newString항상 같습니다 string. 왜 그렇게 많은 투표를합니까?
MikeM

@ MikeM : 요점은 단순히 매칭 기술을 보여주기 때문입니다.
bug

57
@곤충. 아무것도하지 않는 데모는 이상한 종류의 데모입니다. 답은 복사 방식과 작동 방식에 대한 이해없이 붙여 넣은 것처럼 나타납니다. 따라서 수반되는 설명의 부족과 일치하는 것이 무엇인지 입증하지 못했습니다.
MikeM

2
@ MikeM : SO의 규칙은 작성된 질문에 대답하면 정확합니다. OP는 사용 사례를 지정하지 않았습니다
버그

7
이 개념은 정확하지만 그렇습니다. ... JS 콘솔에서이 작업을 실행 해보십시오 "Jim Jam Momm m".replace(/([abcdefg])?m/g, function($0, $1){ return $1 ? $0 : '[match]'; });. 반환해야합니다 Ji[match] Jam Mo[match][match] [match]. 그러나 Jason이 아래에 언급했듯이 특정 경우에는 실패 할 수 있습니다.
Simon East

11

문자 집합을 무효화하여 캡처하지 않은 그룹을 정의 할 수 있습니다.

(?:[^a-g])m

...이 문자 앞에 붙지 m 않는 모든 문자 와 일치합니다 .


2
경기가 실제로 앞의 캐릭터를 덮을 것이라고 생각합니다.
Sam

4
^ 이것은 사실이다. 캐릭터 클래스는 ... 캐릭터를 나타냅니다! 캡처하지 않은 그룹이 수행하는 모든 작업은 대체 컨텍스트에서 해당 값을 사용할 수 없게 만드는 것입니다. 당신의 표현은 "그들 중 어떤 문자도 앞에 오지 않아야합니다"라고 말하고 있지 않습니다. "
그런데

5
답이 원래 문제 (문자열의 시작)를 해결하려면 옵션도 포함해야하므로 결과 정규 표현식은 다음과 같습니다 (?:[^a-g]|^)m. 예제 실행에 대해서는 regex101.com/r/jL1iW6/2 를 참조하십시오 .
Johny Skovdal

void 로직을 사용한다고해서 항상 원하는 효과가있는 것은 아닙니다.
GoldBishop

2

이것이 str.split(/(?<!^)@/)Node.js 8에서 달성 한 방법입니다 (lookbehind를 지원하지 않음).

str.split('').reverse().join('').split(/@(?!$)/).map(s => s.split('').reverse().join('')).reverse()

공장? 예 (유니 코드 테스트되지 않음). 불쾌한? 예.


1

Mijoja의 아이디어를 따르고 JasonS에 의해 노출 된 문제에서 벗어나서, 나는이 아이디어를 가졌다; 나는 조금 확인했지만 자신을 확신하지 못하므로 js 정규식에서 나보다 더 전문가에 의한 확인은 훌륭 할 것입니다 :)

var re = /(?=(..|^.?)(ll))/g
         // matches empty string position
         // whenever this position is followed by
         // a string of length equal or inferior (in case of "^")
         // to "lookbehind" value
         // + actual value we would want to match

,   str = "Fall ball bill balll llama"

,   str_done = str
,   len_difference = 0
,   doer = function (where_in_str, to_replace)
    {
        str_done = str_done.slice(0, where_in_str + len_difference)
        +   "[match]"
        +   str_done.slice(where_in_str + len_difference + to_replace.length)

        len_difference = str_done.length - str.length
            /*  if str smaller:
                    len_difference will be positive
                else will be negative
            */

    }   /*  the actual function that would do whatever we want to do
            with the matches;
            this above is only an example from Jason's */



        /*  function input of .replace(),
            only there to test the value of $behind
            and if negative, call doer() with interesting parameters */
,   checker = function ($match, $behind, $after, $where, $str)
    {
        if ($behind !== "ba")
            doer
            (
                $where + $behind.length
            ,   $after
                /*  one will choose the interesting arguments
                    to give to the doer, it's only an example */
            )
        return $match // empty string anyhow, but well
    }
str.replace(re, checker)
console.log(str_done)

내 개인 출력 :

Fa[match] ball bi[match] bal[match] [match]ama

원칙은 전화하는 것입니다 checker 위치가 시작점이 될 때마다 두 문자 사이에서 문자열의 각 지점에서 하는 것입니다.

--- 어떤 여기 (원하지 않는 것의 크기의 하위 문자열 'ba'따라서,.. (그렇지 않으면 아마도 할 더 열심히해야 그 크기가 알려진 경우))

--- --- 또는 문자열의 시작 부분보다 작은 경우 : ^.?

그리고 이것에 따라

--- 실제로 찾는 것 (여기 'll' ).

를 호출 할 때마다 checker이전 값 ll이 원하지 않는 값인지 확인하는 테스트가 있습니다 ( !== 'ba'). 이 경우 다른 함수를 호출하고 doer, 목적이이 경우 또는보다 일반적으로 str을 변경하는 것은이 함수 ( ) 여야하며 , 수동으로 처리하는 데 필요한 데이터를 입력해야합니다. 스캔 결과str .

여기서 우리는 문자열을 변경하여에 의해 주어진 위치를 상쇄하기 위해 길이의 차이를 추적해야했습니다 replace.str 절대 변경 자체를.

원시 문자열은 변경할 수 str없으므로 변수 를 사용하여 전체 작업의 결과를 저장할 수 있었지만 대체로 인해 복잡한 예제는 다른 변수와 더 명확하다고 생각했습니다.str_done ) .

나는 성능 측면에서 '' '',으로의 무의미한 대체, this str.length-1시간, 그리고 doer에 의한 수동 교체 등 많은 슬라이싱을 의미합니다. 아마도 위의 특정 경우에는 우리는 삽입 할 위치 주위에 조각으로 한 번만 문자열을 절단하여, 그룹화 [match].join()함께 보내고 [match]그 자체.

다른 것은 더 복잡한 경우, 즉 가짜 lookbehind에 대한 복잡한 값을 처리하는 방법을 모른다는 것입니다. 길이는 아마도 가장 문제가되는 데이터 일 것입니다.

에서 checker$ behind에 대해 원하지 않는 값이 여러 가지 가능성이있는 경우 checker동일한 정규 표현식 객체가 생성되지 않도록 외부에서 캐싱 (만들기)하는 또 다른 정규 표현식으로 테스트 해야합니다. 각 호출에 checker) 우리가 피하기 위해 무엇을 추구 있는지 여부를 알 수 있습니다.

내가 분명했으면 좋겠다. 망설이지 않으면 더 잘 할게요. :)


1

케이스를 사용하여 무언가 로 바꾸려면 ( m 예 : 대문자로 변환) M캡처 그룹에서 설정을 무효화 할 수 있습니다.

일치 ([^a-g])m,로 교체$1M

"jim jam".replace(/([^a-g])m/g, "$1M")
\\jiM jam

([^a-g])범위의 모든 문자 not ( ^) 과 일치 a-g하고 첫 번째 캡처 그룹에 저장하므로로 액세스 할 수 있습니다 $1.

우리가 찾을 수 있도록 im에서 jim와로 교체 iM에서 어떤 결과 jiM.


1

앞에서 언급했듯이 JavaScript는 이제 룩백을 허용합니다. 오래된 브라우저에서는 여전히 해결 방법이 필요합니다.

나는 내 머리를 내다 보면 룩백없이 정규식을 찾을 수있는 방법이 없으며 결과를 정확하게 제공합니다. 그룹으로 작업하기 만하면됩니다. 정규식을 가지고 있다고 가정하십시오 (?<!Before)Wanted. 여기서 Wanted일치 Before시킬 정규식이 있고 일치하지 않아야하는 것을 계산하는 정규식입니다. 당신이 할 수있는 최선은 정규식을 무효화하고 정규식 Before을 사용하는 것 NotBefore(Wanted)입니다. 원하는 결과는 첫 번째 그룹입니다$1 입니다.

Before=[abcdefg]부정하기 쉬운 당신의 경우에 NotBefore=[^abcdefg]. 따라서 정규식은 다음과 같습니다 [^abcdefg](m). 의 위치가 필요한 경우 Wanted그룹화해야합니다.NotBefore 원하는 결과가 두 번째 그룹이되도록 .

Before패턴의 일치 길이가 고정 길이 n인 경우, 즉 패턴에 반복 토큰이없는 경우 패턴을 무시하지 Before않고 정규 표현식 (?!Before).{n}(Wanted)을 사용할 수 있지만 여전히 첫 번째 그룹을 사용하거나 정규 표현식 (?!Before)(.{n})(Wanted)을 사용하고 두 번째를 사용해야합니다 그룹. 이 예에서 패턴은 Before실제로 고정 길이, 즉 1을 가지므로 정규식 (?![abcdefg]).(m)또는을 사용하십시오 (?![abcdefg])(.)(m). 모든 경기에 관심이 있다면 g플래그를 추가하고 내 코드 스 니펫을 참조하십시오.

function TestSORegEx() {
  var s = "Donald Trump doesn't like jam, but Homer Simpson does.";
  var reg = /(?![abcdefg])(.{1})(m)/gm;
  var out = "Matches and groups of the regex " + 
            "/(?![abcdefg])(.{1})(m)/gm in \ns = \"" + s + "\"";
  var match = reg.exec(s);
  while(match) {
    var start = match.index + match[1].length;
    out += "\nWhole match: " + match[0] + ", starts at: " + match.index
        +  ". Desired match: " + match[2] + ", starts at: " + start + ".";   
    match = reg.exec(s);
  }
  out += "\nResulting string after statement s.replace(reg, \"$1*$2*\")\n"
         + s.replace(reg, "$1*$2*");
  alert(out);
}

0

이것은 효과적으로 그것을한다

"jim".match(/[^a-g]m/)
> ["im"]
"jam".match(/[^a-g]m/)
> null

검색 및 교체 예

"jim jam".replace(/([^a-g])m/g, "$1M")
> "jiM jam"

음수 뒤에 보이는 문자열의 길이는 1 자 여야합니다.


1
좀 빠지는. "jim"에서는 "i"를 원하지 않습니다. 그냥 "m". 그리고 "m".match(/[^a-g]m/)yeilds null뿐만 아니라. 이 경우에도 "m"을 원합니다.
Andrew Ensley

-1

/(?![abcdefg])[^abcdefg]m/gi 예, 이것은 속임수입니다.


5
해당 문자가 일치하지 않도록하기 위해 이미 작업을 수행 (?![abcdefg])하므로 검사 는 완전히 중복 [^abcdefg]됩니다.
nhahtdh

2
선행 문자가없는 'm'과 일치하지 않습니다.
Andrew Ensley
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.