Expressão regular para combinar parênteses balanceados


290

Eu preciso de uma expressão regular para selecionar todo o texto entre dois colchetes externos.

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

Resultado: (text here(possible text)text(possible text(more text)))


3
Essa pergunta é muito ruim porque não está claro o que está perguntando. Todas as respostas o interpretaram de maneira diferente. @DaveF, você pode esclarecer a pergunta?
Matt Fenwick

Respostas:


144

Expressões regulares são a ferramenta errada para o trabalho porque você está lidando com estruturas aninhadas, ou seja, recursão.

Mas existe um algoritmo simples para fazer isso, que descrevi nesta resposta a uma pergunta anterior .


15
A implementação do .NET possui [Balancing Group Definitions msdn.microsoft.com/en-us/library/… que permite esse tipo de coisa.
Carl G

22
Eu discordo que expressões regulares são a ferramenta errada para isso por alguns motivos. 1) A maioria das implementações de expressão regular tem uma solução viável, se não perfeita, para isso. 2) Muitas vezes, você está tentando encontrar pares equilibrados de delimitadores em um contexto em que outros critérios bem adequados para expressões regulares também estão em jogo. 3) Muitas vezes, você está entregando uma expressão regular a alguma API que aceita apenas expressões regulares e você não tem escolha.
Kenneth Baltrinic


20
Regex é a ferramenta certa para o trabalho. Esta resposta não está certa. Veja a resposta de rogal111.
22815 Andrew Andrew

4
Concordo absolutamente com a resposta. Embora existam algumas implementações de recursão no regexp, elas são iguais às máquinas de estado finito e não devem trabalhar com estruturas aninhadas, mas as Gramáticas Livres de Contexto fazem isso. Veja a hierarquia de gramáticas formais de Homsky.
Nick Roz

138

Quero adicionar esta resposta para uma referência rápida. Sinta-se livre para atualizar.


Regex do .NET usando grupos de balanceamento .

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

Onde cé usado como contador de profundidade.

Demo em Regexstorm.com


PCRE usando um padrão recursivo .

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

Demonstração em regex101 ; Ou sem alternância:

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

Demonstração em regex101 ; Ou desenrolado para desempenho:

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

Demonstração em regex101 ; O padrão é colado no (?R)qual representa (?0).

Perl, PHP, Notepad ++, R : perl = TRUE , pacote Python : Regex com (?V1)comportamento Perl.


Ruby usando chamadas de subexpressão .

Com Ruby 2.0 \g<0>pode ser usado para chamar padrão completo.

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

Demonstração no Rubular ; O Ruby 1.9 suporta apenas a captura de recursão de grupo :

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

Demonstração no Rubular  ( agrupamento atômico desde Ruby 1.9.3)


 API JavaScript :: XRegExp.matchRecursive

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

JS, Java e outros tipos de expressões regulares sem recursão até 2 níveis de aninhamento:

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

Demonstração em regex101 . Aninhamento mais profundo precisa ser adicionado ao padrão.
Para falhar mais rápido em parênteses desequilibrados, solte o +quantificador.


Java : Uma ideia interessante usando referências futuras de @jaytea .


Referência - O que esse regex significa?


1
Quando você repete um grupo com um quantificador possessivo, é inútil tornar esse grupo atômico, pois todas as posições de retorno nesse grupo são excluídas a cada repetição. Então escrever (?>[^)(]+|(?R))*+é o mesmo que escrever (?:[^)(]+|(?R))*+. A mesma coisa para o próximo padrão. Sobre a versão desenrolada, você pode colocar um quantificador possessivo aqui: [^)(]*+para impedir o retorno (no caso de não haver colchete).
Casimir et Hippolyte

Sobre o padrão Ruby 1.9, em vez de tornar o grupo repetido atômico (que tem um interesse limitado quando há muitos parênteses aninhados (...(..)..(..)..(..)..(..)..)) na sequência do assunto), você pode usar um grupo não capturador simples e incluir tudo em um grupo atômico: (?>(?:[^)(]+|\g<1>)*)( isso se comporta exatamente como um quantificador possessivo). No Ruby 2.x, o quantificador possessivo está disponível.
Casimir et Hippolyte

@CasimiretHippolyte Thank you! Eu ajustei os padrões PCRE e, para o Ruby 1.9, você quer dizer que todo o padrão seja assim ? Por favor, sinta-se livre para se atualizar. Entendo o que você quer dizer, mas não tenho certeza se há muita melhoria.
precisa

117

Você pode usar a recursão de regex :

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

