Esiste un'espressione regolare per rilevare un'espressione regolare valida?


1007

È possibile rilevare un'espressione regolare valida con un'altra espressione regolare? In tal caso, fornire il seguente esempio di codice.


58
Quindi il tuo problema è convalidare una regex, hai scelto una regex per risolverlo. Mi chiedo se la proprietà di regexes che aumenta il numero di problemi sia additiva o moltiplicativa. Sembra 4 problemi invece di 2 :)
circa il

15
Esistono molte notazioni per le espressioni regolari - alcune caratteristiche e le loro ortografie sono comuni alla maggior parte, alcune sono scritte diversamente o sono disponibili solo in una notazione particolare. La maggior parte di quelle notazioni non sono "regolari" nel senso grammaticale regolare - avresti bisogno di un parser privo di contesto per gestire l'annidamento illimitato delle sottoespressioni - sebbene molte notazioni moderne di "espressione regolare" abbiano estensioni che vanno oltre la definizione formale originale e potrebbe consentire il riconoscimento delle proprie notazioni. In ogni caso, perché non semplicemente chiedere alla tua libreria regex se ogni regex è valida?
Steve314,

1
@bevacqua ho bisogno di convalidare regexp nello schema XML. Come posso farlo senza un'altra regexp?
zenden2k

3
Compilare / eseguire effettivamente il regex (modello) da verificare, in base a un meccanismo di gestione delle eccezioni della propria lingua. Quindi lo stesso motore / compilatore regex del linguaggio lo controllerà. (Ciò presuppone la sintassi di base corretta in modo che il programma venga eseguito, ma che può essere incluso nel controllo utilizzando le strutture delle tue lingue per valutare la stringa per la regex come codice (possibilmente sintatticamente errato) o simile.)
zdim

Questa è la risposta perfetta per gli utenti di Python: stackoverflow.com/questions/19630994/…
gianni

Risposte:


979
/
^                                             # start of string
(                                             # first group start
  (?:
    (?:[^?+*{}()[\]\\|]+                      # literals and ^, $
     | \\.                                    # escaped characters
     | \[ (?: \^?\\. | \^[^\\] | [^\\^] )     # character classes
          (?: [^\]\\]+ | \\. )* \]
     | \( (?:\?[:=!]|\?<[=!]|\?>)? (?1)?? \)  # parenthesis, with recursive content
     | \(\? (?:R|[+-]?\d+) \)                 # recursive matching
     )
    (?: (?:[?+*]|\{\d+(?:,\d*)?\}) [?+]? )?   # quantifiers
  | \|                                        # alternative
  )*                                          # repeat content
)                                             # end first group
$                                             # end of string
/

Questa è una regex ricorsiva e non è supportata da molti motori regex. Quelli basati su PCRE dovrebbero supportarlo.

Senza spazi bianchi e commenti:

/^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d+(?:,\d*)?\})[?+]?)?|\|)*)$/

.NET non supporta direttamente la ricorsione. (The (?1)e (?R)costrutti.) La ricorsione dovrebbe essere convertita in conteggio di gruppi bilanciati:

^                                         # start of string
(?:
  (?: [^?+*{}()[\]\\|]+                   # literals and ^, $
   | \\.                                  # escaped characters
   | \[ (?: \^?\\. | \^[^\\] | [^\\^] )   # character classes
        (?: [^\]\\]+ | \\. )* \]
   | \( (?:\?[:=!]
         | \?<[=!]
         | \?>
         | \?<[^\W\d]\w*>
         | \?'[^\W\d]\w*'
         )?                               # opening of group
     (?<N>)                               #   increment counter
   | \)                                   # closing of group
     (?<-N>)                              #   decrement counter
   )
  (?: (?:[?+*]|\{\d+(?:,\d*)?\}) [?+]? )? # quantifiers
| \|                                      # alternative
)*                                        # repeat content
$                                         # end of string
(?(N)(?!))                                # fail if counter is non-zero.

Compatto:

^(?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>|\?<[^\W\d]\w*>|\?'[^\W\d]\w*')?(?<N>)|\)(?<-N>))(?:(?:[?+*]|\{\d+(?:,\d*)?\})[?+]?)?|\|)*$(?(N)(?!))

Dai commenti:

Convalideranno sostituzioni e traduzioni?

Convaliderà solo la parte regex di sostituzioni e traduzioni. s/<this part>/.../

