Чи є регулярний вираз для виявлення дійсного регулярного виразу?


1006

Чи можливо виявити дійсний регулярний вираз з іншим регулярним виразом? Якщо так, будь ласка, надайте приклад нижче.


58
Отже, ваша проблема - перевірка регулярного виразу, ви вирішили його. Цікаво, чи властивість регексів, що збільшують кількість проблем, є аддитивною чи мультиплікативною. Схоже, 4 проблеми замість 2 :)
абест

15
Існує багато позначень для регулярних виразів - деякі особливості та їх написання загальні для більшості, деякі написані по-різному або доступні лише в одній конкретній нотації. Більшість цих позначень не є "регулярними" в звичайному граматичному розумінні - вам знадобиться контекстний вільний аналізатор для обробки необмеженого вкладу піддепресій - хоча багато сучасних "регулярних виразів" нотацій мають розширення, що виходять за рамки початкового формального визначення і може дозволити розпізнавати власні позначення. У будь-якому випадку, чому б просто не запитати свою бібліотеку регулярних виразів, якщо кожен регулярний вираз є дійсним?
Steve314

1
@bevacqua Мені потрібно перевірити регулярний вираз в XML-схемі. Як я можу це зробити без іншого regexp?
zenden2k

3
Фактично компілюйте / запустіть регулярний вираз (шаблон), який потрібно перевірити, за механізмом обробки винятків, який має ваша мова. Таким чином, мовний регекс-движок / компілятор сам перевірить це. (Це передбачає правильний базовий синтаксис, щоб програма запускалася, але це можна включити в перевірку, використовуючи засоби ваших мов, щоб оцінити рядок для регулярного вираження як (можливо, синтаксично неправильний) код або подібний.)
zdim

Це ідеальна відповідь для користувачів python: 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 не підтримує рекурсію безпосередньо. (The (?1)and (?R)constructs.) Рекурсію потрібно було б перетворити на підрахунок збалансованих груп:

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

Теоретично неможливо зіставити всі дійсні регекс-граматики з регулярними виразами.

Можливо, якщо двигун regex підтримує рекурсію, таку як PCRE, але це вже не можна назвати регулярними виразами.

Дійсно, "рекурсивний регулярний вираз" - це не регулярний вираз. Але це часто прийняте розширення для двигунів-генексів ... Як не дивно, цей розширений регулярний вираз не відповідає розширеним регексам.

"У теорії теорія та практика однакові. На практиці вони ні". Майже кожен, хто знає регулярні вирази, знає, що регулярні вирази не підтримують рекурсії. Але PCRE та більшість інших реалізацій підтримують набагато більше, ніж основні регулярні вирази.

використовуючи цей скрипт оболонки в команді grep, він показує мені деяку помилку .. grep: Недійсний вміст {}. Я створюю скрипт, який міг би зібрати кодову базу, щоб знайти всі файли, що містять регулярні вирази

Цей шаблон використовує розширення, яке називається рекурсивними регулярними виразами. Це не підтримується ароматом регулярного вираження POSIX. Ви можете спробувати за допомогою перемикача -P, щоб увімкнути аромат регексу PCRE.

Сам Regex "не є звичайною мовою, а тому не може бути розбитий регулярним виразом ..."

Це справедливо для класичних регулярних виразів. Деякі сучасні реалізації дозволяють рекурсію, що перетворює його на мову, що не містить контексту, хоча це завдання є дещо багатослівним.

Я бачу, де ти відповідаєш []()/\. та інші спеціальні символи регулярного виразів. Де ви дозволяєте неспеціальних символів? Схоже, це буде відповідати ^(?:[\.]+)$, але ні ^abcdefg$. Це дійсний регулярний вираз.

[^?+*{}()[\]\\|]відповідатиме будь-якому окремому символу, не входить до жодної з інших конструкцій. Це включає в себе як буквальний ( a- z), а також деякі спеціальні символи ( ^, $, .).


10
Ця відповідь направляє людей у ​​абсолютно неправильному напрямку. Вони ніколи не повинні використовувати regEx для пошуку регулярних виразів, оскільки він не може працювати коректно у всіх випадках. Дивіться додану мою відповідь.
vitaly-t

1
.{,1}не має собі рівних Зміна на ^((?:(?:[^?+*{}()[\]\\|]+|\\.|\[(?:\^?\\.|\^[^\\]|[^\\^])(?:[^\]\\]+|\\.)*\]|\((?:\?[:=!]|\?<[=!]|\?>)?(?1)??\)|\(\?(?:R|[+-]?\d+)\))(?:(?:[?+*]|\{\d*(?:,\d*)?\})[?+]?)?|\|)*)$відповідність. Зміна \d+на\d*
юнзен

4
Регекс по def не повинен мати рекурсії, принаймні скажіть щось подібне в ур-відповіді, ур-регекс-движок, ймовірно, "занадто потужний" і насправді не є регекс-двигуном.
Чарлі Паркер

Лише зауважте, що ви забули прапор x
RedClover

Здається, цей валідатор створений для виразів PCRE, але він передасть багато невірних POSIX ERE. Примітно, що вони трохи перебірливими в діапазоні класів символів, наприклад , це справедливо в PCRE , але не в ЕРД: [a-b-c].
Педро Гімено

321

Навряд чи.

Оцініть його на тій try..catchчи іншій мові.


228

Ні, якщо ви суворо говорите про регулярні вирази і не включаєте в себе реалізацію регулярних виразів, які є фактично граматиками без контексту.

Існує одне обмеження регулярних виразів, яке унеможливлює написання регулярних виразів, які відповідають усім і тільки регулярним виразам. Ви не можете співставити такі реалізації, як дужки, які є парними. Regexes використовує багато таких конструкцій, візьмемо []для прикладу. Кожен раз, коли є, [повинно бути відповідність ], що досить просто для регулярного виразів "\[.*\]".

Те, що унеможливлює регулярні вирази, - це те, що вони можуть бути вкладені. Як можна написати регулярний вираз, який відповідає вкладеним дужкам? Відповідь, ви не можете без нескінченно тривалого регексу. Ви можете зіставити будь-яку кількість вкладених дужок через грубу силу, але ви ніколи не зможете зіставити довільно довгий набір вкладених дужок.

Цю можливість часто називають підрахунком, оскільки ви рахуєте глибину гніздування. Зворотний вираз за визначенням не має можливості рахувати.


Я закінчив писати про це « Регулярні обмеження висловлювань ».


53

Хороше питання.

Справжні звичайні мови не можуть вирішити довільно глибоко вкладені круглі дужки. Якщо ваш алфавіт містить, '('а ')'мета - визначити, чи є рядки з них добре сформованими дужками. Оскільки це необхідна вимога до регулярних виразів, відповідь - ні.

Однак якщо ви послабите вимогу і додасте рекурсію, ви, ймовірно, можете це зробити. Причина полягає в тому, що рекурсія може виступати як стек, дозволяючи «рахувати» поточну глибину вкладення, натискаючи на цей стек.

Расс Кокс написав " Відповідність регулярних виразів може бути простим і швидким ", що є чудовим трактатом про реалізацію двигуна regex.


16

Ні, якщо ви використовуєте стандартні регулярні вирази.

Причина в тому, що ви не можете задовольнити накачувальну лему для звичайних мов. Насосна лема стверджує , що рядок , що належить мові «L» є регулярним , якщо існує таке число «N», що після поділу рядка на три підрядка 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якого повернеться помилковим, якщо регулярний вираз не є дійсним Не забудьте використовувати @для придушення повідомлень про помилки:

@preg_match($regexToTest, '');
  • Поверне 1, якщо регулярний вираз //.
  • Поверне 0, якщо регулярний вираз.
  • Інакше повернеться помилковим.

6

Наступний приклад Пола Макгуайєра, який спочатку був із вікі-програми Pyparsing, але тепер доступний лише через машину Wayback , дає граматику для розбору деяких регулярних виразів для повернення набору відповідних рядків. Як такий, він відкидає ті повтори, які включають необмежені умови повторення, наприклад "+" та "*". Але це повинно дати вам уявлення про те, як структурувати аналізатор, який обробляє повторні.

# 
# 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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.