3
Um exemplo seria realmente útil aqui, não consigo fazer isso funcionar para coisas como "(1, (2, 3)) (4, 5)".
Andy Hayden

4
@AndyHayden, isso ocorre porque "(1, (2, 3)) (4, 5)" possui dois grupos separados por espaço. Use meu regexp com o sinalizador global: / (([^ ()] | (? R)) *) / g. Aqui está o teste on-line: regex101.com/r/lF0fI1/1
rogal111 23/10

1
Eu fiz uma pergunta sobre isso na semana passada stackoverflow.com/questions/26385984/recursive-pattern-in-regex
Andy Hayden

7
Em .NET 4.5 Eu recebo o seguinte erro para este padrão: Unrecognized grouping construct.
nam

3
Impressionante! Esse é um ótimo recurso do regex. Obrigado por ser o único a realmente responder à pergunta. Além disso, esse site regex101 é interessante.
22815 Andrew Andrew

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

[^\(]*corresponde a tudo que não é um colchete de abertura no início da sequência, (\(.*\))captura a substring necessária entre colchetes e [^\)]*corresponde a tudo que não é um colchete de fechamento no final da sequência. Observe que esta expressão não tenta combinar colchetes; um simples analisador (veja a resposta de dehmann ) seria mais adequado para isso.


o suporte dentro da classe não precisa ser escapado. Uma vez que por dentro não é um metacharacted.
José Leal

10
Este expr falha em algo como "texto (texto) texto (texto) texto" retornando "(texto) texto (texto)". Expressões regulares não podem contar colchetes.
23410 Christian Klauser

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

Se você quiser selecionar texto entre dois correspondentes parênteses, você está fora de sorte com expressões regulares. Isso é impossível (*) .

Essa regex apenas retorna o texto entre a primeira abertura e os últimos parênteses de fechamento em sua string.


(*) A menos que seu mecanismo de expressão regular tenha recursos como grupos de balanceamento ou recursão . O número de mecanismos que suportam esses recursos está crescendo lentamente, mas eles ainda não estão disponíveis.


O que significam os sinais "<=" e "="? Qual mecanismo de expressão regular está direcionado para esta expressão?
23410 Christian Klauser

1
Trata-se de declarações gerais, ou mais corretamente, "afirmações de largura zero para frente / para trás". A maioria dos mecanismos regex modernos os suporta.
306 Tomalak

De acordo com o exemplo do OP, ele deseja incluir os parênteses mais externos na partida. Esse regex os joga fora.
277 Alan Moore

1
@ Alan M: Você está certo. Mas, de acordo com o texto da pergunta, ele quer tudo entre os parênteses mais externos. Escolha a sua escolha. Ele disse que estava tentando há horas, então nem sequer considerou "tudo, incluindo os limites mais externos" como a intenção, porque é tão trivial: "(. *)".
Tomalak

3
@ ghayes A resposta é de 2009. Isso faz muito tempo; Os mecanismos de expressão regular que permitem alguma forma de recursão são mais incomuns do que são agora (e ainda são bastante incomuns). Vou mencionar isso na minha resposta.
Tomalak

14

Esta resposta explica a limitação teórica de por que expressões regulares não são a ferramenta certa para esta tarefa.


Expressões regulares não podem fazer isso.

Expressões regulares são baseadas em um modelo de computação conhecido como Finite State Automata (FSA). Como o nome indica, um FSApode lembrar apenas o estado atual, não possui informações sobre os estados anteriores.

FSA

No diagrama acima, S1 e S2 são dois estados em que S1 é a etapa inicial e final. Portanto, se tentarmos com a string 0110, a transição será a seguinte:

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

Nos passos acima, quando estamos em segundo S2ou seja, após a análise 01de 0110, a FSA não tem nenhuma informação sobre o anterior 0em 01como ele só consegue se lembrar o estado atual eo próximo símbolo de entrada.

No problema acima, precisamos saber o não dos parênteses de abertura; isso significa que deve ser armazenado em algum lugar. Mas como FSAsnão é possível fazer isso, uma expressão regular não pode ser escrita.

No entanto, um algoritmo pode ser escrito para executar esta tarefa. Algoritmos são geralmente cai Pushdown Automata (PDA). PDAé um nível acima de FSA. O PDA possui uma pilha adicional para armazenar algumas informações adicionais. Os PDAs podem ser usados ​​para resolver o problema acima, porque podemos ' push' o parêntese de abertura na pilha e ' pop' eles quando encontrarmos um parêntese de fechamento. Se, no final, a pilha estiver vazia, abra os parênteses e feche os parênteses. Caso contrário, não.



1
Existem várias respostas aqui, o que prova, é possível.
Jiří Herník 20/09/19