Non è teoricamente possibile abbinare tutte le grammatiche regex valide con una regex.

È possibile se il motore regex supporta la ricorsione, come PCRE, ma ciò non può più essere chiamato espressioni regolari.

In effetti, una "espressione regolare ricorsiva" non è un'espressione regolare. Ma questa è un'estensione spesso accettata per i motori regex ... Ironia della sorte, questa regex estesa non corrisponde alle regex estese.

"In teoria, teoria e pratica sono le stesse. In pratica, non lo sono." Quasi tutti coloro che conoscono le espressioni regolari sanno che le espressioni regolari non supportano la ricorsione. Ma PCRE e la maggior parte delle altre implementazioni supportano molto più delle espressioni regolari di base.

usando questo con lo script della shell nel comando grep, mi mostra qualche errore. grep: contenuto non valido di {}. Sto realizzando uno script che potrebbe grep una base di codice per trovare tutti i file che contengono espressioni regolari

Questo modello sfrutta un'estensione chiamata espressioni regolari ricorsive. Questo non è supportato dal sapore POSIX di regex. Puoi provare con l'opzione -P, per abilitare il sapore regex di PCRE.

Lo stesso Regex "non è un linguaggio regolare e quindi non può essere analizzato dall'espressione regolare ..."

Questo è vero per le espressioni regolari classiche. Alcune implementazioni moderne consentono la ricorsione, che lo rende un linguaggio senza contesto, sebbene sia in qualche modo dettagliato per questo compito.

Vedo dove ti stai abbinando []()/\. e altri personaggi speciali regex. Dove stai permettendo i personaggi non speciali? Sembra che questo corrisponderà ^(?:[\.]+)$, ma non ^abcdefg$. Questa è una regex valida.

[^?+*{}()[\]\\|]corrisponderà a qualsiasi singolo personaggio, non parte di nessuno degli altri costrutti. Questo include sia letterale ( a- z), e alcuni caratteri speciali ( ^, $, .).


10
Questa risposta manda le persone nella direzione completamente sbagliata. Non dovrebbero mai usare regEx per individuare espressioni regolari, perché non possono funzionare correttamente in tutti i casi. Vedi la mia risposta aggiunta.
vitaly-t

1
.{,1}non ha eguali. Passa alle ^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d*(?:,\d*)?\})[?+]?)?|\|)*)$partite. Cambiare \d+a\d*
yunzen

4
regex per definizione non dovrebbe avere ricorsione, almeno dire qualcosa del genere nella tua risposta, il tuo motore regex è probabilmente "troppo potente" e non proprio un motore regex.
Charlie Parker,

Solo una nota che hai dimenticato la bandiera x
RedClover

Questo validatore sembra essere creato per espressioni PCRE, ma passerà molti ERE POSIX non validi. In particolare, sono un po 'più esigente in diverse categorie di classe di caratteri, ad esempio questo è valido in PCRE ma non in ERE: [a-b-c].
Pedro Gimeno,

321

Improbabile.

Valutalo in una try..catcho qualunque altra lingua.


228

No, se parli rigorosamente di espressioni regolari e non includi alcune implementazioni di espressioni regolari che sono in realtà grammatiche libere dal contesto.

C'è una limitazione delle espressioni regolari che rende impossibile scrivere una regex che corrisponda a tutte e solo le regex. Non è possibile abbinare implementazioni come parentesi graffe che sono accoppiate. I regex usano molti di questi costrutti, prendiamo []come esempio. Ogni volta che c'è un, [ci deve essere una corrispondenza ], che è abbastanza semplice per una regex"\[.*\]" .

Ciò che rende impossibile la regex è che possono essere nidificati. Come puoi scrivere una regex che corrisponde alle parentesi nidificate? La risposta è che non puoi fare a meno di una regex infinitamente lunga. È possibile abbinare qualsiasi numero di parentesi nidificate tramite la forza bruta, ma non è mai possibile abbinare un set arbitrariamente lungo di parentesi nidificate.

Questa funzionalità viene spesso definita come conteggio, poiché si sta contando la profondità della nidificazione. Una regex per definizione non ha la capacità di contare.


Ho finito per scrivere " Limitazioni di espressioni regolari " su questo.


53

Buona domanda.

Le vere lingue regolari non possono decidere tra parentesi ben formate nidificate in modo arbitrario. Se il tuo alfabeto contiene '('e')' l'obiettivo è decidere se una stringa di questi ha parentesi corrispondenti ben formate. Poiché questo è un requisito necessario per le espressioni regolari, la risposta è no.

