Có một biểu thức chính quy để phát hiện một biểu thức chính quy hợp lệ không?


1007

Có thể phát hiện một biểu thức chính quy hợp lệ với một biểu thức chính quy khác không? Nếu vậy xin vui lòng cho mã ví dụ dưới đây.


58
Vì vậy, vấn đề của bạn là xác nhận một regex, bạn đã chọn một regex để giải quyết nó. Tôi tự hỏi nếu thuộc tính tăng số vấn đề của regexes là phụ gia hay nhân. Cảm giác như 4 vấn đề thay vì 2 :)
abesto

15
Có nhiều ký hiệu cho các biểu thức thông thường - một số tính năng và cách viết của chúng là phổ biến đối với hầu hết, một số được đánh vần khác nhau hoặc chỉ có sẵn trong một ký hiệu cụ thể. Hầu hết các ký hiệu đó không "thông thường" theo nghĩa ngữ pháp thông thường - bạn cần một trình phân tích cú pháp miễn phí ngữ cảnh để xử lý việc lồng ghép không bị ràng buộc - mặc dù nhiều ký hiệu "biểu thức chính quy" hiện đại có các phần mở rộng vượt ra ngoài định nghĩa chính thức ban đầu và có thể cho phép các ký hiệu riêng của họ được công nhận. Trong mọi trường hợp, tại sao không chỉ cần hỏi thư viện regex của bạn nếu mỗi regex là hợp lệ?
Steve314

1
@bevacqua tôi cần xác thực regrec trong lược đồ XML. Làm thế nào tôi có thể làm điều đó mà không cần regrec khác?
zenden2k

3
Trên thực tế biên dịch / chạy regex (mẫu) cần kiểm tra, theo cơ chế xử lý ngoại lệ mà ngôn ngữ của bạn có. Vì vậy, chính trình biên dịch / trình biên dịch regex của ngôn ngữ sẽ kiểm tra nó. (Điều này giả sử cú pháp cơ bản chính xác để chương trình chạy, nhưng có thể được bao gồm trong kiểm tra bằng cách sử dụng các phương tiện ngôn ngữ của bạn để đánh giá chuỗi cho regex dưới dạng mã (có thể sai về mặt cú pháp), hoặc như vậy.)
zdim

Đây là câu trả lời hoàn hảo cho người dùng python: stackoverflow.com/questions/19630994/NH
gianni

Câu trả lời:


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
/

Đây là một regex đệ quy và không được hỗ trợ bởi nhiều công cụ regex. Những người dựa trên PCRE nên hỗ trợ nó.

Không có khoảng trắng và bình luận:

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

.NET không hỗ trợ đệ quy trực tiếp. (The (?1)and (?R)constructs.) Đệ quy sẽ phải được chuyển đổi thành các nhóm cân bằng:

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

Nén:

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

Từ các ý kiến:

Điều này sẽ xác nhận thay thế và bản dịch?

Nó sẽ xác nhận chỉ là phần regex của sự thay thế và bản dịch. s/<this part>/.../

Về mặt lý thuyết không thể phù hợp với tất cả các ngữ pháp regex hợp lệ với regex.

Có thể nếu công cụ regex hỗ trợ đệ quy, chẳng hạn như PCRE, nhưng điều đó thực sự không thể được gọi là biểu thức chính quy nữa.

Thật vậy, một "biểu thức chính quy đệ quy" không phải là biểu thức chính quy. Nhưng đây là một phần mở rộng thường được chấp nhận cho các công cụ regex ... Trớ trêu thay, regex mở rộng này không phù hợp với các biểu thức mở rộng.

"Về lý thuyết, lý thuyết và thực hành là như nhau. Trong thực tế, chúng không như vậy." Hầu hết mọi người biết biểu thức chính quy đều biết rằng biểu thức chính quy không hỗ trợ đệ quy. Nhưng PCRE và hầu hết các triển khai khác hỗ trợ nhiều hơn các biểu thức thông thường cơ bản.

sử dụng điều này với shell script trong lệnh grep, nó cho tôi thấy một số lỗi .. grep: Nội dung không hợp lệ của {}. Tôi đang tạo một tập lệnh có thể grep một cơ sở mã để tìm tất cả các tệp có chứa các biểu thức thông thường

