使用Python删除文件中的最后一行

41

如何使用Python删除文件的最后一行?

输入文件示例:

hello
world
foo
bar

输出文件示例:

hello
world
foo

我已经创建了下面的代码来查找文件中的行数,但是我不知道如何删除特定的行号。

    try:
        file = open("file")
    except IOError:
        print "Failed to read file."
    countLines = len(file.readlines())

2
你是想从磁盘上实际删除文件中的行吗?如果是这样,请确保您理解文件系统的角度来看,文件没有“行”。行是程序员和程序的约定。您所看到的“行”实际上是在许多其他字节中间的一系列字节。要删除最后一行,您可以将文件截断为该行中第一个字符对应的字节。这并不难(您只需要找到它),但如果涉及的文件不是很大,则没有太多意义。 - Peter Hansen
如果最后一行是空行呢? - FogleBird
最后一行不是空白行。我使用另一个Python代码片段(来自谷歌)删除了所有空白行。 - torger
文件中不包含空行。上面的示例就是你要查看的内容,没有其他的了。最后一行是我需要删除的。为什么要如此傲慢?我几乎已经通过Strawberry的答案解决了问题。 - torger
所涉及的文件不在内存中 - 就像上面一样。 - torger
我的问题中没有轻蔑的意思...只是困惑,也许有点怀疑你是否以明智的方式进行此操作。是提到了空行删除。如果文件已经在内存中,那么它不是一个文件,而是一个字符串列表。如果你已经在使用Python处理这个“文件”来删除空行,并且这是完全独立的步骤,那么你会浪费两次处理这些数据的时间。这些都是简单的事实,但如果你不需要帮助的话,我现在就停止。 - Peter Hansen
10个回答

86

由于我经常处理几个GB大小的文件,像答案中提到的那样循环遍历对我没有用。我使用的解决方案是:

with open(sys.argv[1], "r+", encoding = "utf-8") as file:

    # Move the pointer (similar to a cursor in a text editor) to the end of the file
    file.seek(0, os.SEEK_END)

    # This code means the following code skips the very last character in the file -
    # i.e. in the case the last line is null we delete the last line
    # and the penultimate one
    pos = file.tell() - 1

    # Read each character in the file one at a time from the penultimate
    # character going backwards, searching for a newline character
    # If we find a new line, exit the search
    while pos > 0 and file.read(1) != "\n":
        pos -= 1
        file.seek(pos, os.SEEK_SET)

    # So long as we're not at the start of the file, delete all the characters ahead
    # of this position
    if pos > 0:
        file.seek(pos, os.SEEK_SET)
        file.truncate()

4
这是最佳答案。使用“with”语句以节省一行代码 :) - cppython
6
在使用 Py3 处理在 Mac 和 Windows 上都使用过的文件时,我遇到了一些兼容性问题,因为在内部 Mac 使用不同于 Windows(使用回车符和换行符两个字符)的行分隔符。解决办法是以二进制读取模式("rb+")打开文件,并查找二进制换行符 b"\n"。 - JrtPec
如果你用"a+"而不是"r+"打开文件,你可以跳过file.seek(0, os.SEEK_END)吗? - TheLizzard

23

您可以使用上面的代码,然后:

lines = file.readlines()
lines = lines[:-1]
这将给你一个包含除最后一行外的所有行的数组。

7
对于大文件,比如数千行,这个能有效工作吗? - torger
3
对于大于一两兆字节的文件,它可能无法正常工作。这取决于您对“正常”定义的理解。对于几千行的桌面应用程序而言,它应该是完全正常的。 - Paul McMillan
@torger 一个选择是使用os.system("sed '$d' file")来运行sed,在处理大文件和一般处理时,使用二进制文件会更快。截断文件似乎是最快的方法。无论如何,这个问题有很多有用的选项 :) +1 对这个问题。 - m3nda
这会从头到尾读取完整文件吗? - alper
@alper 是的,在这个例子中,它会将所有行读入内存中的一个数组。 - Martin
显示剩余3条评论

11

这不是Python语言,但如果你只需要执行这个任务的话,Python并不是最适合的工具。你可以使用标准的*nix实用程序head来完成。


head -n-1 filename > newfile

这将复制除了文件名的最后一行以外的所有内容到新文件中。


我想保持跨平台性 - 因此在问题中提到了通过Python。 - torger
11
这在Mac OSX上无法使用:head: illegal line count -- -1。 - Emil Stenström
喜欢它,简单又好用。我可以接受Linux的解决方案。 :D - Matthew
1
我怀疑使用seek的Python版本对内存的需求较小,因此更适合处理非常大的文件,而head是一个不错的一行代码,但涉及读取和复制几乎整个文件。 - Marie Hoffmann

7
假设您需要使用Python来完成此操作,并且您的文件足够大,不能仅使用列表分片进行操作,您可以在文件上进行一次遍历来完成此操作:
last_line = None
for line in file:
    if last_line:
        print last_line # or write to a file, call a function, etc.
    last_line = line

这不是世界上最优美的代码,但它能完成工作。

基本上,它通过 last_line 变量缓冲文件中的每一行,每次迭代输出前一次迭代的行。


5

这是我为Linux用户提供的解决方案:

import os 
file_path = 'test.txt'
os.system('sed -i "$ d" {0}'.format(file_path))

无需在 Python 中读取和迭代文件。

你如何使用这个来删除文件的“最后n行”? - Hossein Kalbasi

3
在支持 file.truncate() 的系统上,您可以这样做:
file = open('file.txt', 'rb')
pos = next = 0
for line in file:
  pos = next # position of beginning of this line
  next += len(line) # compute position of beginning of next line
file = open('file.txt', 'ab')
file.truncate(pos)

根据我的测试,当按行读取时,file.tell()不起作用,可能是由于缓冲区混淆了它。这就是为什么要通过累加行的长度来确定位置。请注意,这仅适用于行分隔符以'\n'结尾的系统。


在使用多个字符作为“行尾”(如Windows)的平台上非常危险。 - Peter Hansen
好的观点。(这实际上是我最初想使用tell()的原因,但它不起作用。)在这种情况下,以二进制模式打开文件应该可以解决问题。 - Laurence Gonsalves
我也会选择截断,特别是对于大文件。 - alexis

1

这里有一个更通用的内存高效解决方案,允许跳过最后的'n'行(就像head命令):

import collections, fileinput
def head(filename, lines_to_delete=1):
    queue = collections.deque()
    lines_to_delete = max(0, lines_to_delete) 
    for line in fileinput.input(filename, inplace=True, backup='.bak'):
        queue.append(line)
        if lines_to_delete == 0:
            print queue.popleft(),
        else:
            lines_to_delete -= 1
    queue.clear()

1
受之前的帖子启发,我提出以下观点:
with open('file_name', 'r+') as f:
  f.seek(0, os.SEEK_END) 
  while f.tell() and f.read(1) != '\n':
    f.seek(-2, os.SEEK_CUR)
  f.truncate()

0

这里是另一种方法,不需要将整个文件读入内存

p=""
f=open("file")
for line in f:
    line=line.strip()
    print p
    p=line
f.close()

0

虽然我没有测试过它(请不要因此而讨厌我),但我相信有一种更快的方法。这更像是C语言的解决方案,但在Python中也很可能实现。它也不是Pythonic的,只是一种理论上的想法。

首先,你需要知道文件的编码方式。将一个变量设置为该编码方式中每个字符使用的字节数(ASCII中为1个字节)。我们可以称之为CHARsize。对于ASCII文件,这个值通常为1个字节。

然后获取文件的大小,并将其设置为FILEsize

假设你已经拥有了文件的地址(在内存中)FILEadd

FILEsize加到FILEadd中。

向后移动(每次增加-1***CHARsize**),并测试每个CHARsize字节是否为\n(或者你的系统使用的任何换行符)。当你到达第一个\n时,你现在就知道了文件第一行的开头位置。将\n替换为\x1a(26,EOF的ASCII码,或者根据你的系统/编码方式替换为其他字符)。

按照您的需要进行清理(更改文件大小,触摸文件等)。

如果这个方法像我预想的那样有效,您将会节省很多时间,因为您不需要从头开始读取整个文件,而是从结尾开始读取。


请注意,整个 \x1a(也称为 ^Z 或 CTRL-Z 或 EOF,在 ASCII 中实际上是 SUB)已经完全过时了... 现在很少有文本文件以实际的 SUB 字符结尾,即使这些文件也基本上限于 Windows/DOS 系统。还有 CP/M 系统。 - Peter Hansen
啊,好观点 - 我不确定它是否仍然被广泛使用...还有其他方法可以挽救这种技术吗? - Isaac

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