Регулярное выражение для соответствия сбалансированным скобкам


291

Мне нужно регулярное выражение, чтобы выделить весь текст в двух внешних скобках.

Пример: some text(text here(possible text)text(possible text(more text)))end text

Результат: (text here(possible text)text(possible text(more text)))


3
Этот вопрос очень плохой, потому что не ясно, о чем он спрашивает. Все ответы интерпретировали это по-разному. @DaveF, не могли бы вы уточнить вопрос?
Мэтт Фенвик

1
Ответил в этом сообщении: stackoverflow.com/questions/6331065/…
sship21

Ответы:


145

Регулярные выражения являются неподходящим инструментом для работы, потому что вы имеете дело с вложенными структурами, то есть рекурсией.

Но для этого есть простой алгоритм, который я описал в ответе на предыдущий вопрос .


16
Реализация .NET имеет [Определения балансирующей группы msdn.microsoft.com/en-us/library/…, которые допускают подобные вещи.
Карл Дж

24
Я не согласен с тем, что регулярные выражения являются неправильным инструментом для этого по нескольким причинам. 1) Большинство реализаций регулярных выражений имеют работоспособное, если не идеальное решение для этого. 2) Часто вы пытаетесь найти сбалансированные пары разделителей в контексте, где также используются другие критерии, хорошо подходящие для регулярных выражений. 3) Часто вы передаете регулярное выражение в некоторый API, который принимает только регулярные выражения, и у вас нет выбора.
Кеннет Балтриник


21
Regex - ПРАВИЛЬНЫЙ инструмент для работы. Этот ответ не правильный. Смотрите ответ rogal111.
Андрей

4
Абсолютно согласен с ответом. Хотя в regexp есть некоторые реализации рекурсии, они равны конечным автоматам и не предназначены для работы с вложенными структурами, но это делают контекстно-свободные грамматики. Посмотрите на иерархию формальных грамматик Хомского.
Ник Роз

139

Я хочу добавить этот ответ для быстрой ссылки. Не стесняйтесь обновлять.


.NET Regex с использованием балансировочных групп .

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

Где cиспользуется в качестве счетчика глубины.

Демо на Regexstorm.com


PCRE с использованием рекурсивного шаблона .

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

Демо на regex101 ; Или без чередования:

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

Демо на regex101 ; Или развернут для производительности:

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

Демо на regex101 ; Узор наклеен на (?R)который представляет (?0).

Perl, PHP, Notepad ++, R : perl = TRUE , Python : Regex пакет с (?V1)для поведения Perl.


Использование Ruby вызовов подвыражений .

С 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');

JS, Java и другие регулярные выражения без рекурсии до 2 уровней вложенности:

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

Демонстрация на regex101 . Более глубокое вложение должно быть добавлено к образцу.
Чтобы быстрее потерпеть неудачу при несбалансированной скобке, отбросьте +квантификатор.


Java : интересная идея с использованием прямых ссылок @jaytea .


Ссылка - Что означает это регулярное выражение?


1
Когда вы повторяете группу с притяжательным квантификатором, делать эту группу атомарной бесполезно, поскольку все позиции возврата в этой группе удаляются при каждом повторении. Так что письмо (?>[^)(]+|(?R))*+- это то же самое, что и письмо (?:[^)(]+|(?R))*+. То же самое для следующего шаблона. Что касается развернутой версии, вы можете поместить здесь собственнический квантификатор: [^)(]*+для предотвращения возврата (если нет закрывающей скобки).
Казимир и Ипполит

Что касается шаблона Ruby 1.9, то вместо того, чтобы делать повторяющуюся группу атомарной (которая имеет ограниченный интерес, когда много вложенных скобок (...(..)..(..)..(..)..(..)..)) в строке темы), вы можете использовать простую группу без захвата и заключить все в атомарную группу: (?>(?:[^)(]+|\g<1>)*)( это ведет себя точно так же как притяжательный квантификатор). В Ruby 2.x доступен квантификатор притяжений.
Казимир и Ипполит

@CasimiretHippolyte Спасибо! Я настроил шаблоны PCRE и для Ruby 1.9, вы хотите сказать, что весь шаблон будет таким ? Пожалуйста, не стесняйтесь обновлять себя. Я понимаю, что вы имеете в виду, но не уверен, что есть много улучшений.
пузырьковый пузырь

119

Вы можете использовать регулярные выражения :

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

3
Пример был бы очень полезен здесь, я не могу заставить это работать для таких вещей, как "(1, (2, 3)) (4, 5)".
Энди Хейден

5
@AndyHayden это потому, что "(1, (2, 3)) (4, 5)" имеет две группы, разделенные пробелом. Используйте мое регулярное выражение с глобальным флагом: / (([^ ()] | (? R)) *) / g. Вот онлайн тест: regex101.com/r/lF0fI1/1
rogal111

1
Я задал вопрос об этой прошлой неделе stackoverflow.com/questions/26385984/recursive-pattern-in-regex
Энди Хейден

7
В .NET 4.5 Я получаю следующее сообщение об ошибке для этой модели: Unrecognized grouping construct.
Nam

4
Потрясающие! Это отличная особенность регулярных выражений. Спасибо, что вы единственный, кто действительно ответил на вопрос. Кроме того, этот сайт regex101 хорош.
Андрей

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

[^\(]*сопоставляет все, что не является открывающей скобкой в ​​начале строки, (\(.*\))захватывает требуемую подстроку, заключенную в скобки, и [^\)]*сопоставляет все, что не является закрывающей скобкой в ​​конце строки. Обратите внимание, что это выражение не пытается сопоставить скобки; простой парсер (см . ответ Дехмана ) был бы более подходящим для этого.


