2012-02-23 3 views
2

Иногда хорошие старые инструменты по-прежнему работают лучше всего. В СЭД, я мог бы написать что-то вроде этого:Соответствующие диапазоны строк в python (например, диапазоны sed)

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

Первый применяет замену всех линий между теми согласующего/^ Страница 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? Я могу сделать это с регулярным выражением по всему файлу в виде одной строки, что является болью, потому что остальные преобразования являются ориентированными на линию. Я также не смог (полезно) интегрировать итераторы с линейно ориентированными регулярными выражениями. Я был бы признателен за предложения.

+0

«В конечном итоге нужно написать целый парсер»? Просто для подсчета строк? Зачем? –

+0

«Но как мы вставляем« # »в начале каждой строки многострочного комментария Java?» Это гораздо более сложный вопрос. Не имеет отношения к названию вопроса и первой части вопроса. Если это то, что вы действительно хотите узнать, а затем задайте ** настоящий вопрос отдельно. –

+0

@S, мой оригинальный вопрос сказал: «Я делаю вещи типа python с текстом и просто хочу иметь возможность работать на линейных диапазонах простым способом». Я хочу сопоставить диапазоны строк, чтобы что-то с ними делать, а не просто распечатывать их. Это мой первый вопрос SO, и я многому учусь о том, как не допустить, чтобы неправильные части обращали внимание. – alexis

ответ

2

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

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

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

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

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) 
+0

Из предложенных ответов это похоже на наиболее подходящий подход для применения кучи преобразований в файл. Это боль, чтобы интегрировать его с линейно ориентированными регулярными выражениями, тем не менее, поэтому мне все равно хотелось бы услышать лучший способ ... – alexis

+0

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

+0

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

1

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

#!/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

Диапазоны 'sed' * намного более гибкие, чем просто указания номеров строк. См. Примеры OP в качестве отправной точки. – NPE

+0

Правильно. Обозначение фрагмента отлично подходит для абсолютных номеров строк, но я спрашиваю об остальном. – alexis

+0

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

0

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

Но существуют разные подходы можно следовать:

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

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

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

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

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

+0

Спасибо за указатели. Я не говорю о гигабайтах, поэтому решение, которое работает для списка строк, было бы здорово. Я подозреваю, что есть итераторы, которые могут делать такие вещи, и делают кофе, пока они на нем. Надеюсь, кто-то здесь придумает чистый подход. – alexis

+0

@alexis: Я боюсь, что * «Я надеюсь, что кто-то здесь придумает чистый подход» * не заставит вас очень далеко, особенно здесь, на SO. В любом случае, я обновил свой ответ со ссылкой на 'itertools.takewhile()'. –

+0

Спасибо, Рик. Я не пытаюсь быть ленивым, просто я знаю об итераторах (включая takewhile), и я нахожу их очень громоздкими для такого рода задач. Я признаю, что у меня мало опыта использования причудливых, поэтому я прошу дать более конкретные рекомендации. Я уточнил вопрос, надеюсь, сделать мои цели более ясными. – alexis

0

Что-то вроде этого.

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

Спасибо, парень, но это именно те обручи, которых я хочу избежать! – alexis

+0

Основная часть кода - это модуль, который вы импортируете.Это не обруч. Это версия SED, написанная на Python. Как вы думаете, вы можете избежать сложности SED? Это сложно. И вы говорите о некоторых из самых сложных функций SED. Где бы вы хотели, чтобы сложность проявилась? Это должно быть где-то. –

+0

Спасибо за ваши усилия, @S. Вы правы, это довольно мощные функции sed. Но python очень хорошо инкапсулирует множество сложных интерфейсов - просто не этот. Поэтому, чтобы ответить на ваш вопрос, я надеюсь, что кто-то знает о языковой конструкции или модуле, который мне не нужно писать, что облегчит мне жизнь. – alexis

1

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

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')] 

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

+0

Это выглядит многообещающим, спасибо! Но когда эти диапазоны совпадают несколько раз, pattern2 должен найти первое совпадение после pattern1. (Я знаю, я не говорил это в вопросе). Этот код найдет только первое совпадение для каждого шаблона, независимо от порядка. – alexis

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