균형 괄호와 일치하는 정규식


290

두 개의 괄호 사이의 모든 텍스트를 선택하려면 정규식이 필요합니다.

예: some text(text here(possible text)text(possible text(more text)))end text

결과: (text here(possible text)text(possible text(more text)))


3
이 질문은 그것이 무엇을 요구하는지 명확하지 않기 때문에 매우 가난합니다. 모든 대답은 다르게 해석했습니다. @DaveF 질문을 명확하게 설명해 주시겠습니까?
매트 펜윅

답변:


144

정규식은 중첩 구조, 즉 재귀를 처리하므로 작업에 잘못된 도구입니다.

그러나이 작업을 수행하는 간단한 알고리즘 이 있습니다.이 질문 에서 이전 질문에 대한 답변 을 설명 했습니다 .


15
.NET의 구현에는 [Balancing Group Definitions msdn.microsoft.com/en-us/library/…] 가 있습니다.
Carl G

22
몇 가지 이유로 정규 표현식이 잘못된 도구라는 데 동의하지 않습니다. 1) 대부분의 정규 표현식 구현에는 완벽하지 않은 솔루션이 있습니다. 2) 종종 정규 표현식에 적합한 다른 기준이 사용되는 상황에서 균형 잡힌 구분 기호 쌍을 찾으려고합니다. 3) 종종 정규 표현식 만 허용하는 일부 API에 정규 표현식을 전달하고 있으며 선택의 여지가 없습니다.
Kenneth Baltrinic


20
정규식은 작업에 적합한 도구입니다. 이 답변은 옳지 않습니다. rogal111의 답변을 참조하십시오.
앤드류

4
대답에 절대적으로 동의하십시오. 정규 표현식에는 재귀의 일부 구현이 있지만 유한 상태 기계와 같으며 중첩 된 구조로 작동하도록 강요되지는 않지만 컨텍스트 무료 문법이이를 수행합니다. Homsky의 공식 문법에 대한 계층 구조를 살펴보십시오.
Nick Roz

138

빠른 참조를 위해이 답변을 추가하고 싶습니다. 자유롭게 업데이트하십시오.


밸런싱 그룹을 사용하는 .NET 정규식 .

\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)

c깊이 카운터로 사용되는 곳 .

Regexstorm.com의 데모


재귀 패턴을 사용하는 PCRE .

\((?:[^)(]+|(?R))*+\)

regex101의 데모 ; 또는 교대없이 :

\((?:[^)(]*(?R)?)*+\)

regex101의 데모 ; 또는 성능을 위해 풀었다 :

\([^)(]*+(?:(?R)[^)(]*)*+\)

regex101의 데모 ; (?R)를 나타내는 패턴이 붙여 넣어 (?0)집니다.

Perl, PHP, Notepad ++, R : perl = TRUE , Python : Perl 동작 을 위한 정규식 패키지(?V1) .


하위 표현식 호출을 사용하는 루비 .

Ruby 2.0 \g<0>을 사용하면 전체 패턴을 호출 할 수 있습니다.

\((?>[^)(]+|\g<0>)*\)

Rubular에서 데모 ; Ruby 1.9는 그룹 재귀 캡처 만 지원합니다 .

(\((?>[^)(]+|\g<1>)*\))

Rubular 데모  ( Ruby 1.9.3 이후 원자 그룹화 )


JavaScript  API :: XRegExp.matchRecursive

XRegExp.matchRecursive(str, '\\(', '\\)', 'g');

최대 2 단계의 중첩을 재귀하지 않는 JS, Java 및 기타 정규 표현식 :

\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)

regex101의 데모 . 패턴에 더 깊은 중첩이 추가 되어야합니다.
불균형 괄호에서 더 빨리 실패하려면 수량 자를 삭제하십시오 +.


자바 : @jaytea의 참조를 사용 하는 흥미로운 아이디어 .


참조-이 정규식은 무엇을 의미합니까?


