正则表达式去除换行符

14

我是Python的完全新手,但我遇到了一个正则表达式问题。我试图从文本文件中去掉每行末尾的换行符,但只有在它跟随一个小写字母[a-z]时。如果一行末尾是小写字母,则要将换行符/新行字符替换为一个空格。

以下是我目前的代码:

import re
import sys

textout = open("output.txt","w")
textblock = open(sys.argv[1]).read()
textout.write(re.sub("[a-z]\z","[a-z] ", textblock, re.MULTILINE) )
textout.close()

如果在正则表达式中没有 $^,则不需要使用标签 re.MULTILINE - eyquem
3个回答

25

尝试

re.sub(r"(?<=[a-z])\r?\n"," ", textblock)

\Z 只匹配字符串的结尾,即最后一个换行符之后,所以在这里它肯定不是你需要的东西。\z在Python正则表达式引擎中无法识别。

(?<=[a-z])是一个正向回顾断言,它检查当前位置前面的字符是否为小写ASCII字符。只有在满足条件时,正则表达式引擎才会尝试匹配换行符。

此外,始终使用原始字符串与正则表达式一起使用,可以更容易地处理反斜杠。


2
我会将\r?\n替换为[\r\n]+,以便也匹配单个的\r - ThiefMaster
@ThiefMaster:还有使用\r的Mac电脑吗?Python能在上面运行吗?我以为苹果已经放弃了在OS X中使用\r作为行尾符,但是我可能完全错了。 - Tim Pietzcker
希望不是这样,但你永远不知道有什么糟糕的文件存在 - 有太多的文件包含混合\n\r\n,所以我认为还是会存在一些\r文件。 - ThiefMaster
所以 [\r\n]{1,2} 就可以了,或者更好的是根据我的建议:(\n|\r\n?) 其中 \r 单独出现的情况是第三种可能需要测试。 - eyquem
@eyquem:肯定是第二个:(?:\n|\r\n?),否则你可能会意外删除两个相邻的Unix换行符。 - Tim Pietzcker
显示剩余5条评论

3

作为另一种选择,虽然需要更多的行,但我认为以下内容可能会更清晰,因为正则表达式更简单:

import re
import sys

with open(sys.argv[1]) as ifp:
    with open("output.txt", "w") as ofp:
        for line in ifp:
            if re.search('[a-z]$',line):
                ofp.write(line.rstrip("\n\r")+" ")
            else:
                ofp.write(line)

...并且避免将整个文件加载到字符串中。如果你想使用更少的行,但仍然避免使用正向后查找,可以这样做:

import re
import sys

with open(sys.argv[1]) as ifp:
    with open("output.txt", "w") as ofp:
        for line in ifp:
            ofp.write(re.sub('(?m)([a-z])[\r\n]+$','\\1 ',line))

正则表达式的部分内容如下:

  • (?m) [开启多行匹配模式]
  • ([a-z]) [匹配第一组为单个小写字母]
  • [\r\n]+ [匹配一个或多个回车符或换行符,以覆盖\n\r\n\r]
  • $ [匹配字符串的结尾]

如果匹配了该行,则会用\\1替换小写字母和行结尾,其中\\1表示小写字母后跟一个空格。


一行代码 ofp.write(re.sub("(?<=[a-z])(\n|\r\n?)"," ",line) 取代了四行代码 - eyquem
@eyquem:当然,但我的观点是避免使用正向后查找可能会使代码更易读,并且为此增加三行额外的代码也是值得的...好吧,我还是会添加另一个版本。 - Mark Longair

1

我的观点是避免使用正向回顾可能会使代码更易读

好的。尽管个人认为这并不会降低可读性。这是一个品味问题。

在您的编辑中:

  • 首先,(?m)是不必要的,因为for line in ifp:每次选择一行,所以每行字符串的末尾只有一个换行符

  • 其次,$放置在那里没有任何用处,因为它总是匹配字符串行的结尾。

无论如何,采纳您的观点,我找到了两种避免使用回顾断言的方法:

with open(sys.argv[1]) as ifp:
    with open("output.txt", "w") as ofp:
        for line in ifp:
            ante_newline,lower_last = re.match('(.*?([a-z])?$)',line).groups()
            ofp.write(ante_newline+' ' if lower_last else line)

并且

with open(sys.argv[1]) as ifp:
    with open("output.txt", "w") as ofp:
        for line in ifp:
            ofp.write(line.strip('\r\n')+' ' if re.search('[a-z]$',line) else line)

第二个更好: 只有一行, 简单匹配测试, 不需要使用 groups(), 自然逻辑

编辑: 我意识到这个第二段代码只是你的第一段代码用一行重写了, Longair


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接