유효한 정규식을 감지하는 정규식이 있습니까?


1006

다른 정규식으로 유효한 정규식을 감지 할 수 있습니까? 그렇다면 아래에 예제 코드를 입력하십시오.


58
따라서 문제가 정규 표현식을 확인하는 중이므로 해결을 위해 정규 표현식을 선택했습니다. 정규 표현식의 문제 수가 증가하는 속성이 부가 적인지 또는 곱한 지 궁금합니다. 그것은 2 :) 대신 4 문제처럼 느낀다
abesto

15
정규 표현식에 대한 많은 표기법이 있습니다. 일부 기능과 철자는 대부분 공통적이며 일부는 다르게 표기되거나 특정 표기법으로 만 사용할 수 있습니다. 대부분의 현대적인 "정규 표현식"표기법은 원래 공식적인 정의를 뛰어 넘는 확장 기능을 가지고 있지만, 이러한 표기법의 대부분은 정규 문법 의미에서 "정규"가 아닙니다. 자신의 표기법을 인식 할 수 있습니다. 어쨌든 각 정규식이 유효한지 간단히 정규식 라이브러리에 물어보십시오.
Steve314

1
@bevacqua XML 스키마에서 정규 표현식의 유효성을 검사해야합니다. 다른 정규 표현식없이 어떻게 할 수 있습니까?
zenden2k

3
실제로 언어가 가지고있는 예외 처리 메커니즘에서 확인할 정규 표현식 (패턴)을 컴파일 / 실행하십시오. 따라서 언어의 정규식 엔진 / 컴파일러 자체에서 확인합니다. (이것은 프로그램이 실행되도록 올바른 기본 구문을 가정하지만, 언어 기능을 사용하여 정규 표현식의 문자열을 (구문 적으로 잘못되었을 수 있음) 코드 등으로 평가하여 검사에 포함시킬 수 있습니다.)
zdim

이것은 파이썬 사용자에게 완벽한 해답입니다 : stackoverflow.com/questions/19630994/…
gianni

답변:


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
/

이것은 재귀 정규식이며 많은 정규식 엔진에서 지원하지 않습니다. PCRE 기반 제품이이를 지원해야합니다.

공백과 주석이없는 경우 :

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

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

꽉 찬:

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

의견에서 :

대체 및 번역의 유효성을 검사합니까?

대체 및 번역의 정규식 부분 만 유효성을 검사합니다. s/<this part>/.../

이론적으로 모든 유효한 정규식 문법을 정규식과 일치시키는 것은 불가능합니다.

정규식 엔진이 PCRE와 같은 재귀를 지원하는 경우 가능하지만 더 이상 정규 표현식이라고 할 수는 없습니다.

실제로 "재귀 정규 표현식"은 정규 표현식이 아닙니다. 그러나 이것은 정규 표현식 엔진에 자주 사용되는 확장입니다 ... 아이러니하게도이 확장 정규 표현식은 확장 정규 표현식과 일치하지 않습니다.

"이론적으로 이론과 실제는 동일합니다. 실제로는 그렇지 않습니다." 정규 표현식을 알고있는 거의 모든 사람은 정규 표현식이 재귀를 지원하지 않는다는 것을 알고 있습니다. 그러나 PCRE 및 대부분의 다른 구현은 기본 정규식보다 훨씬 많은 기능을 지원합니다.

grep 명령에서 쉘 스크립트와 함께 이것을 사용하면 오류가 표시됩니다. grep : {}의 유효하지 않은 내용. 정규 표현식이 포함 된 모든 파일을 찾기 위해 코드베이스를 grep 할 수있는 스크립트를 만들고 있습니다

이 패턴은 재귀 정규 표현식이라는 확장을 이용합니다. 이것은 POSIX 버전의 정규식에서 지원되지 않습니다. -P 스위치를 사용하여 PCRE 정규 표현식을 사용할 수 있습니다.

정규 표현식 자체는 "정규 언어가 아니므로 정규식으로 구문 분석 할 수 없습니다 ..."

이것은 고전적인 정규식에 해당됩니다. 일부 현대 구현에서는 재귀를 허용 하므로이 작업에는 다소 장황하지만 문맥 자유 언어로 만듭니다.

일치하는 부분이 표시 []()/\됩니다. 다른 특수 정규식 문자. 비 특수 문자를 어디에서 허용합니까? 이것은 일치하는 것처럼 보이지만 ^(?:[\.]+)$그렇지 않습니다 ^abcdefg$. 유효한 정규식입니다.

[^?+*{}()[\]\\|]다른 구문의 일부가 아닌 단일 문자와 일치합니다. (이 두 문자 포함 a- z), 특정 특수 문자 ( ^, $, .).


10
이 답변은 사람들을 완전히 잘못된 방향으로 보냅니다. 정규식을 찾기 위해 regEx를 사용해서는 안됩니다. 모든 경우에 올바르게 작동하지 않기 때문입니다. 내 답변이 추가 된 것을 참조하십시오.
vitaly-t

1
.{,1}타의 추종을 불허합니다. ^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d*(?:,\d*)?\})[?+]?)?|\|)*)$일치로 변경하십시오 . 변화 \d+\d*
yunzen

4
def에 의한 정규 표현식은 재귀를 가져서는 안됩니다. 적어도 대답에 따르면, 정규식 엔진은 아마도 너무 강력하여 실제로 정규식 엔진이 아닙니다.
Charlie Parker

x 플래그를 잊어 버렸습니다
RedClover

