Geçerli bir normal ifadeyi algılamak için düzenli bir ifade var mı?


1006

Başka bir normal ifade ile geçerli bir normal ifade tespit etmek mümkün müdür? Öyleyse lütfen aşağıdaki örnek kodu verin.


58
Yani probleminiz bir normal ifadeyi doğrulamak, çözmek için bir normal ifadeyi seçtiniz. Regex'lerin problem sayısını artıran özelliğinin katkı mı yoksa çarpma mı olduğunu merak ediyorum. 2 yerine 4 problem gibi geliyor :)
abesto

15
Düzenli ifadeler için birçok gösterim vardır - bazı özellikler ve yazımları çoğu için ortaktır, bazıları farklı yazılır veya yalnızca belirli bir gösterimde bulunur. Bu notasyonların çoğu normal dilbilgisi anlamında "düzenli" değildir - birçok modern "düzenli ifade" gösteriminin orijinal biçimsel tanımlamanın ötesine geçen uzantıları olmasına rağmen, alt ifadelerin sınırsız yuvalamasını işlemek için bağlamsız bir ayrıştırıcıya ihtiyacınız olacaktır. kendi gösterimlerinin tanınmasına izin verebilir. Her durumda, neden her regex'in geçerli olup olmadığını regex kütüphanenize sormuyorsunuz?
Steve314

1
@bevacqua XML şemasında regexp doğrulamak gerekir. Başka bir normal ifade olmadan nasıl yapabilirim?
zenden2k

3
Aslında, kontrol edilecek regex'i (desen), dilinizin sahip olduğu bir istisna işleme mekanizması altında derleyin / çalıştırın. Böylece dilin regex motoru / derleyicisi onu kontrol edecektir. (Bu, programın çalışabilmesi için temel sözdiziminin doğru olduğunu varsayar, ancak bu, regex dizesini (muhtemelen sözdizimsel olarak yanlış) kod olarak değerlendirmek için dillerinizin olanaklarını kullanarak
denetime dahil edilebilir

Bu, python kullanıcıları için mükemmel bir cevaptır: stackoverflow.com/questions/19630994/…
gianni

Yanıtlar:


978
/
^                                             # 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
/

Bu özyinelemeli bir regex'tir ve birçok regex motoru tarafından desteklenmez. PCRE tabanlı olanlar bunu desteklemelidir.

Boşluk ve yorum olmadan:

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

.NET doğrudan özyinelemeyi desteklemez. (Ve (?1)ve (?R)yapılar.) Özyineleme, sayılan dengeli gruplara dönüştürülmelidir:

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

sıkıştırılmış:

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

Yorumlardan:

Bu ikame ve çevirileri doğrulayacak mı?

İkamelerin ve çevirilerin regex kısmını doğrular. s/<this part>/.../

Teorik olarak tüm geçerli normal ifade gramerlerini bir normal ifade ile eşleştirmek mümkün değildir.

Normal ifade motorunun PCRE gibi özyinelemeyi desteklemesi mümkündür, ancak bu artık normal ifadeler olarak adlandırılamaz.

Gerçekten de, "özyinelemeli düzenli ifade" normal bir ifade değildir. Ama bu normal ifade motorlarına sıkça kabul edilen bir uzantı ... İronik olarak, bu genişletilmiş normal ifade genişletilmiş normal ifadelerle eşleşmiyor.

"Teorik olarak, teori ve pratik aynıdır. Pratikte öyle değiller." Düzenli ifadeleri bilen neredeyse herkes, düzenli ifadelerin özyinelemeyi desteklemediğini bilir. Ancak PCRE ve diğer birçok uygulama, temel düzenli ifadelerden çok daha fazlasını destekler.

grep komutunda kabuk betiği ile bu, bana bazı hata gösterir .. grep: {} geçersiz içeriği. Düzenli ifadeler içeren tüm dosyaları bulmak için bir kod tabanı grep olabilir bir komut dosyası yapıyorum

Bu model, yinelemeli düzenli ifadeler adı verilen bir uzantıdan yararlanır. Bu normal ifadenin POSIX aroması tarafından desteklenmemektedir. PCRE regex lezzetini etkinleştirmek için -P anahtarıyla deneyebilirsiniz.

Regex'in kendisi "normal bir dil değildir ve bu nedenle düzenli ifadeyle ayrıştırılamaz ..."

Bu klasik düzenli ifadeler için geçerlidir. Bazı modern uygulamalar, bu görev için biraz ayrıntılı olmasına rağmen, onu Bağlamdan Bağımsız bir dile dönüştüren özyinelemeye izin verir.

Nerede eşleştiğini görüyorum []()/\. ve diğer özel normal ifade karakterleri. Özel olmayan karakterlere nerede izin veriyorsunuz? Bu eşleşecek gibi görünüyor ^(?:[\.]+)$, ama değil ^abcdefg$. Bu geçerli bir normal ifade.

[^?+*{}()[\]\\|]diğer yapıların hiçbiriyle değil, tek bir karakterle eşleşir. (Bu, hem literal içerir a- z) ve bazı özel karakterleri ( ^, $, .).


10
Bu cevap insanları tamamen yanlış yöne gönderir. Normal ifadeleri bulmak için asla regEx kullanmamalıdırlar, çünkü her durumda doğru çalışamaz. Cevabımı eklendi bakın.
vitaly-t

1
.{,1}eşleşmedi. Maçlarla değiştirin ^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d*(?:,\d*)?\})[?+]?)?|\|)*)$. Change \d+to\d*
yunzen

4
def tarafından regex özyineleme olmamalıdır, en azından ur cevap böyle bir şey söylemek, ur regex motoru muhtemelen "çok güçlü" ve gerçekten bir regex motoru değil.
Charlie Parker

X bayrağını unuttuğunuz bir not
RedClover

Bu doğrulayıcı PCRE ifadeleri için yapılmış gibi görünüyor, ancak birçok geçersiz POSIX ERE'yi geçecek. Özellikle, bu ERE içinde PCRE'nin ancak geçerli değil örneğin, karakter sınıfı aralıklarında biraz pickier: [a-b-c].
Pedro Gimeno

321

Olası olmayan.

try..catchDilinizi ne olursa olsun ya da dilediğiniz şekilde değerlendirin .


228

Hayır, kesinlikle düzenli ifadelerden bahsediyor ve aslında bağlamsız gramer olan bazı düzenli ifade uygulamaları dahil etmiyorsanız.

Düzenli ifadelerin bir sınırlaması vardır, bu da tüm regex'lerle eşleşen bir regex yazmayı imkansız hale getirir. Eşleştirilmiş parantez gibi uygulamaları eşleştiremezsiniz. Normal ifadeler bu tür yapıları kullanır, []örnek olarak alalım. Ne zaman bir regex için yeterince basit [bir eşleşme olmalıdır .]"\[.*\]"

Normal ifadeleri imkansız kılan şey, iç içe geçebilmeleridir. İç içe parantezlerle eşleşen bir normal ifadeyi nasıl yazabilirsiniz? Cevap, sonsuz uzun bir regex olmadan yapamazsınız. Kaba kuvvet aracılığıyla istediğiniz sayıda iç içe parantezle eşleştirebilirsiniz, ancak keyfi olarak uzun bir iç içe parantez kümesiyle hiçbir zaman eşleşemezsiniz.

Bu yeteneğe genellikle sayma denir, çünkü yuvalamanın derinliğini sayıyorsunuz. Tanıma göre bir normal ifade, sayma yeteneğine sahip değildir.


Bu konuda " Düzenli İfade Sınırlamaları " yazdım .


53

İyi soru.

