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

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

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

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

15 ответов

Решение

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

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

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


.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.


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

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

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

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

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


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

Регулярные выражения основаны на вычислительной модели, известной как Finite State Automata (FSA), Как видно из названия, 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"Их, как только мы сталкиваемся с закрывающей скобкой. Если в конце стек пуст, то открывающая и закрывающая скобки совпадают. В противном случае нет.

Подробное обсуждение можно найти здесь.

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

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

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


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

На самом деле это возможно сделать с помощью регулярных выражений.NET, но это не тривиально, поэтому читайте внимательно.

Вы можете прочитать хорошую статью здесь. Вам также может понадобиться прочитать регулярные выражения.NET. Вы можете начать читать здесь.

Угловые скобки <> были использованы, потому что они не требуют побега.

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

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

Я тоже застрял в этой ситуации, когда появляются вложенные шаблоны.

Регулярное выражение - это то, что нужно для решения вышеуказанной проблемы. Используйте ниже шаблон

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

Это окончательное регулярное выражение:

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

)*
)
\)

Пример:

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

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

обратите внимание, что '(pip' правильно управляется как строка. (пробовал в регуляторе: http://sourceforge.net/projects/regulator/)

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

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

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

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

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

Регулярное выражение с использованием Ruby (версия 1.9.3 или выше):

/(?<match>\((?:\g<match>|[^()]++)*\))/

Демо на Рубларе

Вот настраиваемое решение, разрешающее использование односимвольных литералов в 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)))]

Так что вам нужна первая и последняя скобка, используйте что-то вроде этого str.indexOf('('); - это даст вам первое появление str.lastIndexOf(')'); - последний

так что вам нужна строка между, String искомая строка = str.substring(str1.indexOf('('),str1.lastIndexOf(')');

"""
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"

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

Если вам нужно сопоставить совпадающие вложенные скобки, вам нужно нечто большее, чем регулярные выражения. - см @dehmann

Если это только первый раз, чтобы открыться в последний раз, см. @Zach

Решите, что вы хотите с:

abc ( 123 ( foobar ) def ) xyz ) ghij

Вы должны решить, что ваш код должен соответствовать в этом случае.

Я не использовал регулярное выражение, так как с вложенным кодом сложно работать. Таким образом, этот фрагмент должен позволить вам захватывать разделы кода со сбалансированными скобками:

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

Я использовал это для извлечения фрагментов кода из текстового файла.

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

так что это простой 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' } ]

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

Разбор параметров из строки функции (с вложенными структурами) в 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, но я думаю, что некоторые могут прийти сюда для поиска регулярных выражений вложенной структуры.

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

\s*\w+[(][^+]*[)]\s*

Хотя во многих ответах это упоминается в той или иной форме, говоря, что регулярное выражение не поддерживает рекурсивное сопоставление и т.д., основная причина этого лежит в корнях теории вычислений.

Язык формы {a^nb^n | n>=0} is not regular. Regex может соответствовать только тем вещам, которые являются частью обычного набора языков.

Подробнее @ здесь

Этот тоже работал

re.findall(r'\(.+\)', s)
Другие вопросы по тегам