Регулярное выражение для разбора закомментированного файла конфигурации - PullRequest
3 голосов
/ 24 сентября 2010

Редактировать: Мне просто любопытно, как заставить это регулярное выражение работать. Пожалуйста, не говорите мне, что есть более простые способы сделать это. Это очевидно! : P

Я пишу регулярное выражение (используя Python) для разбора строк в файле конфигурации. Линии могут выглядеть так:

someoption1 = some value # some comment
# this line is only a comment
someoption2 = some value with an escaped \# hash
someoption3 = some value with a \# hash # some comment

Идея состоит в том, что все, что находится после хеш-символа, считается комментарием, за исключением случаев, когда хеш-код экранируется косой чертой.

Я пытаюсь использовать регулярное выражение, чтобы разбить каждую строку на отдельные части: начальный пробел, левая часть назначения, правая часть назначения и комментарий. Для первой строки в примере разбивка будет:

  • Пробелы: ""
  • Назначение слева: "someoption1 ="
  • Право присвоения: "некоторое значение"
  • Комментарий "# некоторый комментарий"

У меня есть такое регулярное выражение:

^(\s)?(\S+\s?=)?(([^\#]*(\\\#)*)*)?(\#.*)?$

У меня ужасно с регулярным выражением, так что не стесняйтесь разрывать его!

Используя Python re.findAll(), возвращается:

  • 0-й индекс: пробел, как и должно быть
  • 1-й индекс: левая сторона задания
  • 2-й указатель: правая часть присвоения, вплоть до первого хэша, независимо от того, экранирована она или нет (что неверно)
  • 5-й индекс: первый хэш, независимо от того, экранирован он или нет, и что-нибудь после него (что неверно)

Возможно, в регулярных выражениях есть что-то фундаментальное, что я упускаю. Если кто-то может решить это, я буду вечно благодарен ...

Ответы [ 6 ]

2 голосов
/ 13 марта 2011

Из 5 решений, представленных к настоящему времени, на самом деле работает только Gumbo. Вот мое решение, которое также работает и комментируется:

import re

def fn(line):
    match = re.search(
        r"""^          # Anchor to start of line
        (\s*)          # $1: Zero or more leading ws chars
        (?:            # Begin group for optional var=value.
          (\S+)        # $2: Variable name. One or more non-spaces.
          (\s*=\s*)    # $3: Assignment operator, optional ws
          (            # $4: Everything up to comment or EOL.
            [^#\\]*    # Unrolling the loop 1st normal*.
            (?:        # Begin (special normal*)* construct.
              \\.      # special is backslash-anything.
              [^#\\]*  # More normal*.
            )*         # End (special normal*)* construct.
          )            # End $4: Value.
        )?             # End group for optional var=value.
        ((?:\#.*)?)    # $5: Optional comment.
        $              # Anchor to end of line""", 
        line, re.MULTILINE | re.VERBOSE)
    return match.groups()

print (fn(r" # just a comment"))
print (fn(r" option1 = value"))
print (fn(r" option2 = value # no escape == IS a comment"))
print (fn(r" option3 = value \# 1 escape == NOT a comment"))
print (fn(r" option4 = value \\# 2 escapes == IS a comment"))
print (fn(r" option5 = value \\\# 3 escapes == NOT a comment"))
print (fn(r" option6 = value \\\\# 4 escapes == IS a comment"))

Приведенный выше скрипт выдает следующий (правильный) вывод: (протестировано с Python 3.0.1)

(' ', None, None, None, '# just a comment')
(' ', 'option1', ' = ', 'value', '')
(' ', 'option2', ' = ', 'value ', '# no escape == IS a comment')
(' ', 'option3', ' = ', 'value \\# 1 escape == NOT a comment', '')
(' ', 'option4', ' = ', 'value \\\\', '# 2 escapes == IS a comment')
(' ', 'option5', ' = ', 'value \\\\\\# 3 escapes == NOT a comment', '')
(' ', 'option6', ' = ', 'value \\\\\\\\', '# 4 escapes == IS a comment')

Обратите внимание, что в этом решении используется метод Джеффри Фридла "Развертывание цикла эффективности (который устраняет медленное чередование). Он также не использует обходной путь вообще и очень быстро. Освоение регулярных выражений (3-е издание) должен читать для всех, кто утверждает, что "знает" регулярные выражения (и когда я говорю "знаю", я имею в виду в нео " Я знаю Кунг-фу! " смысл :) 1013 *

2 голосов
/ 24 сентября 2010

Я бы использовал это регулярное выражение в многострочном режиме:

^\s*([a-zA-Z_][a-zA-Z_0-9]*)\s*=\s*((?:[^\\#]|\\.)+)

Это позволяет экранировать любой символ (\\.). Если вы просто хотите разрешить #, используйте \\#.

2 голосов
/ 24 сентября 2010

Причина, по которой ваше регулярное выражение не соответствует желаемому, заключается в жадном поведении регулярных выражений: каждая часть будет соответствовать самой длинной подстроке, так что остальная часть строки все еще может быть сопоставлена ​​с остальной частью регулярного выражение

Что это означает для одной из ваших строк с экранированным #:

  • [^\#]* (нет необходимости бежать # btw) будет соответствовать всему до первого хэша, включая обратную косую черту перед ним
  • (\\\#)* не будет ничего совпадать, так как строка в этой точке начинается с #
  • Финальный (\#.*) будет соответствовать остальной части строки

Простой пример, чтобы подчеркнуть это потенциально неинтуитивное поведение: в регулярном выражении (a*)(ab)?(b*), (ab)? никогда не будет ничего совпадать

Я считаю, что это регулярное выражение (основанное на оригинальном) должно работать: ^\s*(\S+\s*=([^\\#]|\\#?)*)?(#.*)?$

1 голос
/ 24 сентября 2010

Я оставил комментарий о цели этого вопроса, но, предположив, что этот вопрос касается только регулярных выражений, я все же дам ответ.

Предполагая, что вы имеете дело с вводом по одной строке за раз, я бы сказал об этом как о двухпроходной фазе. Это означает, что у вас будет 2 регулярных выражения.

  1. Что-то вроде (.*?(?<!\\))#(.*): сначала разделить #, перед ним не стоит \ (см. Документацию о негативном взгляде сзади);
  2. Разбор выражения оператора присваивания.
0 голосов
/ 24 сентября 2010

Попробуйте разбить его на 2 этапа:

  1. Обработка сброса для распознавания истинных комментариев (сначала # не предшествует \ (подсказка: «отрицательный взгляд сзади»)), удаление истинных комментариев, затем замена r"\#" на "#"

  2. Обрабатывать остаток без комментариев.

БОЛЬШОЙ СОВЕТ: используйте re.VERBOSE с комментариями

0 голосов
/ 24 сентября 2010

Я бы вообще не использовал для этого регулярное выражение, по той же причине, по которой я бы не пытался убить муху с помощью термоядерной боеголовки.

Предполагая, что вы читаете строку ввремя, просто:

  • , если первый символ - #, установить комментарий ко всей строке и очистить строку.
  • в противном случае найти первое вхождение #не сразу после \, установите для этого комментария плюс остальную часть строки и установите строку для всего до этого.
  • замените все вхождения \# в строке на #.

Вот и все, теперь у вас есть правильная строка и раздел комментариев.Используйте регулярные выражения, чтобы разделить новую секцию линии всеми способами.

Например:

import re

def fn(line):
    # Split line into non-comment and comment.

    comment = ""
    if line[0] == "#":
        comment = line
        line = ""
    else:
        idx = re.search (r"[^\\]#", line)
        if idx != None:
            comment = line[idx.start()+1:]
            line = line[:idx.start()+1]

    # Split non-comment into key and value.

    idx = re.search (r"=", line)
    if idx == None:
        key = line
        val = ""
    else:
        key = line[:idx.start()]
        val = line[idx.start()+1:]
    val = val.replace ("\\#", "#")

    return (key.strip(),val.strip(),comment.strip())

print fn(r"someoption1 = some value # some comment")
print fn(r"# this line is only a comment")
print fn(r"someoption2 = some value with an escaped \# hash")
print fn(r"someoption3 = some value with a \# hash # some comment")

производит:

('someoption1', 'some value', '# some comment')
('', '', '# this line is only a comment')
('someoption2', 'some value with an escaped # hash', '')
('someoption3', 'some value with a # hash', '# some comment')

Если вы должен использовать регулярное выражение (вопреки моему совету), ваша конкретная проблема заключается здесь:

[^\#]

Это (при условии, что вы имели в виду правильно экранированный r"[^\\#]") будет пытаться сопоставить любой символ, кроме \ или #, а не последовательность \#, как вы хотите.Вы можете использовать негативные оглядки, чтобы сделать это, но я всегда говорю, что, как только регулярное выражение становится нечитаемым для дебила в спешке, лучше вернуться к процедурному коду: -)


При отражениилучший способ сделать это с многоуровневым разбиением (чтобы регулярное выражение не становилось слишком отвратительным при обработке пропущенных полей), следующим образом:

def fn(line):
    line = line.strip()                            # remove spaces
    first = re.split (r"\s*(?<!\\)#\s*", line, 1)  # get non-comment/comment
    if len(first) == 1: first.append ("")          # ensure we have a comment
    first[0] = first[0].replace("\\#","#")         # unescape non-comment

    second = re.split (r"\s*=\s*", first[0], 1)    # get key and value
    if len(second) == 1: second.append ("")        # ensure we have a value
    second.append (first[1])                       # create 3-tuple
    return second                                  # and return it

При этом используется отрицательный вид:вперед, чтобы правильно сопоставить разделитель комментариев, затем разделяет бит без комментариев на ключ и значение.Пробелы обрабатываются правильно и в этом, давая:

['someoption1', 'some value', 'some comment']
['', '', 'this line is only a comment']
['someoption2', 'some value with an escaped # hash', '']
['someoption3', 'some value with a # hash', 'some comment']
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...