2012-02-23 82 views
2

有时,好的旧工具仍然效果最好。在SED,我可以写这样的事情:蟒蛇中的线条匹配范围(如sed范围)

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

第一施加取代成的那些匹配/ ^第5页之间的所有行:/和/ ^第6页:/。第二个在第110行开始打印,并在第一个匹配/第10页之后停止3行。第三个示例将替换应用于指定范围中的每一行。

我不介意使用re.search逐行搜索,但对于行范围,行号或相对偏移量,我最终不得不编写一个完整的解析器。有没有可以简化这种操作的Python成语或模块?

我不想从python中调用sed:我正在使用python类型的东西来处理文本,只是希望能够以直接的方式对线段进行操作。

编辑:如果解决方案工作在python的字符串列表上,这很好。我不想处理千兆字节的文本。但是我确实需要指定几个操作,而不仅仅是一个,并且用单行regexp替换来交叉它们。我已经看过迭代器(实际上我会欢迎使用迭代器的解决方案),但结果总是无法用于单一操作以外的任何事情。

下面是一个简单的例子:一个包含java风格评论的代码片段,将被改为python评论。 (别担心,我不是想编写使用正则表达式一个交叉编译:-)

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

x++; // a single-line comment 

是微不足道的编写正则表达式是改变“//”评论“#”(也下降分号,将“++”改为“+ = 1”等)但是我们如何在多行的java注释的每一行的开始插入“#”?我可以将整个文件的正则表达式作为单个字符串来执行,这是一种痛苦,因为其余的转换都是面向行的。我也无法(有用地)将迭代器与面向行的正则表达式集成在一起。我会很感激的建议。

+0

“最终不得不编写一个完整的解析器”?只是为了数线?为什么? – 2012-02-23 10:45:35

+0

“但是我们如何在多行的java注释的每一行的开头插入”#“?”这是一个非常复杂得多的问题。与问题的标题和问题的第一部分无关。如果这是你真的想知道的,那么就单独询问**真实**问题。 – 2012-02-23 13:24:19

+0

@S,我原来的问题是“我正在用文本做python类型的东西,只是希望能够以直接的方式在线范围内进行操作。”我想匹配线范围,以便与他们做一些事情,而不仅仅是打印出来。这是我的第一个SO问题,我正在学习很多关于如何防止错误部分得到所有关注的问题。 – alexis 2012-02-23 13:35:55

回答

2

我会尝试使用正则表达式标记re.DOTALLre.MULTILINE

第一行将换行符视为常规字符,因此如果使用.*,它可能会计算模式内的换行符。

第二个差不多,但仍然可以使用linestarts(^)和endlines($)来匹配这些。这可以用于统计线条。

现在,我可以想出这个,在“六”发生后打印ONE MORE LINE(整条线被最终的^.*?$捕获,但我确信应该有一个更好的方式):

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 2012-03-01 10:06:54

+0

我认为面向行的正则表达式是一种超出我的能力的神奇方式。但肯定有一定的办法。我猜... – heltonbiker 2012-03-01 13:28:07

+0

面向行的并不意味着什么:只需循环遍历文档并在一行内做简单的替换。换句话说,传统的正则表达式用法。我相信你很好! – alexis 2012-03-01 14:10:43

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 2012-02-23 10:36:04

+0

没错。切片符号对绝对线数非常有用,但我正在问其余的问题。 – alexis 2012-02-23 11:05:13

+0

谢谢,当我需要混合多行表达式时,这就是我最终做的事情。但根据模式分组成组只是我需要的一件事。 – alexis 2012-02-23 13:11:34

0

我不认为有一个简单的方法来做到这一点在Python。

但也有不同的方法,你可以遵循:

  • 逐行读取文件中的行,并激活只有当你需要搜索。
    这样做的优点是只读取一次文件,但它在当时只能读取一行文件。

  • itertools.islice()切片文件,并在那里搜索你的模式。
    你必须为每个模式重新读取文件,但它很容易实现。

  • 使用mmap
    如果你的文件不是很大,并且你有多个模式可以查找,我会选择这个模式。

编辑:如果你有兴趣在迭代器工具,itertools.takewhile()有智能拉姆达可能做的工作。

声明:我对sed一无所知。

+0

感谢您的指点。我不是在说千兆字节,所以一个适用于字符串列表的解决方案会很好。我怀疑有迭代器可以做这样的事情,并且在做它时做咖啡。我希望这里有人提出一个干净的方法。 – alexis 2012-02-23 11:11:13

+0

@alexis:我担心*“我希望这里有人提出一个干净的方法”*不会让你走得太远,特别是在这里。无论如何,我已经用'itertools.takewhile()'的引用更新了我的答案。 – 2012-02-23 11:36:33

+0

谢谢,Rik。我并不是在懒惰,只是因为我了解迭代器(包括takewhile),而且我发现它们对于这类任务来说非常笨拙。我承认我对于使用那些花哨的人没有什么经验,这就是为什么我要求更具体的指导。我澄清了这个问题,希望能够让我的目标更清晰。 – alexis 2012-02-23 12:36:33

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 2012-02-23 11:06:47

+0

大部分代码是您导入的模块。这不是一个箍。它是用Python编写的SED版本。你认为如何避免SED的复杂性?这很复杂。而你正在谈论SED的一些最复杂的功能。你想在哪里显示自己的复杂性?它必须在某个地方。 – 2012-02-23 11:15:11

+0

感谢您的努力,@S。你是对的,这些都是sed的强大功能。但是python很好地封装了许多复杂的接口 - 只是不是这个。所以要回答你的问题,我希望有人知道一个语言结构,或一个模块,我不必自己写,这将使我的生活更轻松。 – alexis 2012-02-23 12:49:27

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对手。

+0

这看起来很有前途,谢谢!但是当这些范围匹配多次时,pattern2应该在pattern1之后找到第一个匹配项。 (我知道,我没有在这个问题中拼出来)。此代码只会查找每个模式的第一个匹配项,而不管顺序如何。 – alexis 2012-02-23 13:01:46