如何修改文本文件?

231

我正在使用Python,并希望将一个字符串插入到文本文件中,而不需要删除或复制该文件。 我应该如何做到这一点?


1
你可以查看Alex Martelli在这里的回答。 - Alok
1
http://stackoverflow.com/a/4358169/538284 - Omid Raha
可能是Python中在CSV文件的最顶行写入内容的重复问题。 - Ani Menon
@Ani,另一篇帖子确实是在文本文件的指定位置插入行的重复,而且这里有明确的答案。为什么不在这里添加你的答案呢?被接受的答案并不是一个好问题的要求。 - Bhargav Rao
@BhargavRao 投票已撤回。我应该找到那个重复的! - Ani Menon
参见:https://dev59.com/cXM_5IYBdhLWcg3wjj-r#1325927 - dreftymac
8个回答

162

很不幸,没有办法在不重写文件的情况下将内容插入到文件的中间。正如之前的帖子所指出的那样,您可以使用seek在文件末尾追加或覆盖其中一部分内容,但如果您想在开头或中间添加东西,您将不得不重写它。

这是一个操作系统的问题,不是Python的问题。所有语言都是一样的。

我通常会从文件中读取,进行修改并将其写入名为myfile.txt.tmp或类似名称的新文件中。这比将整个文件读入内存更好,因为文件可能过大。完成临时文件后,我将其重命名为原始文件名。

这是一种很好、安全的方法,因为如果文件写入由于任何原因崩溃或中止,您仍然有未受影响的原始文件。


4
像awk/sed这样的Unix工具在它们的代码中是否做类似的事情? - Manish Gill
1
这并不是所有语言都一样的。在ActionScript中:fileStream.openAsync(filename,FileMode.UPDATE);然后我可以随意进入文件并更改任何内容。 - AndrewBenjamin
3
你知道ActionScript正在做什么系统调用吗?openAsync调用后是否有可能读取文件并写入新文件? - AlexLordThorsen
1
@Rawrgulmuffins 我不知道。但是,我知道它不会将整个文件读入内存,因为我已经使用它来处理几个GB的文件大小。我猜想它与使用C# streamwriter写入的方式相同。我认为Python是一个快速完成小事情的工具,而不是大规模开发和文件操作的工具。 - AndrewBenjamin
5
@AndrewBenjamin,用户并不是在询问如何在文件中查找并更改内容(我所知道的每种语言都可以做到这一点);他正在询问如何插入文本,这与简单地更改/覆盖文件中已有的内容不同。也许在实际应用中有所不同,但是我在ActionScript API中找不到任何迹象表明它在这方面的行为与其他任何语言有所不同。 - eestrada
1
也许我误解了。我以为它是同一件事 - FileStream.writeUTF - AndrewBenjamin

140

取决于你想要做什么。 要追加,可以使用 "a" 打开:

 with open("foo.txt", "a") as f:
     f.write("new line\n")

如果您想要在文件中前置一些内容,您需要先从文件中读取:

with open("foo.txt", "r+") as f:
     old = f.read() # read everything in the file
     f.seek(0) # rewind
     f.write("new line\n" + old) # write the new line before

11
只需要做一个小小的补充,在Python 2.5中使用"with"语句,您需要添加"from future import with_statement"。除此之外,使用with语句打开文件肯定比手动关闭更可读且不易出错。 - Alexander Kojevnikov
2
当使用inline=True参数时,您可以考虑使用fileinput辅助库,它可以很好地处理脏的打开/读取/修改/写入/替换例程。示例在这里:https://dev59.com/IEnSa4cB1Zd3GeqPRtks#2363893 - mikegreenberg
7
这不是我使用的风格,D.Rosado,但是在使用with风格时,我认为您不需要手动关闭。with会跟踪它创建的资源。 - Chris
10
不需要手动关闭文件。这就是在此处使用“with”的全部意义。(实际上,Python 会在文件对象被垃圾回收时立即执行此操作,而在CPython中,这发生在与其绑定的名称超出范围时...但其他实现不会这样做,而且CPython也可能停止这样做,因此建议使用“with”) - Jürgen A. Erhard
我们可以替换其中的一些字符吗? - alper
请注意,如果新文本比原始文件小,您必须在f.seek后调用f.truncate();否则,最终会在文件末尾得到原始文件中的随机垃圾内容。 - undefined

84
Python标准库的fileinput模块可以使用inplace=1参数来就地重写文件:
import sys
import fileinput

# replace all occurrences of 'sit' with 'SIT' and insert a line after the 5th
for i, line in enumerate(fileinput.input('lorem_ipsum.txt', inplace=1)):
    sys.stdout.write(line.replace('sit', 'SIT'))  # replace 'sit' and write
    if i == 4: sys.stdout.write('\n')  # write a blank line after the 5th line

2
这在Python3中预计如何工作?我刚刚将一个应用程序从Python移植到Python3,并且我根本无法使其正常工作。'line'变量是bytes类型,我尝试将其解码为Unicode,然后修改它,再将其编码回bytes,但它就是不能正常工作。它引发了一些我记不起来的异常。人们是否成功地在Python3中使用fileinput inplace=1? - robru
4
@Robru:这里是Python 3代码 - jfs
14
没问题,因为你先在一个不重要的文件上测试过了,对吧? - Paula Livingstone

34

在原地重写文件通常是通过保存带有修改名称的旧副本来完成的。Unix中添加了~以标记旧文件。Windows用户会做各种各样的事情-添加.bak或.old-或者完全重命名文件,或将~放在名称的前面。

import shutil
shutil.move(afile, afile + "~")