скобка внутри класса не нуждается в экранировании. Так как внутри это не метасимвол.
Хосе Лил

10
Этот expr не работает с чем-то вроде «text (text) text (text) text« return »(text) text (text)». Регулярные выражения не могут считать скобки.
Кристиан Клаузер

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

Если вы хотите выбрать текст в двух совпадающих скобках, вам не повезло с регулярными выражениями. Это невозможно (*) .

Это регулярное выражение просто возвращает текст между первым открывающим и последним закрывающими скобками в вашей строке.


(*) Если ваш движок регулярных выражений не имеет таких функций, как балансировка групп или рекурсия . Количество движков, поддерживающих такие функции, медленно растет, но они все еще не являются общедоступными.


Что означают знаки "<=" и "="? На какой механизм регулярных выражений нацелено это выражение?
Кристиан Клаузер

1
Это осмотр, или, точнее, «утверждения упреждения / упущения нулевой ширины». Большинство современных двигателей регулярных выражений поддерживают их.
Томалак

Согласно примеру ОП, он хочет включить в матч самые отдаленные парены. Это регулярное выражение выбрасывает их.
Алан Мур

1
@ Алан М: Вы правы. Но согласно тексту вопроса, он хочет, чтобы все между самыми внешними паренями. Выберите свой выбор. Он сказал, что пытался часами, поэтому даже не рассматривал «все, включая самых крайних парней», как намерение, потому что это так тривиально: «(. *)».
Томалак

3
@ghayes Ответ с 2009 года. Это давно ; механизмы регулярных выражений, которые допускают некоторую форму рекурсии, были более необычными, чем сейчас (и они все еще довольно редки). Я упомяну это в своем ответе.
Томалак

14

Этот ответ объясняет теоретическое ограничение того, почему регулярные выражения не являются подходящим инструментом для этой задачи.


Регулярные выражения не могут этого сделать.

Регулярные выражения основаны на вычислительной модели, известной как Finite State Automata (FSA). Как видно из названия, a FSAможет запомнить только текущее состояние, в нем нет информации о предыдущих состояниях.

FSA

На приведенной выше диаграмме S1 и S2 представляют собой два состояния, где S1 является начальным и последним этапом. Так что, если мы попробуем со строкой 0110, переход будет следующим:

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

В приведенных выше шагов, когда мы находимся на втором , S2т.е. после разбора 01из 0110, АФН не имеет никакой информации о предыдущей 0в 01как он может вспомнить только текущее состояние и следующий входной символ.

В вышеупомянутой проблеме нам нужно знать номер открывающей скобки; это означает, что он должен храниться в каком-то месте. Но так как FSAsне может этого сделать, регулярное выражение не может быть написано.

Однако для этой задачи можно написать алгоритм. Алгоритмы, как правило, подпадают под Pushdown Automata (PDA). PDAна один уровень выше FSA. КПК имеет дополнительный стек для хранения дополнительной информации. КПК могут быть использованы для решения вышеуказанной проблемы, потому что мы можем « pushоткрывать круглые скобки в стеке» и pop«их», когда встречаем закрывающие скобки. Если в конце стек пуст, то открывающая и закрывающая скобки совпадают. В противном случае нет.


Push и pop возможны в регулярном выражении stackoverflow.com/questions/17003799/… регулярные- экспрессионы.info/balancing.html
Марко

1
Здесь есть несколько ответов, которые доказывают, что это возможно.
Иржи Эрник,

1
@Marco Этот ответ говорит о регулярных выражениях в теоретической перспективе. Многие движки регулярных выражений в наши дни не только полагаются на эту теоретическую модель и используют некоторую дополнительную память для своей работы!
Musibs

@ JiříHerník: это не регулярные выражения в строгом смысле: не определяется как регулярные выражения Клини . Некоторые механизмы регулярных выражений действительно реализовали некоторые дополнительные возможности, благодаря чему они могут анализировать не только обычные языки .
Виллем Ван Онсем

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

Я написал небольшую библиотеку JavaScript под названием сбалансированный, чтобы помочь с этой задачей. Вы можете сделать это, выполнив

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

Вы даже можете сделать замены:

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

Вот более сложный и интерактивный пример JSFiddle .


4

В добавление к ответу «всплывающего пузыря» есть и другие варианты регулярных выражений, в которых поддерживаются рекурсивные конструкции.

Lua

Используйте %b()( %b{}/ %b[]для фигурных скобок / квадратных скобок):

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)」)

Смотрите демо .

Python reрешение без регулярных выражений

См ответ тыкать в для Как получить выражение между сбалансированными скобками .

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
Это не ответ.
Алан Мур

Да, требование об изменении вопроса следует представить в виде комментария,
Гангнус

0

Поскольку регулярное выражение js не поддерживает рекурсивное сопоставление, я не могу сделать сопоставление в скобках.

так что это простой javascript для версии цикла, который превращает строку «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 . Regex может соответствовать только вещам, которые являются частью обычного набора языков.

Читать дальше @ здесь


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

Это может быть полезно для некоторых:

Разбор параметров из строки функции (с вложенными структурами) в JavaScript

Структуры соответствия, такие как:
Разбор параметров из строки функции

  • соответствует скобкам, квадратным скобкам, круглым скобкам, одинарным и двойным кавычкам

Здесь вы можете увидеть сгенерированное регулярное выражение в действии

/**
 * 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, но я думаю, что это может быть полезным для некоторых, приходящих сюда для поиска регулярных выражений вложенной структуры.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.