匹配python中的行范围(如sed范围)

时间:2012-02-23 10:19:33

标签: python sed

有时好的旧工具仍然效果最好。在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集成在一起。我很感激你的建议。

5 个答案:

答案 0 :(得分:2)

我会尝试使用正则表达式标记re.DOTALLre.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 ) )