destination= open(aFile, "w")
source= open(aFile + "~", "r")
for line in source:
    destination.write(line)
    if <some condition>:
        destination.write(<some additional line> + "\n")

source.close()
destination.close()

你可以使用以下方法,而不是shutil

import os
os.rename(aFile, aFile + "~")

1
看起来不错。不知道使用.readlines()是否比迭代源更好? - bozdoz
2
@bozdoz:迭代更好,因为readlines会读取整个文件。对于大文件不好。当然,这是建立在您可以以这种本地化的方式进行修改的前提下。有时候你不能这样做,或者你的代码变得更加复杂。 - Jürgen A. Erhard
@S.Lott: os.rename(aFile, aFile + "~") 会修改源文件的名称,而不是创建一个副本。 - Patapoom

15

Python的mmap模块可以让您向文件中插入内容。以下示例展示了如何在Unix系统中完成此操作(Windows mmap可能不同)。请注意,这并未处理所有错误情况,您可能会破坏或丢失原始文件。此外,它无法处理Unicode字符串。

import os
from mmap import mmap

def insert(filename, str, pos):
    if len(str) < 1:
        # nothing to insert
        return

    f = open(filename, 'r+')
    m = mmap(f.fileno(), os.path.getsize(filename))
    origSize = m.size()

    # or this could be an error
    if pos > origSize:
        pos = origSize
    elif pos < 0:
        pos = 0

    m.resize(origSize + len(str))
    m[pos+len(str):] = m[pos:origSize]
    m[pos:pos+len(str)] = str
    m.close()
    f.close()

也可以在以'r+'模式打开的文件中进行此操作,但这样做不够方便,效率也不高,因为您需要从插入位置到EOF读取并临时存储文件的内容-可能非常大。


14

正如Adam所提到的,您在决定处理方式之前必须考虑系统的限制,无论是将所有内容都读入内存还是替换部分并重新编写。

如果您正在处理小文件或没有内存问题,这可能会有所帮助:

选项1) 将整个文件读入内存,在整行或部分行上执行正则表达式替换,并将其替换为该行加上额外的行。您需要确保“中间行”在文件中是唯一的,或者如果每行都有时间戳,则这应该是相当可靠的。

# open file with r+b (allow write and binary mode)
f = open("file.log", 'r+b')   
# read entire content of file into memory
f_content = f.read()
# basically match middle line and replace it with itself and the extra line
f_content = re.sub(r'(middle line)', r'\1\nnew line', f_content)
# return pointer to top of file so we can re-write the content with replaced string
f.seek(0)
# clear file content 
f.truncate()
# re-write the content with the updated content
f.write(f_content)
# close file
f.close()

选项2) 找出中间行,然后用该行加上额外的一行进行替换。

# open file with r+b (allow write and binary mode)
f = open("file.log" , 'r+b')   
# get array of lines
f_content = f.readlines()
# get middle line
middle_line = len(f_content)/2
# overwrite middle line
f_content[middle_line] += "\nnew line"
# return pointer to top of file so we can re-write the content with replaced string
f.seek(0)
# clear file content 
f.truncate()
# re-write the content with the updated content
f.write(''.join(f_content))
# close file
f.close()

1
写了一个小类,以清晰的方式执行此操作。
import tempfile

class FileModifierError(Exception):
    pass

class FileModifier(object):

    def __init__(self, fname):
        self.__write_dict = {}
        self.__filename = fname
        self.__tempfile = tempfile.TemporaryFile()
        with open(fname, 'rb') as fp:
            for line in fp:
                self.__tempfile.write(line)
        self.__tempfile.seek(0)

    def write(self, s, line_number = 'END'):
        if line_number != 'END' and not isinstance(line_number, (int, float)):
            raise FileModifierError("Line number %s is not a valid number" % line_number)
        try:
            self.__write_dict[line_number].append(s)
        except KeyError:
            self.__write_dict[line_number] = [s]

    def writeline(self, s, line_number = 'END'):
        self.write('%s\n' % s, line_number)

    def writelines(self, s, line_number = 'END'):
        for ln in s:
            self.writeline(s, line_number)

    def __popline(self, index, fp):
        try:
            ilines = self.__write_dict.pop(index)
            for line in ilines:
                fp.write(line)
        except KeyError:
            pass

    def close(self):
        self.__exit__(None, None, None)

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        with open(self.__filename,'w') as fp:
            for index, line in enumerate(self.__tempfile.readlines()):
                self.__popline(index, fp)
                fp.write(line)
            for index in sorted(self.__write_dict):
                for line in self.__write_dict[index]:
                    fp.write(line)
        self.__tempfile.close()

然后你可以这样使用它:
with FileModifier(filename) as fp:
    fp.writeline("String 1", 0)
    fp.writeline("String 2", 20)
    fp.writeline("String 3")  # To write at the end of the file

这对我个人来说不起作用,它会将文本添加到文件中,但首先会删除所有内容! - Bret Hawker
确实,这根本不起作用。很遗憾,因为它看起来是个好主意。 - Mario Krušelj

-2
如果您了解一些Unix,可以尝试以下操作:
注意:$表示命令提示符
假设您有一个名为my_data.txt的文件,其内容如下:
$ cat my_data.txt
This is a data file
with all of my data in it.

然后使用os模块,您可以使用通常的sed命令。

import os

# Identifiers used are:
my_data_file = "my_data.txt"
command = "sed -i 's/all/none/' my_data.txt"

# Execute the command
os.system(command)

如果你还不知道sed,请去了解一下,它非常有用。

3
这并不符合Pythonic的风格。 - DarkSuniuM

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