1
@Marco Esta resposta fala sobre expressões regulares em perspectiva teórica. Hoje em dia, muitos mecanismos regex não dependem apenas desse modelo teórico e usam alguma memória adicional para fazer o trabalho!
Musibs

@ JiříHerník: essas não são expressões regulares no sentido estrito: não definidas como expressões regulares por Kleene . Alguns mecanismos de expressão regular implementaram alguns recursos extras, fazendo com que analisassem mais do que apenas linguagens regulares .
Willem Van Onsem

12

Na verdade, é possível fazê-lo usando expressões regulares do .NET, mas não é trivial, portanto, leia com atenção.

Você pode ler um bom artigo aqui . Você também pode precisar ler expressões regulares do .NET. Você pode começar a ler aqui .

Parênteses angulares <>foram usados ​​porque não requerem escape.

A expressão regular é assim:

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

4

Este é o regex definitivo:

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

)*
)
\)

Exemplo:

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

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

observe que o '(pip'é gerenciado corretamente como string. (experimentado no regulador: http://sourceforge.net/projects/regulator/ )


4

Eu escrevi uma pequena biblioteca JavaScript chamada equilibrada para ajudar nessa tarefa. Você pode conseguir isso fazendo

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

Você pode até fazer substituições:

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

Aqui está um exemplo mais complexo e interativo do JSFiddle .


4

Além da resposta do bobble bubble , existem outros tipos de expressões regulares em que as construções recursivas são suportadas.

Lua

Use %b()( %b{}/ %b[]para chaves / colchetes):

  • for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end(veja demonstração )

Perl6 :

Correspondências de múltiplos parênteses equilibrados sem sobreposição:

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

Sobreposição de vários parênteses balanceados:

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

Veja a demonstração .

reSolução não regex Python

Veja a resposta do puxão em Como obter uma expressão entre parênteses balanceados .

Solução não regex personalizável em Java

Aqui está uma solução personalizável que permite delimitadores literais de um caractere em 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;
    }
}

Uso da amostra:

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

Veja uma demonstração online do Java para obter uma prova de que funciona com várias correspondências.
precisa saber é o seguinte


3

Você precisa do primeiro e do último parênteses. Use algo como isto:

str.indexOf ('('); - ele fornecerá a primeira ocorrência

str.lastIndexOf (')'); - último

Então você precisa de uma string entre,

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

A resposta depende se você precisa corresponder a conjuntos de colchetes correspondentes ou apenas o primeiro aberto até o último fechamento no texto de entrada.

Se você precisar combinar colchetes aninhados correspondentes, precisará de algo mais do que expressões regulares. - Vejo @dehmann

Se for apenas o primeiro aberto para o último fechamento, veja @Zach

Decida com o que você deseja que aconteça:

abc ( 123 ( foobar ) def ) xyz ) ghij

Você precisa decidir com o que seu código deve corresponder neste caso.


3
Esta não é uma resposta.
Alan Moore

Sim, a demanda por uma mudança na pergunta deve ser dada como um comentário, #
Gangnus

0

Como o js regex não suporta correspondência recursiva, não consigo fazer o trabalho de correspondência entre parênteses balanceados.

portanto, este é um javascript simples para a versão de loop que transforma a string "method (arg)" na matriz

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)

o resultado é como

[ { 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

Enquanto tantas respostas mencionam isso de alguma forma, dizendo que o regex não suporta correspondência recursiva e assim por diante, a principal razão para isso está nas raízes da Teoria da Computação.

Idioma do formulário {a^nb^n | n>=0} is not regular . O Regex pode corresponder apenas a itens que fazem parte do conjunto regular de idiomas.

Leia mais @ aqui


0

Não usei regex, pois é difícil lidar com código aninhado. Portanto, esse snippet deve permitir que você pegue seções do código com colchetes balanceados:

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

Eu usei isso para extrair trechos de código de um arquivo de texto.


0

Eu também estava preso nessa situação em que padrões aninhados aparecem.

Expressão regular é a coisa certa para resolver o problema acima. Use abaixo do padrão

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


-1

Isso pode ser útil para alguns:

Analisar parâmetros da string de função (com estruturas aninhadas) em javascript

Combine estruturas como:
Analisar parâmetros da sequência de funções

  • corresponde a colchetes, colchetes, parênteses, aspas simples e duplas

Aqui você pode ver o regexp gerado em ação

/**
 * 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;
};

Isso não aborda completamente a questão do OP, mas eu acho que pode ser útil para alguns que vêm aqui procurar a estrutura aninhada regexp.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.