Соответствие диапазонов линий в Python (например, диапазоны sed) - PullRequest
2 голосов
/ 23 февраля 2012

Иногда старые добрые инструменты работают лучше всего.В sed я мог бы написать что-то вроде этого:

sed '/^Page 5:/,/^Page 6:/p' 
sed '110,/^Page 10:/+3p'
sed '/^Page 5:/,/^Page 6:/s/this/that/g' 

Первая применяет замену ко всем строкам между теми, которые соответствуют / ^ Page 5: / и / ^ Page 6: /.Второй начинает печатать в строке 110 и останавливается на 3 строки после того, как совпадает / ^ Страница 10: /.Третий пример применяет подстановку к каждой строке в указанном диапазоне.

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

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

Редактировать: Хорошо, если решение работает со списком строк Python.Я не хочу обрабатывать гигабайты текста.Но мне нужно указать несколько операций, а не только одну, и перемежать их с помощью однострочных подстановочных выражений.Я смотрел на итераторы (на самом деле я бы приветствовал решение, использующее итераторы), но результаты всегда выходили из-под контроля для чего-то большего, чем одна операция.

Вот простой пример: фрагмент кода с javaв стиле комментариев, которые должны быть изменены на комментарии Python.(Не волнуйтесь, я НЕ пытаюсь написать кросс-компилятор с использованием регулярных выражений :-)

/* 
 This is a multi-line comment.
 It does not obligingly start lines with " * "
 */

x++;  // a single-line comment

Тривиально писать регулярные выражения, которые заменяют комментарии "//" на "#" (а также отбрасыватьточка с запятой, измените «++» на «+ = 1» и т. д.) Но как вставить «#» в начало каждой строки многострочного Java-комментария?Я могу сделать это с помощью регулярного выражения всего файла в виде одной строки, что является проблемой, потому что остальные преобразования ориентированы на строки.Я также не смог (полезно) интегрировать итераторы с регулярными выражениями, ориентированными на строки.Буду признателен за предложения.

Ответы [ 5 ]

2 голосов
/ 23 февраля 2012

Я бы попробовал использовать флаги регулярных выражений re.DOTALL или re.MULTILINE.

Первый обрабатывает символы новой строки как обычные символы, поэтому, если вы используете .*, он может считать новые строки внутри шаблона.

Второй вариант почти такой же, но вы все равно можете использовать линейные линии (^) и конечные линии ($), чтобы соответствовать им. Это может быть полезно для подсчета строк.

Я мог бы, на данный момент, придумать это, который печатает ОДНУ БОЛЬШЕ ЛИНИИ после вхождения «шесть» (целая строка захвачена финальной ^.*?$, но я почти уверен, что должно быть лучший способ):

import re

source = """one
two
three
four
five
six
seven
eight
nine
ten"""

print re.search('^three.*six.*?^.*?$', source, re.DOTALL|re.MULTILINE).group(0)
1 голос
/ 23 февраля 2012

Вы можете попробовать что-то вроде этого:

import re

def firstline(rx, lst):
    for n, s in enumerate(lst):
        if re.search(rx, s):
            return n
    return 0

и затем:

text = ["How", "razorback", "jumping", "frogs", "can", "level", "six", "piqued", "gymnasts"]

# prints all lines between the one matching `^r` and the one matching `^s`
print text[firstline('^r', text)+1:firstline('^s', text)]

Это выглядит слишком многословно, но многословие можно уменьшить, например:

import functools
L = functools.partial(firstline, lst=text)

print text[L('^r')+1:L('^s')]

Последний почти такой же лаконичный, как и его аналог sed.

1 голос
/ 23 февраля 2012

Для комментариев, по крайней мере, просто используйте настоящий парсер.

#!/usr/bin/python

from pyparsing import javaStyleComment
import re

text = """

/*
 * foo
 * bar
 * blah
 */

/***********************
 it never ends
***********************/

/* foo

   bar blah
*/

/*
* ugly
* comment
*/

// Yet another

int a = 100;

char* foo;

"""

commentTokenStripper = re.compile(r'\s*[/\\\*]')

for match in javaStyleComment.scanString(text):
    start,end = match[-2:]
    print '# comment block %d-%d ##############' % (start,end)
    lines = ['#' + re.sub(commentTokenStripper, '', l) for l in match[0][0].splitlines()]
    print '\n'.join(lines)
    print

Выход

# comment block 2-30 ##############
#
# foo
# bar
# blah
#

# comment block 32-96 ##############
#
# it never ends
#

# comment block 98-121 ##############
# foo
# 
#   bar blah
#

# comment block 123-145 ##############
#
# ugly
# comment
#

# comment block 147-161 ##############
# Yet another
0 голосов
/ 23 февраля 2012

Как то так.

from __future__ import print_function

def get_lines( some_file, start_rule, end_rule, process=print ):
    line_iter= enumerate( source )
    for n, text in line_iter:
        if start_rule( n, text ): 
            process( text )
            break
    for n, text in line_iter:
        process( text )
        if end_rule( n, text ): break

Тогда вы можете определить множество мелких функций:

def match_page_5( n, text ):
    return re.match( '^Page 5:', text )
def match_line( n, text ):
    return line == n

Или сохраняющие состояние, вызываемые объекты

class Match_Pattern( collections.Callable ):
    def __init__( self, pattern ):
        self.pat= re.compile( pattern )
    def __call__( self, n, text ):
        return self.pat.match( text )

class Match_Lines_Post_Pattern( collections.Callable ):
    def __init__( self, pattern, lines ):
        self.pat= re.compile( pattern )
        self.lines= lines
        self.saw_it= None
    def __call__( self, n, text ):
        if self.saw_it:
            if n == self.saw_it + self.lines
                return True
            if self.pat.match( text ):
                self.saw_it = n

Вы можете создать синтаксический сахар с помощью таких функций.

def sed_by_pattern( filename, pattern1, pattern2 ):
    with open(filename,'r') as source:
        get_lines( source, lambda n,tx: re.match(pattern1,tx), lambda n,tx: re.match(pattern2,tx) )

Это приведет вас к функции, подобной следующей Это использование так же просто, как команда SED с дополнительной пунктуацией.

sed_by_pattern( some_file, '^Page 5:', '^Page 6:' )

Или этот кусочек сахара ...

def sed_by_matcher( filename, matcher1, matcher2 )
    with open(filename, 'r') as source:
        get_lines( source, matcher1, matcher2 )

Это использование так же просто, как команда SED с дополнительной пунктуацией.

see_by_matcher( some_file, match_line(100), Match_Lines_Post_Pattern( '^Page 10:', 3 ) )
0 голосов
/ 23 февраля 2012

Я не думаю, что есть простой способ сделать это в Python.

Но есть разные подходы, которые вы могли бы использовать:

  • Читайте файл построчно и активируйте поиск только тогда, когда вам нужно.
    Преимущество заключается в том, что файл читается только один раз, но он работает в одну строку за раз.

  • Нарежьте файл с помощью itertools.islice() и выполните поиск вашего паттерна там.
    Вам придется снова читать файл для каждого шаблона, но его очень легко реализовать.

  • Использование mmap.
    Если ваш файл не слишком большой и у вас есть несколько шаблонов для поиска, я бы пошел с этим.

Редактировать: Если вы заинтересованы в инструментах итераторов, itertools.takewhile() с умной лямбдой может помочь.

Отказ от ответственности: Я ничего не знаю о sed.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...