1
소유 수량 화기를 사용하여 그룹을 반복하는 경우 해당 그룹의 모든 역 추적 위치가 각 반복에서 삭제되므로 해당 그룹을 원자로 만드는 것은 쓸모가 없습니다. 따라서 글쓰기 (?>[^)(]+|(?R))*+는 글쓰기 와 동일 (?:[^)(]+|(?R))*+합니다. 다음 패턴도 마찬가지입니다. 롤링되지 않은 버전에 대해서는 [^)(]*+역 추적을 방지하기 위해 소유주 수량 자를 여기에 넣을 수 있습니다 (닫는 괄호가없는 경우).
Casimir et Hippolyte 2016 년

대신 (많은 중첩 된 괄호가 제한된 관심이 원자 반복 그룹을 만드는 루비 1.9 패턴에 대해 (...(..)..(..)..(..)..(..)..)주제 문자열을)), 당신은 간단한 비 캡처 그룹을 사용할 수 있으며 원자 그룹의 모든 묶어야 : (?>(?:[^)(]+|\g<1>)*)( 이것은 소유 적 정량 자처럼 작동합니다). Ruby 2.x에서는 소유 수량화 도구를 사용할 수 있습니다.
Casimir et Hippolyte 2016 년

감사합니다! PCRE 패턴을 조정했으며 Ruby 1.9의 경우 전체 패턴이 다음 과 같 습니까? 자유롭게 업데이트하십시오. 나는 당신이 의미하는 바를 이해하지만 많은 개선이 있는지 확실하지 않습니다.
버블 버블

117

정규식 재귀 를 사용할 수 있습니다 .

\(([^()]|(?R))*\)

3
예를 들어 여기서 정말 유용 할 것입니다. "(1, (2, 3)) (4, 5)"와 같은 작업에서는이 기능을 사용할 수 없습니다.
Andy Hayden

