簡體   English   中英

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

[英]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.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)

至少對於注釋,只需使用一個真正的解析器。

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM