Expression régulière pour correspondre à des parenthèses équilibrées


290

J'ai besoin d'une expression régulière pour sélectionner tout le texte entre deux crochets extérieurs.

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

Résultat: (text here(possible text)text(possible text(more text)))


3
Cette question est très médiocre parce que ce qu'elle demande n'est pas clair. Toutes les réponses l'ont interprétée différemment. @DaveF pouvez-vous clarifier la question?
Matt Fenwick

1
Répondu dans cet article: stackoverflow.com/questions/6331065/…
sship21

Réponses:


144

Les expressions régulières ne sont pas le bon outil pour le travail car vous avez affaire à des structures imbriquées, c'est-à-dire à la récursivité.

Mais il existe un algorithme simple pour ce faire, que j'ai décrit dans cette réponse à une question précédente .


15
L'implémentation de .NET a [Balancing Group Definitions msdn.microsoft.com/en-us/library/… qui permettent ce genre de chose.
Carl G

22
Je ne suis pas d'accord pour dire que les expressions régulières ne sont pas le bon outil pour cela pour plusieurs raisons. 1) La plupart des implémentations d'expressions régulières ont une solution fonctionnelle sinon parfaite pour cela. 2) Souvent, vous essayez de trouver des paires de délimiteurs équilibrées dans un contexte où d'autres critères bien adaptés aux expressions régulières sont également en jeu. 3) Souvent, vous transférez une expression régulière dans une API qui n'accepte que des expressions régulières et vous n'avez pas le choix.
Kenneth Baltrinic


20
Regex est le bon outil pour le travail. Cette réponse n'est pas correcte. Voir la réponse de rogal111.
Andrew

4
Tout à fait d'accord avec la réponse. Bien qu'il existe certaines implémentations de récursivité dans l'expression rationnelle, elles sont égales aux machines à états finis et ne sont pas supposées fonctionner avec des structures imbriquées, mais les grammaires libres de contexte le font. Regardez la hiérarchie des grammaires formelles de Homsky.
Nick Roz

138

Je veux ajouter cette réponse pour référence rapide. N'hésitez pas à mettre à jour.


.NET Regex utilisant des groupes d'équilibrage .

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

cest utilisé comme compteur de profondeur.

Démo sur Regexstorm.com


PCRE utilisant un modèle récursif .

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

Démo à regex101 ; Ou sans alternance:

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

Démo à regex101 ; Ou déroulé pour la performance:

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

Démo à regex101 ; Le motif est collé sur (?R)lequel représente (?0).

Perl, PHP, Notepad ++, R : perl = TRUE , Python : package Regex avec (?V1)pour le comportement Perl.


Ruby utilisant des appels de sous-expression .

Avec Ruby 2.0 \g<0>peut être utilisé pour appeler le modèle complet.

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

Démo chez Rubular ; Ruby 1.9 ne prend en charge que la capture de la récursivité de groupe :

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

Démo chez Rubular  ( groupement atomique depuis Ruby 1.9.3)


 API JavaScript :: XRegExp.matchRecursive

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

JS, Java et autres saveurs regex sans récursivité jusqu'à 2 niveaux d'imbrication:

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

Démo sur regex101 . Une imbrication plus profonde doit être ajoutée au modèle.
Pour échouer plus rapidement sur des parenthèses déséquilibrées, supprimez le +quantificateur.


Java : une idée intéressante utilisant les références directes de @jaytea .


Référence - Que signifie cette expression régulière?


1
Lorsque vous répétez un groupe avec un quantificateur possessif, il est inutile de rendre ce groupe atomique car toutes les positions de retour en arrière de ce groupe sont supprimées à chaque répétition. L'écriture (?>[^)(]+|(?R))*+est donc la même chose que l'écriture (?:[^)(]+|(?R))*+. Même chose pour le motif suivant. À propos de la version déroulée, vous pouvez mettre un quantificateur possessif ici: [^)(]*+pour éviter le retour en arrière (au cas où il n'y aurait pas de crochet de fermeture).
Casimir et Hippolyte le

À propos du modèle Ruby 1.9, au lieu de rendre le groupe répété atomique (qui a un intérêt limité quand il y a beaucoup de parenthèses imbriquées (...(..)..(..)..(..)..(..)..)) dans la chaîne du sujet), vous pouvez utiliser un groupe non capturant simple et enfermer le tout dans un groupe atomique: (?>(?:[^)(]+|\g<1>)*)( cela se comporte exactement comme un quantificateur possessif). Dans Ruby 2.x, le quantificateur possessif est disponible.
Casimir et Hippolyte

@CasimiretHippolyte Merci! J'ai ajusté les motifs PCRE et pour Ruby 1.9, voulez-vous dire que l'ensemble du motif doit être comme ça ? N'hésitez pas à vous mettre à jour. Je comprends ce que vous voulez dire, mais je ne sais pas s'il y a beaucoup d'amélioration.
Bobble bubble

117

Vous pouvez utiliser la récursion regex :

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

3
Un exemple serait vraiment utile ici, je ne peux pas le faire fonctionner pour des choses comme "(1, (2, 3)) (4, 5)".
Andy Hayden

4
@AndyHayden c'est parce que "(1, (2, 3)) (4, 5)" a deux groupes séparés par un espace. Utilisez mon expression rationnelle avec l'indicateur global: / (([^ ()] | (? R)) *) / g. Voici le test en ligne: regex101.com/r/lF0fI1/1
rogal111

1
J'ai posé une question à propos de cette semaine stackoverflow.com/questions/26385984/recursive-pattern-in-regex
Andy Hayden

7
Dans .NET 4.5 , je reçois l'erreur suivante pour ce modèle: Unrecognized grouping construct.
nam

3
Impressionnant! C'est une grande fonctionnalité de regex. Merci d'être le seul à avoir répondu à la question. En outre, ce site regex101 est doux.
Andrew

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

[^\(]*correspond à tout ce qui n'est pas un crochet ouvrant au début de la chaîne, (\(.*\))capture la sous-chaîne requise entre crochets et [^\)]*correspond à tout ce qui n'est pas un crochet fermant à la fin de la chaîne. Notez que cette expression ne tente pas de faire correspondre les crochets; un simple analyseur (voir la réponse de dehmann ) serait plus approprié pour cela.


le crochet à l'intérieur de la classe n'a pas besoin d'être échappé. Puisqu'à l'intérieur ce n'est pas un métacaracté.
José Leal

10
Cette expr échoue contre quelque chose comme "texte (texte) texte (texte) texte" renvoyant "(texte) texte (texte)". Les expressions régulières ne peuvent pas compter les crochets.
Christian Klauser

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

Si vous souhaitez sélectionner du texte entre deux parenthèses correspondantes , vous n'avez pas de chance avec les expressions régulières. C'est impossible (*) .

Cette expression régulière renvoie simplement le texte entre la première ouverture et les dernières parenthèses fermantes de votre chaîne.


(*) À moins que votre moteur d'expression régulière n'ait des fonctionnalités comme l' équilibrage des groupes ou la récursivité . Le nombre de moteurs qui prennent en charge de telles fonctionnalités augmente lentement, mais ils ne sont toujours pas disponibles.


Que signifient les signes "<=" et "="? Quel moteur d'expression régulière cette expression cible-t-elle?
Christian Klauser

1
Il s'agit de regarder autour de soi, ou plus correctement de «déclarations de largeur / longueur d'avance / de recul». La plupart des moteurs regex modernes les prennent en charge.
Tomalak

Selon l'exemple du PO, il souhaite inclure les parens les plus externes dans le match. Cette expression régulière les jette.
Alan Moore

1
@Alan M: Vous avez raison. Mais selon le texte de la question, il veut tout entre les parens les plus externes. Choisissez votre choix. Il a dit qu'il avait essayé pendant des heures, donc il n'a même pas considéré "tout, y compris les parens les plus externes" comme l'intention, car c'est si trivial: "(. *)".
Tomalak

3
@ghayes La réponse est de 2009. C'est il y a longtemps ; Les moteurs d'expression régulière qui permettent une certaine forme de récursivité ont été plus rares qu'ils ne le sont actuellement (et ils sont encore assez rares). Je vais le mentionner dans ma réponse.
Tomalak

14

Cette réponse explique la limitation théorique des raisons pour lesquelles les expressions régulières ne sont pas le bon outil pour cette tâche.


Les expressions régulières ne peuvent pas faire cela.

Les expressions régulières sont basées sur un modèle informatique appelé Finite State Automata (FSA). Comme son nom l'indique, un FSAne peut se souvenir que de l'état actuel, il n'a aucune information sur les états précédents.

FSA

Dans le diagramme ci-dessus, S1 et S2 sont deux états où S1 est l'étape initiale et finale. Donc, si nous essayons avec la chaîne 0110, la transition se déroule comme suit:

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

Dans les étapes ci - dessus, lorsque nous sommes à la deuxième S2soit après l' analyse syntaxique 01de 0110la FSA n'a pas d' informations sur le précédent 0en 01car il ne peut se rappeler l'état actuel et le symbole d'entrée suivante.

Dans le problème ci-dessus, nous devons connaître le non de la parenthèse ouvrante; cela signifie qu'il doit être stocké à un endroit. Mais puisque FSAscela ne peut pas être fait, une expression régulière ne peut pas être écrite.

Cependant, un algorithme peut être écrit pour effectuer cette tâche. Les algorithmes relèvent généralement du domaine Pushdown Automata (PDA). PDAest un niveau au-dessus de FSA. PDA dispose d'une pile supplémentaire pour stocker des informations supplémentaires. Les PDA peuvent être utilisés pour résoudre le problème ci-dessus, car nous pouvons ` push` les parenthèses ouvrantes dans la pile et ' pop' les une fois que nous rencontrons une parenthèse fermante. Si à la fin, la pile est vide, l'ouverture des parenthèses et la fermeture des parenthèses correspondent. Sinon non.



1
Il y a plusieurs réponses ici, ce qui prouve, c'est possible.
Jiří Herník

1
@Marco Cette réponse parle d'expressions régulières dans une perspective théorique. De nombreux moteurs regex ne dépendent plus seulement de ce modèle théorique et utilisent de la mémoire supplémentaire pour faire le travail!
musibs

@ JiříHerník: ce ne sont pas des expressions régulières au sens strict: non définies comme des expressions régulières par Kleene . Certains moteurs d'expression régulière ont en effet mis en œuvre des fonctionnalités supplémentaires, ce qui les rend plus analysés que les langues régulières .
Willem Van Onsem

12

Il est en fait possible de le faire en utilisant des expressions régulières .NET, mais ce n'est pas trivial, alors lisez attentivement.

Vous pouvez lire un bel article ici . Vous devrez peut-être également lire les expressions régulières .NET. Vous pouvez commencer à lire ici .

Des équerres <>ont été utilisées car elles ne nécessitent pas de fuite.

L'expression régulière ressemble à ceci:

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

4

C'est le regex définitif:

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

)*
)
\)

Exemple:

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

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

notez que le '(pip'est correctement géré en tant que chaîne. (essayé dans le régulateur: http://sourceforge.net/projects/regulator/ )


4

J'ai écrit une petite bibliothèque JavaScript appelée équilibrée pour aider à cette tâche. Vous pouvez accomplir cela en faisant

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

Vous pouvez même faire des remplacements:

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

Voici un exemple JSFiddle plus complexe et interactif .


4

Pour ajouter à la réponse de Bobble Bubble , il existe d'autres saveurs d'expression régulière où les constructions récursives sont prises en charge.

Lua

Utilisation %b()( %b{}/ %b[]pour les accolades / crochets):

  • for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end(voir démo )

Perl6 :

Correspondances entre parenthèses équilibrées multiples sans chevauchement:

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

Chevauchement de plusieurs correspondances équilibrées entre parenthèses:

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

Voir la démo .

reSolution Python non regex

Voir la réponse de poke pour savoir comment obtenir une expression entre parenthèses équilibrées .

Solution non regex personnalisable Java

Voici une solution personnalisable permettant des délimiteurs littéraux à un seul caractère en 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;
    }
}

Exemple d'utilisation:

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

Voir une démo Java en ligne pour une preuve qu'il fonctionne avec plusieurs correspondances.
Wiktor Stribiżew


3

Vous avez besoin des première et dernière parenthèses. Utilisez quelque chose comme ceci:

str.indexOf ('('); - il vous donnera la première occurrence

str.lastIndexOf (')'); - le dernier

Vous avez donc besoin d'une chaîne 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

La réponse dépend de si vous devez faire correspondre les ensembles de crochets correspondants, ou simplement de la première ouverture à la dernière fermeture dans le texte d'entrée.

Si vous devez faire correspondre les crochets imbriqués correspondants, vous avez besoin de quelque chose de plus que des expressions régulières. - voir @dehmann

Si c'est juste ouvert pour fermer, voir @Zach

Décidez de ce que vous voulez faire avec:

abc ( 123 ( foobar ) def ) xyz ) ghij

Vous devez décider à quoi votre code doit correspondre dans ce cas.


3
Ce n'est pas une réponse.
Alan Moore

Oui, la demande de modification de la question doit être donnée en commentaire,
Gangnus

0

parce que js regex ne prend pas en charge la correspondance récursive, je ne peux pas faire fonctionner la correspondance de parenthèses équilibrées.

c'est donc un simple javascript pour la version en boucle qui transforme la chaîne "method (arg)" en tableau

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)

le résultat est comme

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

Alors que tant de réponses mentionnent cela sous une forme quelconque en disant que l'expression régulière ne prend pas en charge la correspondance récursive et ainsi de suite, la principale raison de cela réside dans les racines de la théorie du calcul.

Langue du formulaire {a^nb^n | n>=0} is not regular. Regex ne peut correspondre qu'à des éléments qui font partie de l'ensemble régulier de langues.

En savoir plus @ ici


0

Je n'ai pas utilisé l'expression régulière car il est difficile de gérer le code imbriqué. Cet extrait devrait donc vous permettre de récupérer des sections de code avec des crochets équilibrés:

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

J'ai utilisé cela pour extraire des extraits de code d'un fichier texte.


0

J'étais également coincé dans cette situation où les modèles imbriqués viennent.

L'expression régulière est la bonne chose pour résoudre le problème ci-dessus. Utiliser le modèle ci-dessous

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


-1

Cela peut être utile à certains:

Analyser les paramètres de la chaîne de fonction (avec des structures imbriquées) en javascript

Faites correspondre les structures comme:
Analyser les paramètres de la chaîne de fonction

  • correspond aux crochets, crochets, parenthèses, guillemets simples et doubles

Ici vous pouvez voir l'expression rationnelle générée en action

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

Cela ne répond pas entièrement à la question OP mais je pense que cela peut être utile à certains venant ici pour rechercher des expressions rationnelles de structure imbriquée.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.