如何用多行正则表达式完成删除整行?

时间:2019-03-04 22:17:34

标签: python regex line newline

我要删除此多行字符串中包含b的所有行:

aba\n
aaa\n
aba\n
aaa\n
aba[\n\n - optional]

请注意,文件不一定由换行符终止,或者在我想保留的末尾可能会有额外的换行符

这是预期的输出:

aaa\n
aaa[\n\n - as in the input file]

这是我尝试过的:

import re
String = "aba\naaa\naba\naaa\naba"
print(String)
print(re.sub(".*b.*", "", String))  # this one leaves three empty lines
print(re.sub(".*b.*\n", "", String))  # this one misses the last line
print(re.sub("\n.*b.*", "", String))  # this one misses the first line
print(re.sub(".*b.*\n?", "", String))  # this one leaves an empty last line
print(re.sub("\n?.*b.*", "", String))  # this one leaves an empty first line
print(re.sub("\n?.*b.*\n?", "", String))  # this one joins the two remaining lines

我也尝试过flags=re.M以及各种先行和后退,但主要问题似乎是:如何在匹配项中删除\n的第一个或最后一次出现字符串,取决于哪一个存在-如果两个都存在,则不行?

2 个答案:

答案 0 :(得分:3)

您可以使用正则表达式或非正则表达式方法:

import re
s = "aba\naaa\naba\naaa\naba"
print( "\n".join([st for st in s.splitlines() if 'b' not in st]) )
print( re.sub(r'^[^b\r\n]*b.*[\r\n]*', '', s, flags=re.M).strip() )

请参见Python demo

非正则表达式方法"\n".join([st for st in s.splitlines() if 'b' in st]),使用换行符分割字符串,过滤掉所有没有b的行,然后将这些行重新合并。

正则表达式方法涉及r'^[^b\r\n]b.*[\r\n]*'之类的模式:

  • ^-一行的开头
  • [^b\r\n]*-除CR,LF和b之外的0个或多个字符
  • b-一个b字符
  • .*-除换行符以外的任何0+字符
  • [\r\n]*-0个以上的CR或LF字符。

请注意,在此之后,您需要使用.strip()来消除字符串开头/结尾的多余空格。

单个正则表达式解决方案太麻烦了,我不建议在现实生活中使用它:

rx = r'(?:{0}(?:\n|$))+|(?:\n|^){0}'.format(r'[^b\n]*b.*')
print( re.sub(rx, '', s) )

请参见Python demo

模式看起来像(?:[^b\n]*b.*(?:\n|$))+|(?:\n|^)[^b\n]*b.*并且匹配

  • (?:[^b\n]*b.*(?:\n|$))+-重复1次或更多次
    • [^b\n]*-b和换行符以外的任意0+个字符
    • b.*-b和该行的其余部分(.*匹配除换行符以外的任何0+个字符)
    • (?:\n|$)-换行符或字符串结尾
  • |-或
    • (?:\n|^)-换行符或字符串开头
    • [^b\n]*b.*-一行至少包含一个b

答案 1 :(得分:1)

在调用re.sub()来删除其中带有b的行时,需要考虑以下三种情况:

  1. 模式后跟行尾字符(eol)
  2. 文本的最后一行(没有结尾的eol)
  3. 只有一行没有结尾的eol时

在第二种情况下,您要删除前面的eol字符以避免创建空行。如果有“ b”,则第三种情况将产生一个空字符串。

正则表达式的贪婪将引入第四种情况,因为不能有任何模式重叠。如果您的最后一行包含“ b”,而其前一行也包含“ b”,则案例#1将消耗前一行的eol字符,因此它不符合检测到最后一行的模式的条件(即eol,然后是文字结尾处的模式)。可以通过将连续匹配的行清除(案例1)作为一个组并将最后一行包括为该组的可选组件来解决。不管剩下的是尾随行(情况2),您要在其中删除前一个eol而不是后一个。

为了管理线条图案.*b.*的重复,您需要从两部分组成搜索图案:线条图案和多次使用它的列表图案。既然我们已经深入正则表达式,为什么不也使用re.sub()来做到这一点。

import re

LinePattern = "(.*b.*)"
ListPattern = "(Line\n)+(Line$)?|(\nLine$)|(^Line$)" # Case1|Case2|Case3
Pattern     = re.sub("Line",LinePattern,ListPattern)

String  = "aba\naaa\naba\naaa\naba"
cleaned = re.sub(Pattern,"",String)

注意:此技术也可以与其他分隔符一起使用(例如,用逗号代替eol),但是该字符需要从行模式中排除(例如,([^,]*b[^,]*)