Tuttavia, se allenti il ​​requisito e aggiungi ricorsione, probabilmente puoi farlo. Il motivo è che la ricorsione può agire come uno stack che consente di "contare" l'attuale profondità di annidamento spingendo su questo stack.

Russ Cox ha scritto " La corrispondenza delle espressioni regolari può essere semplice e veloce ", che è un meraviglioso trattato sull'implementazione del motore regex.


16

No, se usi espressioni regolari standard.

Il motivo è che non puoi soddisfare il lemma del pompaggio per le lingue normali. Gli stati di pompaggio lemma che una stringa appartenente al linguaggio "L" è regolare se esiste un numero "N" in modo tale che, dopo aver diviso la stringa in tre sottostringhe x, y, z, in modo tale che |x|>=1 && |xy|<=N, si può ripetere ytutte le volte che si desidera e la l'intera stringa apparterrà comunque a L.

Una conseguenza del lemma di pompaggio è che non è possibile avere stringhe regolari nella forma a^Nb^Mc^N, ovvero due sottostringhe aventi la stessa lunghezza separate da un'altra stringa. In qualsiasi modo si dividere tali stringhe in x, ye z, non si può "pompa" y, senza ottenere una stringa con un diverso numero di "a" e "c", lasciando così la lingua originale. È il caso, ad esempio, delle parentesi nelle espressioni regolari.


5
Questa non è una descrizione molto precisa del lemma del pompaggio. Innanzitutto, è l'intera lingua che può essere regolare o meno, non una singola stringa. In secondo luogo, è una condizione necessaria, non sufficiente, per la regolarità. Infine, è possibile pompare solo stringhe sufficientemente lunghe.
Darij Grinberg,

13

Sebbene sia perfettamente possibile usare una regex ricorsiva come ha pubblicato MizardX, per questo tipo di cose è molto più utile un parser. Originariamente i regex erano destinati all'uso con linguaggi regolari, essere ricorsivi o avere gruppi di bilanciamento è solo una patch.

Il linguaggio che definisce le regex valide è in realtà una grammatica libera dal contesto e dovresti usare un parser appropriato per gestirlo. Ecco un esempio di un progetto universitario per l'analisi di regex semplici (senza la maggior parte dei costrutti). Utilizza JavaCC. E sì, i commenti sono in spagnolo, anche se i nomi dei metodi sono piuttosto autoesplicativi.

SKIP :
{
    " "
|   "\r"
|   "\t"
|   "\n"
}
TOKEN : 
{
    < DIGITO: ["0" - "9"] >
|   < MAYUSCULA: ["A" - "Z"] >
|   < MINUSCULA: ["a" - "z"] >
|   < LAMBDA: "LAMBDA" >
|   < VACIO: "VACIO" >
}

IRegularExpression Expression() :
{
    IRegularExpression r; 
}
{
    r=Alternation() { return r; }
}

// Matchea disyunciones: ER | ER
IRegularExpression Alternation() :
{
    IRegularExpression r1 = null, r2 = null; 
}
{
    r1=Concatenation() ( "|" r2=Alternation() )?
    { 
        if (r2 == null) {
            return r1;
        } else {
            return createAlternation(r1,r2);
        } 
    }
}

// Matchea concatenaciones: ER.ER
IRegularExpression Concatenation() :
{
    IRegularExpression r1 = null, r2 = null; 
}
{
    r1=Repetition() ( "." r2=Repetition() { r1 = createConcatenation(r1,r2); } )*
    { return r1; }
}

// Matchea repeticiones: ER*
IRegularExpression Repetition() :
{
    IRegularExpression r; 
}
{
    r=Atom() ( "*" { r = createRepetition(r); } )*
    { return r; }
}

// Matchea regex atomicas: (ER), Terminal, Vacio, Lambda
IRegularExpression Atom() :
{
    String t;
    IRegularExpression r;
}
{
    ( "(" r=Expression() ")" {return r;}) 
    | t=Terminal() { return createTerminal(t); }
    | <LAMBDA> { return createLambda(); }
    | <VACIO> { return createEmpty(); }
}

// Matchea un terminal (digito o minuscula) y devuelve su valor
String Terminal() :
{
    Token t;
}
{
    ( t=<DIGITO> | t=<MINUSCULA> ) { return t.image; }
}