이 유효성 검증기는 PCRE 표현 식용으로 작성된 것 같지만 많은 유효하지 않은 POSIX ERE를 전달합니다. 특히, 이들은 문자 클래스 범위에서 조금 더 까다 롭습니다 [a-b-c].
Pedro Gimeno

321

있을 것 같지 않게.

try..catch귀하가 사용하는 언어로 또는 무엇이든 평가하십시오 .


228

아니요, 정규 표현식에 대해 엄격하게 말하고 실제로 컨텍스트 프리 문법 인 일부 정규 표현식 구현을 포함하지 않는 경우.

정규식에는 한 가지 제한 사항이있어 정규식 만 일치하는 정규식을 작성할 수 없습니다. 쌍으로 된 중괄호와 같은 구현을 일치시킬 수 없습니다. 정규 표현식은 그러한 많은 구성을 사용 []합니다. 예를 들어 봅시다 . [일치 할 때마다 ]정규 표현식에 대해 충분히 간단한 일치 항목이 있어야합니다 "\[.*\]".

정규식에 불가능한 것은 중첩 될 수 있다는 것입니다. 중첩 된 대괄호와 일치하는 정규식을 어떻게 작성할 수 있습니까? 대답은 무한정 긴 정규 표현식 없이는 할 수 없다는 것입니다. 무차별 대입을 통해 여러 개의 중첩 된 괄호를 일치시킬 수 있지만 임의로 긴 중첩 된 괄호 세트는 일치시킬 수 없습니다.

이 기능은 중첩 깊이를 계산하기 때문에 계산이라고도합니다. 정의에 의한 정규 표현식에는 계산 기능이 없습니다.


이것에 대해 " 정규 표현식 제한 "을 작성 했습니다.


53

좋은 질문.

진정한 정규 언어는 임의로 깊게 중첩 된 올바른 괄호를 결정할 수 없습니다. 당신의 알파벳이 포함 '('하고 ')'목표라면 이것들의 문자열이 일치하는 괄호를 가지고 있는지 결정하는 것입니다. 이것은 정규 표현식에 필요한 요구 사항이므로 대답은 아니오입니다.

그러나 요구 사항을 풀고 재귀를 추가하면 가능합니다. 그 이유는 재귀가이 스택 위로 밀면 현재 중첩 깊이를 "계산"할 수있는 스택의 역할을 할 수 있기 때문입니다.

Russ Cox는 정규 표현식 엔진 구현에 대한 훌륭한 논문 인 " 정규 표현식 일치가 간단하고 빠를 수있다 "라고 썼습니다 .


16

아니요, 표준 정규식을 사용하는 경우

그 이유는 일반 언어에 대한 펌핑 보조 를 만족시킬 수 없기 때문입니다 . 숫자 "N"은 세 개의 하위 문자열로 문자열을 분할 한 후 같은 것이 존재하면 펌핑 보조 정리 상태 "L"언어에 속하는 문자열은 정규이다 x, y, z, 있도록 |x|>=1 && |xy|<=N당신이 반복 할 수 있습니다, y당신이 원하는만큼 여러 번과를 전체 문자열은 여전히에 속합니다 L.

펌핑 보조 정리의 결과로 a^Nb^Mc^N길이가 같은 두 개의 하위 문자열, 즉 다른 문자열로 구분 된 일반 문자열을 사용할 수 없습니다 . 어떤 식 으로든 x, y및 에서 이러한 문자열을 분리 z하면 y"a"와 "c"가 다른 문자열을 얻지 않으면 "펌프"할 수 없으므로 원래 언어를 그대로 둡니다. 예를 들어 정규 표현식에 괄호가있는 경우입니다.


5
그것은 펌핑 보조 정리에 대한 매우 정확한 설명이 아닙니다. 첫째, 단일 문자열이 아닌 규칙적이거나 그렇지 않은 전체 언어입니다. 둘째, 규칙 성을 위해서는 충분하지 않은 필수 조건입니다. 마지막으로 충분히 긴 줄만 펌핑 할 수 있습니다.
darij grinberg

13

MizardX가 게시 한 것처럼 재귀 정규 표현식을 사용하는 것이 가능하지만 이러한 종류의 파서는 훨씬 유용합니다. 정규식은 원래 정규 언어와 함께 사용되도록 만들어졌으며 재귀 적이거나 균형 그룹을 갖는 것은 단지 패치입니다.

유효한 정규 표현식을 정의하는 언어는 실제로 컨텍스트 프리 문법이므로 적절한 구문 분석기를 사용하여 처리해야합니다. 다음은 간단한 정규 표현식을 파싱하기위한 대학 프로젝트의 예입니다 (대부분의 구성없이). 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; }
}

11

preg_match정규식이 유효하지 않은 경우 false를 반환 하는 정규식을 제출할 수 있습니다 . @오류 메시지를 억제 하기 위해를 사용하는 것을 잊지 마십시오 :

@preg_match($regexToTest, '');
  • 정규식이 인 경우 1을 반환합니다 //.
  • 정규식이 정상이면 0을 반환합니다.
  • 그렇지 않으면 false를 반환합니다.

6

원래 pyparsing wiki에서 왔지만 Wayback Machine을 통해서만 사용할 수 있는 Paul McGuire의 다음 예제 는 일치하는 문자열 세트를 리턴하기 위해 일부 정규 표현식 을 구문 분석하는 문법을 제공합니다 . 따라서 '+'및 '*'와 같은 무한 반복 조건을 포함하는 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()
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.