Mẫu này khai thác một phần mở rộng được gọi là biểu thức chính quy đệ quy. Điều này không được hỗ trợ bởi hương vị POSIX của regex. Bạn có thể thử với công tắc -P, để bật hương vị regex PCRE.

Bản thân Regex "không phải là ngôn ngữ thông thường và do đó không thể được phân tích cú pháp bằng biểu thức chính quy ..."

Điều này đúng với các biểu thức chính quy cổ điển. Một số triển khai hiện đại cho phép đệ quy, làm cho nó thành ngôn ngữ Ngữ cảnh miễn phí, mặc dù nó hơi dài dòng cho nhiệm vụ này.

Tôi thấy nơi bạn phù hợp []()/\. và các nhân vật regex đặc biệt khác. Bạn đang cho phép các nhân vật không đặc biệt ở đâu? Có vẻ như điều này sẽ phù hợp ^(?:[\.]+)$, nhưng không ^abcdefg$. Đó là một regex hợp lệ.

[^?+*{}()[\]\\|]sẽ khớp với bất kỳ ký tự đơn nào, không phải là một phần của bất kỳ cấu trúc nào khác. Điều này bao gồm cả nghĩa đen ( a- z), và một số ký tự đặc biệt ( ^, $, .).


10
Câu trả lời này gửi mọi người theo hướng hoàn toàn sai. Họ không bao giờ nên sử dụng regEx để định vị các biểu thức chính quy, vì nó không thể hoạt động chính xác trong mọi trường hợp. Xem câu trả lời của tôi thêm vào.
Vitaly-t

1
.{,1}là vô song. Thay đổi để ^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d*(?:,\d*)?\})[?+]?)?|\|)*)$phù hợp. Thay đổi \d+để\d*
yunzen

4
regex by def không nên có đệ quy, ít nhất là nói như thế trong câu trả lời của bạn, công cụ regex của bạn có lẽ là "quá mạnh" và không thực sự là một công cụ regex.
Charlie Parker

Chỉ cần một lưu ý là bạn đã quên cờ x
RedClover

Trình xác nhận này dường như được tạo cho các biểu thức PCRE, nhưng nó sẽ vượt qua nhiều ERE POSIX không hợp lệ. Đáng chú ý, chúng là một chút kén chọn hơn trong phạm vi lớp nhân vật, ví dụ: điều này hợp lệ trong PCRE nhưng không phải trong ERE : [a-b-c].
Pedro Gimeno

321

Không có khả năng.

Đánh giá nó trong một try..catchhoặc bất cứ điều gì ngôn ngữ của bạn cung cấp.


228

Không, nếu bạn nghiêm túc nói về các biểu thức chính quy và không bao gồm một số triển khai biểu thức chính quy thực sự là ngữ pháp tự do ngữ cảnh.

Có một hạn chế của các biểu thức thông thường khiến cho không thể viết một biểu thức chính phù hợp với tất cả và chỉ các biểu thức chính. Bạn không thể kết hợp các triển khai như niềng răng được ghép nối. Regexes sử dụng nhiều cấu trúc như vậy, hãy lấy []một ví dụ. Bất cứ khi nào có một [phải có một kết hợp ], đủ đơn giản cho một regex "\[.*\]".

Điều khiến cho regexes không thể là chúng có thể được lồng vào nhau. Làm thế nào bạn có thể viết một regex phù hợp với dấu ngoặc lồng? Câu trả lời là bạn không thể không có regex dài vô tận. Bạn có thể khớp bất kỳ số ngoặc đơn lồng nhau nào thông qua lực lượng vũ phu nhưng bạn không bao giờ có thể khớp với một bộ dấu ngoặc lồng dài tùy ý.

Khả năng này thường được gọi là đếm, bởi vì bạn đang đếm độ sâu của lồng. Một regex theo định nghĩa không có khả năng đếm.


Cuối cùng tôi đã viết " Hạn chế biểu hiện thường xuyên " về điều này.


53

Câu hỏi hay.

