是否有正则表达式来检测有效的正则表达式?

psy*_*tek 968 regex

是否可以使用另一个正则表达式检测有效的正则表达式?如果是这样,请在下面举例说明.

Mar*_*rot 954

/
^                                             # 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
/
Run Code Online (Sandbox Code Playgroud)

这是一个递归正则表达式,许多正则表达式引擎都不支持.基于PCRE的应该支持它.

没有空格和评论:

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

.NET不直接支持递归.((?1)(?R)构造.)递归必须转换为计数平衡组:

^                                         # 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.
Run Code Online (Sandbox Code Playgroud)

压实:

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

  • 这个答案使人们完全走错了方向。他们不应该使用regEx来定位正则表达式,因为它不能在所有情况下都能正常工作。看到我的答案添加。 (6认同)
  • def的regex不应递归,至少应在您的回答中说类似的话,您的regex引擎可能“过于强大”,而不是真正的regex引擎。 (4认同)
  • 那么用于检测正则表达式检测器的正则表达式呢? (4认同)
  • `.{,1}` 是无与伦比的。改为`^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\ \]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?&lt;[=!] |\?&gt;)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\ d*(?:,\d*)?\})[?+]?)?|\|)*)$` 匹配。将 `\d+` 改为 `\d*` (3认同)
  • @PeterV.Mørch 我删除了它,因为奇怪的是,它收到了很多反对票。[在这里,我将我的答案复制到要点](https://gist.github.com/vitaly-t/05d6c8882a93e9303480d58e3cb12f77)。 (3认同)
  • @vitaly-t:你的答案在哪里? (2认同)

Dan*_*Dan 317

不太可能.

try..catch用您或您提供的语言评估它.

  • **确实*提供了问题的答案.因为问题是XY问题.当然*真正的*问题是"如何验证正则表达式". (43认同)
  • @Raedwald它_may_是,但话又说回来,它可能**不是**.XY问题的_good_答案解释了为什么X是一个坏主意而OP真的应该做Y而不是.这个非常简洁的答案没有. (5认同)
  • 具有讽刺意味的是,对于大多数人尝试使用正则表达式进行验证的事情,此技巧也适用。 (5认同)
  • 请不要回答未回答的问题。如果您怀疑AB问题,那么您仍然应该回答所提出的实际问题。您也可以说“但您可能也想问B”,但只是说“您不应该问A,让我告诉您有关B的消息”时,他们真的很想问A因此,这个答案实际上是“不太可能”。 (5认同)
  • 但是,如果从用户那里收到一个值,他就会获得广泛的空间来利用正则表达式引擎中的某些漏洞。 (4认同)
  • 不过,无论是否通过另一个正则表达式“验证”正则表达式,对此类正则表达式注入攻击都没有影响 (2认同)

Jar*_*Par 220

如果你严格地谈论正则表达式并且不包括一些实际上是无上下文语法的正则表达式实现,那就不.

正则表达式有一个限制,这使得无法编写匹配所有正则表达式的正则表达式.您无法匹配配对的大括号等实现.正则表达式使用许多这样的结构,让我们以[]为例.只要有[必须有匹配].足够简单的正则表达式"[.*]".

正则表达式无法实现的原因是它们可以嵌套.你怎么写一个匹配嵌套括号的正则表达式?答案是你不能没有无限长的正则表达式.你可以通过暴力匹配任意数量的嵌套parens,但你不能匹配任意长的嵌套括号.

此功能通常称为计数(您计算嵌套的深度).根据定义,正则表达式无法计数.

编辑:结束撰写关于此的博客文章:正则表达式限制

  • 我经常要区分名为regex的常用文本匹配工具和它所基于的常规表达式.可悲的是,许多人没有看到这种区别.RE2的独特之处在于它只允许可以转换回普通RE的扩展.它还具有RE(有界内存,运行时,速度)的所有优点,并具有大多数语法扩展. (4认同)
  • @ labela--gotoa这是“实际上是无上下文语法的正则表达式实现”中的一个示例(如您所使用的那样,递归很昂贵,并且在正则表达式中是不允许的) (2认同)

I G*_*ERS 52

好问题.真正的常规语言无法决定任意深度嵌套的良好形式的括号.即,如果您的字母包含'('和')',那么目标是确定这些字符串是否具有格式良好的匹配括号.由于这是正则表达式的必要条件,因此答案是否定的.

但是:如果放宽要求并添加递归,则可以执行此操作.原因是递归可以充当"堆栈",让您通过推入此堆栈来"计算"当前的嵌套深度.

Russ Cox撰写了一篇关于正则表达式引擎实现的精彩论文:正则表达式匹配可以简单快速


小智 16

不,如果您使用标准正则表达式。

原因是您无法满足常规语言的抽奖引理。抽运引理指出,如果存在数字N,则属于语言L的字符串是规则的,这样,在将字符串分为3个子字符串xyz之后,使得| x |> = 1 && | xy | <= N,您可以重复y您需要多次,并且整个字符串仍属于L。

抽运引理的结果是您不能使用形式a^Nb^Mc^N为常规的字符串,即两个长度相同的子字符串被另一个字符串分隔。以任何方式在xy和z中拆分此类字符串,如果不获取带有不同数量“ a”和“ c”的字符串,就无法“抽取” y,从而保留原始语言。例如,正则表达式中的括号就是这种情况。

  • 这不是抽水引理的非常精确的描述。首先,是整个语言可以是正规的,也可以不是正规的,而不是单个字符串。其次,这是规则性的必要条件,而不是充分条件。最后,只能抽出足够长的弦。 (4认同)

San*_*ino 13

虽然完全有可能像MizardX发布的那样使用递归正则表达式,但对于这类事情来说,解析器更有用.Regexes最初打算用于常规语言,递归或平衡组只是一个补丁.

定义有效正则表达式的语言实际上是一个无上下文语法,您应该使用适当的解析器来处理它.这是一个用于解析简单正则表达式的大学项目的示例(没有大多数构造).它使用JavaCC.是的,评论是西班牙语,虽然方法名称是不言自明的.

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; }
}
Run Code Online (Sandbox Code Playgroud)

  • 我同意在相同的代码中使用西班牙语,英语和Spanglish并不是一种快乐的做法.问题是我习惯用英语编码,但在项目中有一些指导原则(例如用西班牙语评论,或者使用某些名称作为代币).无论如何,关键在于给出一个关于算法的想法,而不是给出一些完整的参考代码. (24认同)
  • 更好一点,我同意你应该坚持使用一种语言.而且,没有听到亲英语或"你的语言很糟糕",Linus Torvalds至少已经提出了一个标准. (6认同)
  • 我不完全同意"matchea"真的是西班牙语... :-) (4认同)
  • 任何对此感兴趣的非西班牙人。matchea 的意思是“匹配”,vacio 的意思是“空”,digito 的意思是“数字”,miniscula 的意思是“小写”。Matchea disyunciones = 匹配析取。Matchea concatenaciones = 匹配串联。Matchea repetitiones = 匹配重复。Matchea 正则表达式atomas = 匹配原子正则表达式。Matchea un Terminal (digito o minuscula) y devuelve su valor = 匹配终端(数字或小写字母)并返回其值。 (2认同)

Ric*_*ted 9

你可以将正则表达式提交给preg_match,如果正则表达式无效,它将返回false.不要忘记使用'@'来禁止错误消息:

@preg_match($regexToTest, '');
Run Code Online (Sandbox Code Playgroud)
  • 如果正则表达式为"//",则返回1.
  • 如果正则表达式没问题,将返回0.
  • 否则将返回false.


Pau*_*McG 6

Paul McGuire的以下示例最初来自pyparsing Wiki,但现在仅可通过Wayback Machine获得,它给出了解析某些正则表达式的语法,目的是返回匹配字符串集。因此,它拒绝那些包含无限制重复项的re,例如“ +”和“ *”。但这应该给您一个关于如何构造将处理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()
Run Code Online (Sandbox Code Playgroud)