11

È possibile inviare la regex a preg_matchcui restituirà false se la regex non è valida. Non dimenticare di usare il @per sopprimere i messaggi di errore:

@preg_match($regexToTest, '');
  • Restituirà 1 se il regex è // .
  • Restituirà 0 se il regex è a posto.
  • Restituirà falso altrimenti.

6

Il seguente esempio di Paul McGuire, originariamente dal wiki pyparsing, ma ora disponibile solo tramite la Wayback Machine , fornisce una grammatica per analizzare alcune regex, allo scopo di restituire il set di stringhe corrispondenti. Come tale, rifiuta quelle ri che includono termini di ripetizione illimitati, come '+' e '*'. Ma dovrebbe darti un'idea su come strutturare un parser che elabori re.

# 
# invRegex.py
#
# Copyright 2008, Paul McGuire
#
# pyparsing script to expand a regular expression into all possible matching strings
# Supports:
# - {n} and {m,n} repetition, but not unbounded + or * repetition
# - ? optional elements
# - [] character ranges
# - () grouping
# - | alternation
#
__all__ = ["count","invert"]

from pyparsing import (Literal, oneOf, printables, ParserElement, Combine, 
    SkipTo, operatorPrecedence, ParseFatalException, Word, nums, opAssoc,
    Suppress, ParseResults, srange)

class CharacterRangeEmitter(object):
    def __init__(self,chars):
        # remove duplicate chars in character range, but preserve original order
        seen = set()
        self.charset = "".join( seen.add(c) or c for c in chars if c not in seen )
    def __str__(self):
        return '['+self.charset+']'
    def __repr__(self):
        return '['+self.charset+']'
    def makeGenerator(self):
        def genChars():
            for s in self.charset:
                yield s
        return genChars

class OptionalEmitter(object):
    def __init__(self,expr):
        self.expr = expr
    def makeGenerator(self):
        def optionalGen():
            yield ""
            for s in self.expr.makeGenerator()():
                yield s
        return optionalGen

class DotEmitter(object):
    def makeGenerator(self):
        def dotGen():
            for c in printables:
                yield c
        return dotGen

class GroupEmitter(object):
    def __init__(self,exprs):
        self.exprs = ParseResults(exprs)
    def makeGenerator(self):
        def groupGen():
            def recurseList(elist):
                if len(elist)==1:
                    for s in elist[0].makeGenerator()():
                        yield s
                else:
                    for s in elist[0].makeGenerator()():
                        for s2 in recurseList(elist[1:]):
                            yield s + s2
            if self.exprs:
                for s in recurseList(self.exprs):
                    yield s
        return groupGen

class AlternativeEmitter(object):
    def __init__(self,exprs):
        self.exprs = exprs
    def makeGenerator(self):
        def altGen():
            for e in self.exprs:
                for s in e.makeGenerator()():
                    yield s
        return altGen

class LiteralEmitter(object):
    def __init__(self,lit):
        self.lit = lit
    def __str__(self):
        return "Lit:"+self.lit
    def __repr__(self):
        return "Lit:"+self.lit
    def makeGenerator(self):
        def litGen():
            yield self.lit
        return litGen

def handleRange(toks):
    return CharacterRangeEmitter(srange(toks[0]))

def handleRepetition(toks):
    toks=toks[0]
    if toks[1] in "*+":
        raise ParseFatalException("",0,"unbounded repetition operators not supported")
    if toks[1] == "?":
        return OptionalEmitter(toks[0])
    if "count" in toks:
        return GroupEmitter([toks[0]] * int(toks.count))
    if "minCount" in toks:
        mincount = int(toks.minCount)
        maxcount = int(toks.maxCount)
        optcount = maxcount - mincount
        if optcount:
            opt = OptionalEmitter(toks[0])
            for i in range(1,optcount):
                opt = OptionalEmitter(GroupEmitter([toks[0],opt]))
            return GroupEmitter([toks[0]] * mincount + [opt])
        else:
            return [toks[0]] * mincount

def handleLiteral(toks):
    lit = ""
    for t in toks:
        if t[0] == "\\":
            if t[1] == "t":
                lit += '\t'
            else:
                lit += t[1]
        else:
            lit += t
    return LiteralEmitter(lit)    