Gerçek düzenli diller keyfi olarak derinden yuvalanmış iyi biçimlendirilmiş parantezlere karar veremez. Alfabeniz içeriyorsa '('ve ')'amaç, bunlardan oluşan bir dizenin uygun şekilli parantez olup olmadığına karar vermektir. Bu düzenli ifadeler için gerekli bir gereklilik olduğu için cevap hayırdır.

Ancak, gereksinimi gevşetir ve özyineleme eklerseniz, büyük olasılıkla yapabilirsiniz. Bunun nedeni, özyinelemenin, bu iç içe geçme derinliğini bu yığının üzerine iterek "saymanıza" izin veren bir yığın görevi görebilmesidir.

Russ Cox regex motor uygulaması için harika bir inceleme olan " Düzenli İfade Eşleştirme Basit ve Hızlı Olabilir " yazdı .


16

Hayır, standart düzenli ifadeler kullanırsanız.

Nedeni, pompalanan lemi düzenli diller için tatmin edememenizdir . Bir numara "N" üç alt dizeleri içine dize bölme sonra böyle, orada varsa pompalama lemma devletler "L" dile ait bir dize düzenli olduğunu x, y, z, öyle ki |x|>=1 && |xy|<=Nsen tekrarlayabilirsiniz, yistediğiniz kadar çok kez ve tüm dize hala ait olacaktır L.

Pompalama lemmasının bir sonucu, formda düzenli dizelere a^Nb^Mc^N, yani başka bir dizeyle ayrılmış aynı uzunlukta iki alt dizeye sahip olamamanızdır. Hiçbir şekilde size böyle dizeleri bölmek x, yve zsen "pompa", olamaz yböylece orijinal dili bırakarak "a" ve "c" nin farklı bir sayı ile bir dize alınmadan. Örneğin, düzenli ifadelerdeki parantezlerde durum budur.


5
Bu, pompalama lemmasının çok kesin bir tanımı değildir. Birincisi, tek bir dize değil, düzenli olabilir veya olmayabilir tüm dildir. İkincisi, düzenlilik için gerekli, yeterli değil bir koşuldur. Son olarak, sadece yeterince uzun teller pompalanabilir.
darij grinberg

13

MizardX'un yayınladığı gibi yinelemeli bir normal ifadeyi kullanmak mükemmel bir şekilde mümkün olsa da, bu tür şeyler için bir ayrıştırıcı çok daha yararlıdır. Regex'lerin başlangıçta normal dillerle kullanılması amaçlanmıştır, özyinelemeli veya dengeleme gruplarına sahip olmak sadece bir yamadır.

Geçerli normal ifadeleri tanımlayan dil aslında bağlamsız bir gramerdir ve bunu işlemek için uygun bir ayrıştırıcı kullanmalısınız. Basit regex'leri (çoğu yapı olmadan) ayrıştırmak için bir üniversite projesi için bir örnek. JavaCC kullanır. Ve evet, yorumlar İspanyolca'dır, ancak yöntem adları oldukça açıklayıcıdır.

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

Normal ifade preg_matchgeçerli değilse yanlış döndürülecek normal ifadeyi gönderebilirsiniz . @Hata mesajlarını bastırmak için the komutunu kullanmayı unutmayın :

@preg_match($regexToTest, '');
  • Normal ifade ise 1 döndürecektir //.
  • Normal ifade uygunsa 0 döndürecektir.
  • Aksi takdirde yanlış döndürür.

6

Asıl olarak pyparsing wiki'den olan ancak şimdi sadece Wayback Machine aracılığıyla kullanılabilen aşağıdaki örnek Paul McGuire, eşleşen dizeleri geri döndürmek amacıyla bazı regex'leri ayrıştırmak için bir dilbilgisi vermektedir . Bu nedenle, '+' ve '*' gibi sınırsız tekrar terimleri içerenleri reddeder. Ancak bu, işleyicileri işleyecek bir ayrıştırıcının nasıl yapılandırılacağı hakkında bir fikir vermelidir.

# 
# 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()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.