4
@AndyHayden "(1, (2, 3)) (4, 5)"에는 공백으로 구분 된 두 그룹이 있기 때문입니다. 전역 플래그와 함께 내 정규 표현식을 사용하십시오 : / (([[((^)] | (? R)) *) / g. 온라인 테스트는 다음과 같습니다. regex101.com/r/lF0fI1/1
rogal111


7
.NET 4.5에서는이 패턴에 대해 다음과 같은 오류가 발생합니다 Unrecognized grouping construct.
nam

3
대박! 이것은 정규식의 훌륭한 기능입니다. 실제로 질문에 대답 할 수있는 유일한 사람이되어 주셔서 감사합니다. 또한 그 regex101 사이트는 달콤합니다.
Andrew

28
[^\(]*(\(.*\))[^\)]*

[^\(]*문자열의 시작 부분에 여는 괄호가 아닌 모든 항목을 일치 (\(.*\))시키고 괄호로 묶인 필수 하위 문자열을 캡처하고 문자열 [^\)]*의 끝에서 닫는 괄호가 아닌 모든 항목을 일치시킵니다. 이 표현식은 대괄호와 일치하지 않습니다. 간단한 파서 ( dehmann의 답변 참조 )가 더 적합 할 것입니다.


클래스 내부의 대괄호는 벗어날 필요가 없습니다. 내부는 메타 문자가 아닙니다.
José Leal

10
이 expr은 "(text) text (text)"를 반환하는 "text (text) text (text) text"와 같은 것에 대해 실패합니다. 정규 표현식은 대괄호를 계산할 수 없습니다.
Christian Klauser

17
(?<=\().*(?=\))

일치하는 두 괄호 사이에서 텍스트를 선택하려면 정규식이 적합하지 않습니다. 불가능합니다 (*) .

이 정규식은 문자열의 첫 번째 여는 마지막 괄호 사이의 텍스트를 반환합니다.


(*) 정규식 엔진에 그룹 또는 재귀 균형 조정 과 같은 기능이없는 한 . 이러한 기능을 지원하는 엔진의 수가 점차 증가하고 있지만 여전히 일반적으로 사용 가능한 것은 아닙니다.


"<"및 "="표시는 무엇을 의미합니까? 이 표현식을 타겟팅하는 정규식 엔진은 무엇입니까?
Christian Klauser

1
이것은 둘러보기이거나 더 정확하게는 "제로 너비 미리보기 / 바로보기 어설 션"입니다. 대부분의 최신 정규식 엔진이이를 지원합니다.
Tomalak

OP의 예에 따르면, 그는 경기에서 가장 바깥 쪽 파렌을 포함하려고합니다. 이 정규식은 그들을 버립니다.
Alan Moore

1
@ 앨런 M : 네 말이 맞아. 그러나 질문 본문에 따르면, 그는 가장 바깥 쪽의 파엔 사이 의 모든 것을 원합니다 . 선택하십시오. 그는 몇 시간 동안 노력을 기울이고 있다고 말했기 때문에 "가장 바깥 쪽의 양을 포함한 모든 것"을 의도로 생각하지도 않았다. "(. *)".
Tomalak 2019

3
@ghayes 답은 2009 년입니다. 오래 전입니다. 어떤 형태의 재귀를 허용하는 정규 표현식 엔진은 지금보다 더 드문 일입니다 (그리고 여전히 꽤 드문 일입니다). 내 대답에 언급하겠습니다.
Tomalak

14

이 답변은 왜 정규 표현식이이 작업에 적합한 도구가 아닌지에 대한 이론적 한계를 설명합니다.


정규식은 이것을 할 수 없습니다.

정규식은로 알려진 컴퓨팅 모델을 기반으로합니다 Finite State Automata (FSA). 이름에서 알 수 있듯이 FSA현재 상태 만 기억할 수 있으며 이전 상태에 대한 정보는 없습니다.

FSA

위의 다이어그램에서 S1과 S2는 S1이 시작 및 최종 단계 인 두 가지 상태입니다. 따라서 string 0110을 사용하면 전환이 다음과 같이 진행됩니다.

      0     1     1     0
-> S1 -> S2 -> S2 -> S2 ->S1

우리가 두 번째에있을 때 위의 단계에서, S2구문 분석 후 즉, 010110는 FSA는 이전에 대한 정보가 없습니다 0에서 01그것은 단지 현재 상태 및 다음 입력 기호를 기억할 수 있습니다.

위의 문제에서 우리는 여는 괄호가 없다는 것을 알아야합니다. 즉 , 어떤 곳에 보관 해야합니다 . 그러나 FSAs그렇게 할 수 없기 때문에 정규 표현식을 작성할 수 없습니다.

그러나이 작업을 수행하기 위해 알고리즘을 작성할 수 있습니다. 알고리즘은 일반적으로 아래에 해당됩니다 Pushdown Automata (PDA). PDA의 한 수준 위에 FSA있습니다. PDA에는 추가 정보를 저장하기위한 추가 스택이 있습니다. PDA는 ' push'스택의 여는 괄호와 pop닫는 괄호를 만나면 ' ' 할 수 있기 때문에 위의 문제를 해결하는 데 사용할 수 있습니다 . 마지막에 스택이 비어 있으면 여는 괄호와 닫는 괄호가 일치합니다. 그렇지 않으면 아닙니다.



1
여기에 몇 가지 대답이 있습니다.
Jiří Herník

1
@Marco이 답변은 이론적 인 관점에서 정규 표현식에 대해 이야기합니다. 오늘날 많은 정규식 엔진은이 이론적 모델에 의존 할뿐만 아니라 추가 메모리를 사용하여 작업을 수행합니다!
musibs

@ JiříHerník : 엄격한 의미에서 정규 표현식이 아닙니다 . Kleene에 의해 정규 표현식으로 정의되지 않았습니다 . 일부 정규 표현식 엔진은 실제로 일부 추가 기능을 구현하여 일반 언어 이상으로 구문 분석 합니다 .
Willem Van Onsem 2016 년

12

실제로는 .NET 정규 표현식을 사용하여 수행 할 수는 있지만 사소한 것은 아니므로주의 깊게 읽으십시오.

여기서 좋은 기사를 읽을 수 있습니다 . .NET 정규식을 읽어야 할 수도 있습니다. 여기서 읽을 수 있습니다 .

꺾쇠 괄호 <>는 이스케이프가 필요하지 않기 때문에 사용되었습니다.

정규식은 다음과 같습니다.

<
[^<>]*
(
    (
        (?<Open><)
        [^<>]*
    )+
    (
        (?<Close-Open>>)
        [^<>]*
    )+
)*
(?(Open)(?!))
>

4

이것은 확실한 정규식입니다.

\(
(?<arguments> 
(  
  ([^\(\)']*) |  
  (\([^\(\)']*\)) |
  '(.*?)'

)*
)
\)

예:

input: ( arg1, arg2, arg3, (arg4), '(pip' )

output: arg1, arg2, arg3, (arg4), '(pip'

(가) 있습니다 '(pip'제대로 문자열로 관리됩니다. (레귤레이터에서 시도 : http://sourceforge.net/projects/regulator/ )


4

이 작업을 돕기 위해 balance 라는 작은 JavaScript 라이브러리를 작성했습니다 . 당신은 이것을함으로써 이것을 달성 할 수 있습니다

balanced.matches({
    source: source,
    open: '(',
    close: ')'
});

교체도 가능합니다.

balanced.replacements({
    source: source,
    open: '(',
    close: ')',
    replace: function (source, head, tail) {
        return head + source + tail;
    }
});

더 복잡한 대화식 예제 JSFiddle이 있습니다.


4

버블 버블의 대답에 덧붙여 재귀 구조가 지원되는 다른 정규 표현식이 있습니다.

루아

사용 %b()( %b{}/ %b[]중괄호에 대한 / 대괄호) :

  • for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end( 데모 참조 )

Perl6 :

겹치지 않는 여러 균형 괄호가 일치합니다.

my regex paren_any { '(' ~ ')' [ <-[()]>+ || <&paren_any> ]* }
say "Extract (a(b)c) and ((d)f(g))" ~~ m:g/<&paren_any>/;
# => (「(a(b)c)」 「((d)f(g))」)

여러 개의 괄호로 묶은 겹침 :

say "Extract (a(b)c) and ((d)f(g))" ~~ m:ov:g/<&paren_any>/;
# => (「(a(b)c)」 「(b)」 「((d)f(g))」 「(d)」 「(g)」)

데모를 참조하십시오 .

파이썬 re비정규 솔루션

균형 괄호 사이에서 표현을 얻는 방법에 대한 poke의 답변 을 참조하십시오 .

Java 사용자 정의 가능 비정규 솔루션

다음은 Java에서 단일 문자 리터럴 분리 문자를 허용하는 사용자 정의 가능한 솔루션입니다.

public static List<String> getBalancedSubstrings(String s, Character markStart, 
                                 Character markEnd, Boolean includeMarkers) 

{
        List<String> subTreeList = new ArrayList<String>();
        int level = 0;
        int lastOpenDelimiter = -1;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == markStart) {
                level++;
                if (level == 1) {
                    lastOpenDelimiter = (includeMarkers ? i : i + 1);
                }
            }
            else if (c == markEnd) {
                if (level == 1) {
                    subTreeList.add(s.substring(lastOpenDelimiter, (includeMarkers ? i + 1 : i)));
                }
                if (level > 0) level--;
            }
        }
        return subTreeList;
    }
}

샘플 사용법 :

String s = "some text(text here(possible text)text(possible text(more text)))end text";
List<String> balanced = getBalancedSubstrings(s, '(', ')', true);
System.out.println("Balanced substrings:\n" + balanced);
// => [(text here(possible text)text(possible text(more text)))]

여러 개의 일치 항목과 작동하는지에 대한 증거 는 온라인 Java 데모 를 참조하십시오 .
Wiktor Stribiżew


3

첫 번째와 마지막 괄호가 필요합니다. 다음과 같이 사용하십시오 :

str.indexOf ( '(');-처음 나타날 것입니다

str.lastIndexOf ( ')'); -마지막

따라서 사이에 문자열이 필요합니다.

String searchedString = str.substring(str1.indexOf('('),str1.lastIndexOf(')');

1
"""
Here is a simple python program showing how to use regular
expressions to write a paren-matching recursive parser.

This parser recognises items enclosed by parens, brackets,
braces and <> symbols, but is adaptable to any set of
open/close patterns.  This is where the re package greatly
assists in parsing. 
"""

import re


# The pattern below recognises a sequence consisting of:
#    1. Any characters not in the set of open/close strings.
#    2. One of the open/close strings.
#    3. The remainder of the string.
# 
# There is no reason the opening pattern can't be the
# same as the closing pattern, so quoted strings can
# be included.  However quotes are not ignored inside
# quotes.  More logic is needed for that....


pat = re.compile("""
    ( .*? )
    ( \( | \) | \[ | \] | \{ | \} | \< | \> |
                           \' | \" | BEGIN | END | $ )
    ( .* )
    """, re.X)

# The keys to the dictionary below are the opening strings,
# and the values are the corresponding closing strings.
# For example "(" is an opening string and ")" is its
# closing string.

matching = { "(" : ")",
             "[" : "]",
             "{" : "}",
             "<" : ">",
             '"' : '"',
             "'" : "'",
             "BEGIN" : "END" }

# The procedure below matches string s and returns a
# recursive list matching the nesting of the open/close
# patterns in s.

def matchnested(s, term=""):
    lst = []
    while True:
        m = pat.match(s)

        if m.group(1) != "":
            lst.append(m.group(1))

        if m.group(2) == term:
            return lst, m.group(3)

        if m.group(2) in matching:
            item, s = matchnested(m.group(3), matching[m.group(2)])
            lst.append(m.group(2))
            lst.append(item)
            lst.append(matching[m.group(2)])
        else:
            raise ValueError("After <<%s %s>> expected %s not %s" %
                             (lst, s, term, m.group(2)))

# Unit test.

if __name__ == "__main__":
    for s in ("simple string",
              """ "double quote" """,
              """ 'single quote' """,
              "one'two'three'four'five'six'seven",
              "one(two(three(four)five)six)seven",
              "one(two(three)four)five(six(seven)eight)nine",
              "one(two)three[four]five{six}seven<eight>nine",
              "one(two[three{four<five>six}seven]eight)nine",
              "oneBEGINtwo(threeBEGINfourENDfive)sixENDseven",
              "ERROR testing ((( mismatched ))] parens"):
        print "\ninput", s
        try:
            lst, s = matchnested(s)
            print "output", lst
        except ValueError as e:
            print str(e)
    print "done"

0

대답은 일치하는 대괄호 세트를 일치시켜야하는지 또는 입력 텍스트에서 첫 번째 열림과 마지막 열림 중 일치해야하는지 여부에 따라 다릅니다.

일치하는 중첩 대괄호를 일치시켜야하는 경우 정규식 이상의 것이 필요합니다. - @dehmann 참조

처음에 마지막으로 열었다면 @Zach를 .

당신이하고 싶은 일을 결정 :

abc ( 123 ( foobar ) def ) xyz ) ghij

이 경우 코드가 일치해야하는 것을 결정해야합니다.


3
이것은 답이 아닙니다.
Alan Moore

예, 질문의 변화에 ​​대한 요구는 논평으로 제시되어야합니다
Gangnus

0

JS 정규식은 재귀 일치를 지원하지 않기 때문에 균형 괄호 일치 작업을 수행 할 수 없습니다.

그래서 이것은 "method (arg)"문자열을 배열로 만드는 루프 버전을위한 간단한 자바 스크립트입니다.

push(number) map(test(a(a()))) bass(wow, abc)
$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)
const parser = str => {
  let ops = []
  let method, arg
  let isMethod = true
  let open = []

  for (const char of str) {
    // skip whitespace
    if (char === ' ') continue

    // append method or arg string
    if (char !== '(' && char !== ')') {
      if (isMethod) {
        (method ? (method += char) : (method = char))
      } else {
        (arg ? (arg += char) : (arg = char))
      }
    }

    if (char === '(') {
      // nested parenthesis should be a part of arg
      if (!isMethod) arg += char
      isMethod = false
      open.push(char)
    } else if (char === ')') {
      open.pop()
      // check end of arg
      if (open.length < 1) {
        isMethod = true
        ops.push({ method, arg })
        method = arg = undefined
      } else {
        arg += char
      }
    }
  }

  return ops
}

// const test = parser(`$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)`)
const test = parser(`push(number) map(test(a(a()))) bass(wow, abc)`)

console.log(test)

결과는 같다

[ { method: 'push', arg: 'number' },
  { method: 'map', arg: 'test(a(a()))' },
  { method: 'bass', arg: 'wow,abc' } ]
[ { method: '$$', arg: 'groups' },
  { method: 'filter',
    arg: '{type:\'ORGANIZATION\',isDisabled:{$ne:true}}' },
  { method: 'pickBy', arg: '_id,type' },
  { method: 'map', arg: 'test()' },
  { method: 'as', arg: 'groups' } ]

0

많은 답변이 정규식이 재귀 일치 등을 지원하지 않는다고 말함으로써 어떤 형태로 이것을 언급하지만, 그 주된 이유는 계산 이론의 근본에 있습니다.

양식의 언어 {a^nb^n | n>=0} is not regular . 정규식은 정규 언어 세트의 일부를 구성하는 것만 일치시킬 수 있습니다.

자세히보기 @ 여기


0

중첩 코드를 다루기가 어렵 기 때문에 정규 표현식을 사용하지 않았습니다. 따라서이 스 니펫을 사용하면 대괄호를 사용하여 코드 섹션을 가져올 수 있습니다.

def extract_code(data):
    """ returns an array of code snippets from a string (data)"""
    start_pos = None
    end_pos = None
    count_open = 0
    count_close = 0
    code_snippets = []
    for i,v in enumerate(data):
        if v =='{':
            count_open+=1
            if not start_pos:
                start_pos= i
        if v=='}':
            count_close +=1
            if count_open == count_close and not end_pos:
                end_pos = i+1
        if start_pos and end_pos:
            code_snippets.append((start_pos,end_pos))
            start_pos = None
            end_pos = None

    return code_snippets

이것을 사용하여 텍스트 파일에서 코드 스 니펫을 추출했습니다.


0

또한 중첩 패턴이 나오는 상황에 갇혀있었습니다.

정규식은 위의 문제를 해결하는 것이 옳습니다. 아래 패턴 사용

'/(\((?>[^()]+|(?1))*\))/'


-1

이것은 일부에게 유용 할 수 있습니다.

자바 스크립트에서 함수 문자열 (중첩 구조 포함)에서 매개 변수 구문 분석

다음과 같은 일치 구조 :
함수 문자열에서 파라미터 분석

  • 대괄호, 대괄호, 괄호, 작은 따옴표 및 큰 따옴표와 일치

여기에서 생성 된 정규 표현식을 볼 수 있습니다.

/**
 * get param content of function string.
 * only params string should be provided without parentheses
 * WORK even if some/all params are not set
 * @return [param1, param2, param3]
 */
exports.getParamsSAFE = (str, nbParams = 3) => {
    const nextParamReg = /^\s*((?:(?:['"([{](?:[^'"()[\]{}]*?|['"([{](?:[^'"()[\]{}]*?|['"([{][^'"()[\]{}]*?['")}\]])*?['")}\]])*?['")}\]])|[^,])*?)\s*(?:,|$)/;
    const params = [];
    while (str.length) { // this is to avoid a BIG performance issue in javascript regexp engine
        str = str.replace(nextParamReg, (full, p1) => {
            params.push(p1);
            return '';
        });
    }
    return params;
};

이것은 OP 질문을 완전히 다루지는 않지만 중첩 구조 정규 표현식을 검색하는 데 도움이 될 수는 있지만 유용합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.