def handleMacro(toks):
    macroChar = toks[0][1]
    if macroChar == "d":
        return CharacterRangeEmitter("0123456789")
    elif macroChar == "w":
        return CharacterRangeEmitter(srange("[A-Za-z0-9_]"))
    elif macroChar == "s":
        return LiteralEmitter(" ")
    else:
        raise ParseFatalException("",0,"unsupported macro character (" + macroChar + ")")

def handleSequence(toks):
    return GroupEmitter(toks[0])

def handleDot():
    return CharacterRangeEmitter(printables)

def handleAlternative(toks):
    return AlternativeEmitter(toks[0])


_parser = None
def parser():
    global _parser
    if _parser is None:
        ParserElement.setDefaultWhitespaceChars("")
        lbrack,rbrack,lbrace,rbrace,lparen,rparen = map(Literal,"[]{}()")

        reMacro = Combine("\\" + oneOf(list("dws")))
        escapedChar = ~reMacro + Combine("\\" + oneOf(list(printables)))
        reLiteralChar = "".join(c for c in printables if c not in r"\[]{}().*?+|") + " \t"

        reRange = Combine(lbrack + SkipTo(rbrack,ignore=escapedChar) + rbrack)
        reLiteral = ( escapedChar | oneOf(list(reLiteralChar)) )
        reDot = Literal(".")
        repetition = (
            ( lbrace + Word(nums).setResultsName("count") + rbrace ) |
            ( lbrace + Word(nums).setResultsName("minCount")+","+ Word(nums).setResultsName("maxCount") + rbrace ) |
            oneOf(list("*+?")) 
            )

        reRange.setParseAction(handleRange)
        reLiteral.setParseAction(handleLiteral)
        reMacro.setParseAction(handleMacro)
        reDot.setParseAction(handleDot)

        reTerm = ( reLiteral | reRange | reMacro | reDot )
        reExpr = operatorPrecedence( reTerm,
            [
            (repetition, 1, opAssoc.LEFT, handleRepetition),
            (None, 2, opAssoc.LEFT, handleSequence),
            (Suppress('|'), 2, opAssoc.LEFT, handleAlternative),
            ]
            )
        _parser = reExpr

    return _parser

def count(gen):
    """Simple function to count the number of elements returned by a generator."""
    i = 0
    for s in gen:
        i += 1
    return i

def invert(regex):
    """Call this routine as a generator to return all the strings that
       match the input regular expression.
           for s in invert("[A-Z]{3}\d{3}"):
               print s
    """
    invReGenerator = GroupEmitter(parser().parseString(regex)).makeGenerator()
    return invReGenerator()

def main():
    tests = r"""
    [A-EA]
    [A-D]*
    [A-D]{3}
    X[A-C]{3}Y
    X[A-C]{3}\(
    X\d
    foobar\d\d
    foobar{2}
    foobar{2,9}
    fooba[rz]{2}
    (foobar){2}
    ([01]\d)|(2[0-5])
    ([01]\d\d)|(2[0-4]\d)|(25[0-5])
    [A-C]{1,2}
    [A-C]{0,3}
    [A-C]\s[A-C]\s[A-C]
    [A-C]\s?[A-C][A-C]
    [A-C]\s([A-C][A-C])
    [A-C]\s([A-C][A-C])?
    [A-C]{2}\d{2}
    @|TH[12]
    @(@|TH[12])?
    @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9]))?
    @(@|TH[12]|AL[12]|SP[123]|TB(1[0-9]?|20?|[3-9])|OH(1[0-9]?|2[0-9]?|30?|[4-9]))?
    (([ECMP]|HA|AK)[SD]|HS)T
    [A-CV]{2}
    A[cglmrstu]|B[aehikr]?|C[adeflmorsu]?|D[bsy]|E[rsu]|F[emr]?|G[ade]|H[efgos]?|I[nr]?|Kr?|L[airu]|M[dgnot]|N[abdeiop]?|Os?|P[abdmortu]?|R[abefghnu]|S[bcegimnr]?|T[abcehilm]|Uu[bhopqst]|U|V|W|Xe|Yb?|Z[nr]
    (a|b)|(x|y)
    (a|b) (x|y)
    """.split('\n')

    for t in tests:
        t = t.strip()
        if not t: continue
        print '-'*50
        print t
        try:
            print count(invert(t))
            for s in invert(t):
                print s
        except ParseFatalException,pfe:
            print pfe.msg
            print
            continue
        print

if __name__ == "__main__":
    main()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.