有効な正規表現を検出するための正規表現はありますか?


1007

別の正規表現で有効な正規表現を検出することはできますか?もしそうなら、以下のサンプルコードを教えてください。


58
つまり、問題は正規表現を検証することであり、それを解決するために正規表現を選択しました。正規表現の問題数が増える性質が加法的か乗法的か。それは2ではなく4つの問題のように感じます:)
abesto '18年

15
正規表現には多くの表記法があります-一部の機能とそのスペルはほとんどに共通ですが、一部は異なるスペルで記述されているか、1つの特定の表記法でしか使用できません。これらの表記のほとんどは、通常の文法の意味で「通常」ではありません。部分式の無制限のネストを処理するには、コンテキストフリーパーサーが必要です。独自の表記が認識されるようにする可能性があります。いずれにせよ、それぞれの正規表現が有効かどうかを単に正規表現ライブラリに尋ねてみませんか?
Steve314、2015

1
@bevacqua XMLスキーマで正規表現を検証する必要があります。別の正規表現なしでどうすればいいですか?
zenden2k

3
実際に、チェックする正規表現(パターン)をコンパイル/実行し、使用している言語の例外処理メカニズムの下で実行します。したがって、言語の正規表現エンジン/コンパイラ自体がそれをチェックします。(これは、プログラムが実行されるように正しい基本構文を想定していますが、言語の機能を使用して正規表現の文字列を(おそらく構文的に間違っている)コードなどとして評価することでチェックに含めることができます。)
zdim

これはPythonのユーザーのための完璧な答えです:stackoverflow.com/questions/19630994/...
ジャンニ

回答:


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
/

これは再帰的な正規表現であり、多くの正規表現エンジンではサポートされていません。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$。それは有効な正規表現です。

[^?+*{}()[\]\\|]他の構成要素の一部ではなく、任意の1文字に一致します。(これは、リテラルの両方を含むa- z(、および特定の特殊文字)^$.)。


10
この答えは人々を完全に間違った方向に導きます。正規表現を見つけるためにregExを使用してはなりません。正規表現はすべてのケースで正しく機能するわけではないためです。追加された私の答えを参照してください。
vitaly-t 2016

1
.{,1}比類のないです。^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d*(?:,\d*)?\})[?+]?)?|\|)*)$マッチに変更。CHange \d+to\d*
yunzen 2017年

4
defによるregexには再帰があってはなりません。少なくともそのようなことを言うなら、ur regexエンジンはおそらく「強力すぎる」ため、実際にはregexエンジンではありません。
チャーリーパーカー

xフラグを忘れたときのメモ
RedClover 2017

このバリデーターはPCRE式用に作成されたようですが、多くの無効なPOSIX EREを渡します。特に、これらは文字クラスの範囲で少し厳密です。たとえば、これはPCREでは有効ですが、EREでは無効です[a-b-c]
ペドロGimeno

321

ありそうもない。

それをtry..catchあなたの言語が提供するもので評価してください。


228

いいえ、厳密に正規表現について話していて、実際には文脈自由文法であるいくつかの正規表現実装を含まない場合は。

正規表現には1つの制限があり、すべての正規表現にのみ一致する正規表現を作成することはできません。対になっている中括弧などの実装を一致させることはできません。正規表現はそのような構成要素を多く使用します。[]ています。例としてて。があるときはいつでも、正規表現に対して十分に単純な[マッチングが存在する必要があります。]"\[.*\]"

正規表現が不可能になるのは、それらがネストできることです。ネストされた括弧に一致する正規表現をどのように書くことができますか?答えは、無限に長い正規表現なしではあり得ないことです。入れ子になった括弧はブルートフォースでいくつでも一致させることができますが、任意に長いネストされた括弧のセットを一致させることはできません。

ネストの深さを数えるので、この機能はしばしばカウントと呼ばれます。正規表現には、定義上、カウントする機能はありません。


私はこれについて「正規表現の制限」を書きました。


53

良い質問。

真の通常の言語は、任意に深くネストされた整形式の括弧を決定できません。あなたのアルファベットが含まれている場合'('と、')'、目標がこれらの文字列に整形式の一致する括弧があるかどうかを決定することである場合 これは正規表現に必要な要件なので、答えはノーです。

ただし、要件を緩めて再帰を追加すると、おそらくそれを実行できます。その理由は、再帰がスタックとして機能し、このスタックにプッシュすることにより、現在の入れ子の深さを「カウント」できるからです。

Russ Coxは「正規表現マッチングは単純かつ高速である可能性がある」と書いており、これは正規表現エンジンの実装に関するすばらしい論文です。


16

いいえ、標準の正規表現を使用する場合。

その理由は、通常の言語ではポンプの補題を満たすことができないためです。ポンプの補題番号が存在する場合、言語「L」に属する文字列が正規であると述べている「N」など3つの部分文字列に文字列を分割した後、そのxyzというように、|x|>=1 && |xy|<=Nあなたは繰り返すことができ、y必要に何度でもと文字列全体は引き続きに属しLます。

ポンピングレンマの結果として、という形式の通常の文字列a^Nb^Mc^N、つまり、同じ長さの2つの部分文字列を別の文字列で区切ることはできません。どのような方法でもx、そのような文字列をとで分割するyzy「a」と「c」の数が異なる文字列を取得せずに「ポンプ」して、元の言語を残すことはできません。たとえば、正規表現で括弧を使用する場合です。


5
これは、ポンピングレンマの正確な説明ではありません。1つ目は、1つの文字列ではなく、言語全体であるかどうかです。第二に、それは規則性の必要条件であり、十分条件ではありません。最後に、十分に長いストリングのみがポンピング可能です。
ダライグリンバーグ

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

次の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()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.