2013-03-26 4 views
0

Работа с простым скриптом, обрабатывающим текстовые файлы и журналы. Он должен взять из командной строки список регулярных выражений для подстановок. Например:Пользовательская подстановка regex в python

./myscript.py --replace=s/foo/bar/ [email protected]/etc/[email protected]/etc/[email protected] [email protected]\@[email protected]\@[email protected] 

Есть простой способ, чтобы обеспечить заданный пользователь шаблон подстановки в питон повторной библиотеки? И этот шаблон работает против строки? Любое элегантное решение?

Если возможно, я бы хотел не писать собственный парсер. Обратите внимание, что я хотел бы поддерживать модификаторы, такие как/g или/i и т. Д.

Спасибо!

+0

Вам нужно будет написать свой собственный парсер для разбора 'S/Foo/бар/G' в' регулярное выражение, ReplaceWith, flags' и обрабатывать спасаясь сепараторов. – Eric

+2

На данный момент, было бы проще просто использовать sed? –

+0

Как только вы разделите шаблон замещения на свой шаблон и запасные части, вы можете использовать 're.compile()' для создания объекта regexp. – Barmar

ответ

0

Как упоминалось в комментариях, вы можете использовать re.compile(), но это работает только для match ing и search ing, по-видимому. Предполагая, что вы только замены, вы могли бы сделать что-то вроде этого:

modifiers_map = { 
    'i': re.IGNORE, 
    ... 
} 

for replace in replacements: 
    # Look for a generalized separator in front of a command 
    m = re.match(r'(s?)(.)([^\2]+)\2([^\2]+)\2([ig]*)', replace) 
    if not m: 
     print 'Invalid command: %s' % replace 
     continue 
    command, separator, query, substitution, modifiers = m.groups() 
    # Convert the modifiers to flags 
    flags = reduce(operator.__or__, [modifiers_map[char] for char in modifiers], 0) 
    # This needs a little bit of tweaking if you want to support 
    # group matching (like \1, \2, etc.). This also assumes that 
    # you're only getting 's' as a command 
    my_text = re.sub(query, substitution, my_text, flags=flags) 

Достаточно сказать, что это черновик, но я думаю, что бы получить вам 90% пути к тому, что вы ищете ,

+0

're.compile()' возвращает объект, который имеет методы '.match',' .search', '.sub',' .finditer' и т. Д. '[^ \ 1]' не будет работать: он эквивалентен '[^ c]' где 'ord (c) == 1'. Также регулярное выражение не обрабатывает экранированный разделитель.Флаг 'g' по умолчанию используется в Python, поэтому' modifiers_map' не будет обрабатывать его отсутствие. – jfs

+0

Это полезно знать о 're.compile()' - я бы хотел, хотя бы это было странно, если бы они поддерживали только команды поиска :-) –

+0

Однако, хотя '' [^ \ 1] ''может ссылаться на символ, 'r '[^ \ 1]'' относится к группе: 're.match (r '(s?) (.) ([^ \ 2] +) \ 2 ([^ \ 2] +) \ 2 ([ig] *) ',' s @ foo @ bar @ i '). Groups() 'было бы быстрым способом увидеть, что –

0

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

$ myscript --replace=foo bar \ 
>   --replace=/etc/hosts /etc/foo gi \ 
>   [email protected] [email protected] 

g флага по умолчанию в Python, так что вы должны добавить специальную поддержку для него:

#!/usr/bin/env python 
import re 
from argparse import ArgumentParser 
from functools import partial 

all_re_flags = 'Lgimsux' # regex flags 
parser = ArgumentParser(usage='%(prog)s [--replace PATTERN REPL [FLAGS]]...') 
parser.add_argument('-e', '--replace', action='append', nargs='*') 
args = parser.parse_args() 
print(args.replace) 

subs = [] # replacement functions: input string -> result 
for arg in args.replace: 
    count = 1 # replace only the first occurrence if no `g` flag 
    if len(arg) == 2: 
     pattern, repl = arg 
    elif len(arg) == 3: 
     pattern, repl, flags = arg 
     if ''.join(sorted(flags)) not in all_re_flags: 
      parser.error('invalid flags %r for --replace option' % flags) 
     if 'g' in flags: # add support for `g` flag 
      flags = flags.replace('g', '') 
      count = 0 # replace all occurrences 
     if flags: # embed flags 
      pattern = "(?%s)%s" % (flags, pattern) 
    else: 
     parser.error('wrong number of arguments for --replace option') 
    subs.append(partial(re.compile(pattern).sub, repl, count=count)) 

Вы можете использовать subs следующим образом:

input_string = 'a b a b' 
for replace in subs: 
    print(replace(input_string)) 

экзамен PLE:

$ ./myscript -e 'a b' 'no flag' -e 'a B' 'with flags' ig 

Выход:

[['a b', 'no flag'], ['a B', 'with flags', 'ig']] 
no flag a b 
with flags with flags 
0

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

Это не является значительно более сложным, чем другие предложения, см. Ниже. Мне просто нужно написать тесты сейчас.

Спасибо!

class Replacer(object): 
    def __init__(self, patterns=[]): 
    self.patterns = [] 
    for pattern in patterns: 
     self.AddPattern(pattern) 

    def ParseFlags(self, flags): 
    mapping = { 
     'g': 0, 'i': re.I, 'l': re.L, 'm': re.M, 's': re.S, 'u': re.U, 'x': re.X, 
     'd': re.DEBUG 
    } 

    result = 0 
    for flag in flags: 
     try: 
     result |= mapping[flag] 
     except KeyError: 
     raise ValueError(
      "Invalid flag: %s, known flags: %s" % (flag, mapping.keys())) 
    return result 

    def Apply(self, text): 
    for regex, repl in self.patterns: 
     text = regex.sub(repl, text) 
    return text 

    def AddPattern(self, pattern): 
    separator = pattern[0] 
    match = [] 
    for position, char in enumerate(pattern[1:], start=1): 
     if char == separator: 
     if pattern[position - 1] != '\\': 
      break 
     match[-1] = separator 
     continue 
     match += char 
    else: 
     raise ValueError("Invalid pattern: could not find divisor.") 

    replacement = [] 
    for position, char in enumerate(pattern[position + 1:], start=position + 1): 
     if char == separator: 
     if pattern[position - 1] != '\\': 
      break 
     replacement[-1] = separator 
     continue 
     replacement += char 
    else: 
     raise ValueError(
      "Invalid pattern: could not find divisor '%s'." % separator) 

    flags = self.ParseFlags(pattern[position + 1:]) 
    match = ''.join(match) 
    replacement = ''.join(replacement) 
    self.patterns.append((re.compile(match, flags=flags), replacement)) 
+0

Он производит тот же результат с/без флага 'g' (вы, вероятно, должны заменить только первое совпадение, если не указано' g'). Он не обрабатывает четное количество escape-символов (чтобы избежать escape-символа). – jfs

+0

бог точек! попытается исправить их, не должно быть трудно. – rabexc

Смежные вопросы