有时好的旧工具仍然效果最好。在sed中,我可以这样写:
sed '/^Page 5:/,/^Page 6:/p'
sed '110,/^Page 10:/+3p'
sed '/^Page 5:/,/^Page 6:/s/this/that/g'
第一个将替换应用于匹配的那些之间的所有行:/ / Page 6:/。第二个开始在第110行打印并在一个匹配后停止3行:/ /。第三个示例将替换应用于指定范围内的每一行。
我不介意使用re.search逐行搜索,但对于行范围,行号或相对偏移,我最终必须编写一个完整的解析器。是否有可以简化这种操作的python习语或模块?
我不想从python调用sed:我正在用文本做python类型的东西,只是希望能够以简单的方式在行范围上操作。
编辑:如果解决方案适用于python字符串列表,那就没问题了。我不打算处理千兆字节的文本。但我确实需要指定几个操作,而不只是一个操作,并将它们与单行regexp替换交错。我已经看过迭代器了(事实上我会欢迎使用迭代器的解决方案),但结果总是失控,除了单个操作。
这是一个简单的例子:带有java风格注释的代码片段,将被更改为python注释。 (别担心,我不是在尝试使用regexps编写交叉编译器:-)
/*
This is a multi-line comment.
It does not obligingly start lines with " * "
*/
x++; // a single-line comment
编写将“//”注释更改为“#”(同时删除分号,将“++”更改为“+ = 1”等)的正则表达式是微不足道的。但是我们如何在“#”处插入“#”多行java评论的每一行的开头?我可以将整个文件的正则表达式作为单个字符串来完成,这很痛苦,因为其余的转换是面向行的。我也无法(有用地)将迭代器与面向行的regexp集成在一起。我很感激你的建议。
答案 0 :(得分: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)
答案 1 :(得分: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
答案 2 :(得分: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')]
后者几乎与其sed对应物一样简洁。
答案 3 :(得分:0)
我认为在Python中没有直接的方法。
但是你可以遵循不同的方法:
逐行阅读文件,仅在需要时激活搜索 这样做的好处是只读取一次文件,但当时它在一行上工作。
使用itertools.islice()
剪切文件并在那里搜索您的模式
你必须为每个模式再次阅读文件,但它很容易实现。
使用mmap
。
如果您的文件不是太大而且您需要查找多个模式,我会选择此模式。
编辑:如果您对迭代器工具感兴趣,使用智能lambda的itertools.takewhile()
可能会有效。
免责声明:我对sed
一无所知。
答案 4 :(得分: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 ) )