[英]Matching ranges of lines in python (like sed ranges)
有時候,好的舊工具仍然效果最好。 在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行開始打印,並在一個匹配的/ ^ Page 10:/之后停止3行。 第三個示例對指定范圍內的每行應用替換。
我不介意使用re.search逐行搜索,但是對於行范圍,行號或相對偏移量,我最終不得不編寫整個解析器。 是否存在可以簡化此類操作的python慣用語或模塊?
我不想從python調用sed:我正在用文本做python類型的事情,只希望能夠以一種直接的方式在行范圍上進行操作。
編輯:如果解決方案適用於python字符串列表,則很好。 我不希望處理千兆字節的文本。 但是我確實需要指定多個操作,而不僅僅是一個操作,並將它們與單行正則表達式替換交錯。 我已經研究了迭代器(實際上,我會歡迎使用迭代器的解決方案),但是除單一操作外,結果總是失控。
這是一個簡單的示例:具有Java樣式注釋的代碼片段,將其更改為python注釋。 (不用擔心,我不會嘗試使用regexps編寫交叉編譯器:-)
/*
This is a multi-line comment.
It does not obligingly start lines with " * "
*/
x++; // a single-line comment
編寫將“ //”注釋更改為“#”的正則表達式很簡單(也可以刪除分號,將“ ++”更改為“ + = 1”,等等。)但是我們如何在開始時插入“#”多行java注釋的每一行? 我可以使用整個字符串的正則表達式作為單個字符串來完成此操作,這很痛苦,因為其余的轉換都是面向行的。 我也無法(有用地)將迭代器與面向行的正則表達式集成在一起。 我將不勝感激建議。
我會嘗試使用正則表達式標志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)
至少對於注釋,只需使用一個真正的解析器。
#!/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
您可以嘗試這樣的事情:
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一樣簡潔。
我認為在Python中沒有簡單的方法可以做到這一點。
但是您可以遵循不同的方法:
逐行閱讀文件並僅在需要時激活搜索。
這具有僅讀取文件一次的優點,但是一次只能讀取一行。
使用itertools.islice()
將文件切片,然后在其中搜索模式。
您必須為每種模式再次讀取文件,但是實現起來非常容易。
使用mmap
。
如果您的文件不太大,並且要查找的樣式不止一種,那么我會選擇這種樣式。
編輯:如果您對迭代器工具感興趣,可以使用帶有智能lambda的itertools.takewhile()
來完成。
免責聲明:我對sed
一無所知。
這樣的事情。
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 ) )
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.