Các ngôn ngữ thông thường thực sự không thể quyết định dấu ngoặc đơn được tổ chức tốt. Nếu bảng chữ cái của bạn chứa '('')'mục tiêu là quyết định xem một chuỗi trong số này có dấu ngoặc đơn phù hợp được hình thành tốt hay không. Vì đây là một yêu cầu cần thiết cho các biểu thức chính quy, câu trả lời là không.

Tuy nhiên, nếu bạn nới lỏng yêu cầu và thêm đệ quy, bạn có thể làm điều đó. Lý do là đệ quy có thể hoạt động như một ngăn xếp cho phép bạn "đếm" độ sâu làm tổ hiện tại bằng cách đẩy lên ngăn xếp này.

Russ Cox đã viết " Kết hợp biểu thức chính quy có thể đơn giản và nhanh chóng ", đây là một chuyên luận tuyệt vời về triển khai động cơ regex.


16

Không, nếu bạn sử dụng các biểu thức chính quy tiêu chuẩn.

Lý do là bạn không thể đáp ứng bổ đề bơm cho các ngôn ngữ thông thường. Các trạng thái bơm Bổ đề mà một chuỗi thuộc ngôn ngữ "L" là bình thường nếu có tồn tại một số "N" như vậy mà, sau khi chia chuỗi thành ba chuỗi con x, y, z, như vậy |x|>=1 && |xy|<=N, bạn có thể lặp lại ynhiều lần như bạn muốn và toàn bộ chuỗi sẽ vẫn thuộc về L.

Một hậu quả của bổ đề bơm là bạn không thể có các chuỗi thông thường ở dạng a^Nb^Mc^N, nghĩa là hai chuỗi con có cùng độ dài cách nhau bởi một chuỗi khác. Trong bất kỳ cách nào bạn chia chuỗi như vậy trong x, yz, bạn không thể "bơm" ymà không có một chuỗi với một số khác nhau của "a" và "c", do đó để lại ngôn ngữ gốc. Đó là trường hợp, ví dụ, với dấu ngoặc đơn trong các biểu thức thông thường.


5
Đó không phải là một mô tả rất chính xác về bổ đề bơm. Đầu tiên, đó là toàn bộ ngôn ngữ có thể là chính quy hoặc không, không phải là một chuỗi. Thứ hai, nó là một điều kiện cần, không đủ, cho sự đều đặn. Cuối cùng, chỉ các chuỗi đủ dài là có thể bơm được.
darij grinberg

13

Mặc dù hoàn toàn có thể sử dụng một biểu thức đệ quy như MizardX đã đăng, nhưng đối với những thứ này, trình phân tích cú pháp sẽ hữu ích hơn nhiều. Regexes ban đầu được dự định sẽ được sử dụng với các ngôn ngữ thông thường, được đệ quy hoặc có các nhóm cân bằng chỉ là một bản vá.

Ngôn ngữ xác định regexes hợp lệ thực sự là một ngữ pháp không ngữ cảnh và bạn nên sử dụng một trình phân tích cú pháp thích hợp để xử lý nó. Dưới đây là một ví dụ cho một dự án đại học để phân tích các biểu thức chính đơn giản (không có hầu hết các cấu trúc). Nó sử dụng JavaCC. Và vâng, các bình luận bằng tiếng Tây Ban Nha, mặc dù tên phương thức khá tự giải thích.

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

Bạn có thể gửi regex tới preg_matchđó sẽ trả về false nếu regex không hợp lệ. Đừng quên sử dụng @để chặn thông báo lỗi:

@preg_match($regexToTest, '');
  • Sẽ trả về 1 nếu regex là //.
  • Sẽ trả về 0 nếu regex ổn.
  • Sẽ trả lại sai nếu không.

6

Ví dụ sau đây của Paul McGuire, ban đầu từ wiki pyparsing, nhưng hiện chỉ có sẵn thông qua Wayback Machine , đưa ra một ngữ pháp để phân tích một số regex, với mục đích trả về tập hợp các chuỗi phù hợp. Như vậy, nó từ chối những thứ đó bao gồm các điều khoản lặp lại không giới hạn, như '+' và '*'. Nhưng nó sẽ cho bạn một ý tưởng về cách cấu trúc một trình phân tích cú pháp sẽ xử lý lại.

# 
# 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()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.