344

我想遍历文本文件的内容并在某些行上进行搜索和替换,然后将结果写回文件。我可以先将整个文件加载到内存中,然后再将其写回,但这可能不是最好的方法。

在以下代码中,最好的方法是什么?

f = open(file)
for line in f:
    if line.contains('foo'):
        newline = line.replace('foo', 'bar')
        # how to write this newline back to the file
4

13 回答 13

295

最短的方法可能是使用fileinput 模块。例如,以下将行号添加到文件中,就地:

import fileinput

for line in fileinput.input("test.txt", inplace=True):
    print('{} {}'.format(fileinput.filelineno(), line), end='') # for Python 3
    # print "%d: %s" % (fileinput.filelineno(), line), # for Python 2

这里发生的是:

  1. 原始文件移动到备份文件
  2. 标准输出被重定向到循环内的原始文件
  3. 因此,任何print语句都写回原始文件

fileinput有更多的花里胡哨。例如,它可用于自动操作 中的所有文件sys.args[1:],而无需显式地迭代它们。从 Python 3.2 开始,它还提供了一个方便的上下文管理器以在with语句中使用。


虽然fileinput非常适合一次性脚本,但我会谨慎地在实际代码中使用它,因为不可否认它不是很易读或不熟悉。在实际(生产)代码中,值得多花几行代码来使过程明确,从而使代码可读。

有两种选择:

  1. 该文件不是太大,您可以将其完全读取到内存中。然后关闭文件,以写入模式重新打开,将修改后的内容写回去。
  2. 文件太大,无法存储在内存中;您可以将其移至临时文件并打开它,逐行读取,然后写回原始文件。请注意,这需要两倍的存储空间。
于 2008-11-14T15:47:40.300 回答
221

我想这样的事情应该这样做。它基本上将内容写入新文件并用新文件替换旧文件:

from tempfile import mkstemp
from shutil import move, copymode
from os import fdopen, remove

def replace(file_path, pattern, subst):
    #Create temp file
    fh, abs_path = mkstemp()
    with fdopen(fh,'w') as new_file:
        with open(file_path) as old_file:
            for line in old_file:
                new_file.write(line.replace(pattern, subst))
    #Copy the file permissions from the old file to the new file
    copymode(file_path, abs_path)
    #Remove original file
    remove(file_path)
    #Move new file
    move(abs_path, file_path)
于 2008-09-02T09:42:21.037 回答
89

这是另一个经过测试的示例,将匹配搜索和替换模式:

import fileinput
import sys

def replaceAll(file,searchExp,replaceExp):
    for line in fileinput.input(file, inplace=1):
        if searchExp in line:
            line = line.replace(searchExp,replaceExp)
        sys.stdout.write(line)

示例使用:

replaceAll("/fooBar.txt","Hello\sWorld!$","Goodbye\sWorld.")
于 2008-11-24T19:02:28.313 回答
68

这应该有效:(就地编辑)

import fileinput

# Does a list of files, and
# redirects STDOUT to the file in question
for line in fileinput.input(files, inplace = 1): 
      print line.replace("foo", "bar"),
于 2009-09-07T10:07:25.203 回答
24

根据 Thomas Watnedal 的回答。但是,这并不能准确回答原始问题的逐行部分。该功能仍然可以逐行替换

此实现在不使用临时文件的情况下替换文件内容,因此文件权限保持不变。

也 re.sub 而不是替换,只允许正则表达式替换而不是纯文本替换。

将文件作为单个字符串而不是逐行读取允许多行匹配和替换。

import re

def replace(file, pattern, subst):
    # Read contents from file as a single string
    file_handle = open(file, 'r')
    file_string = file_handle.read()
    file_handle.close()

    # Use RE package to allow for replacement (also allowing for (multiline) REGEX)
    file_string = (re.sub(pattern, subst, file_string))

    # Write contents to file.
    # Using mode 'w' truncates the file.
    file_handle = open(file, 'w')
    file_handle.write(file_string)
    file_handle.close()
于 2012-11-30T08:51:17.913 回答
18

正如 lassevk 建议的那样,随时写出新文件,这是一些示例代码:

fin = open("a.txt")
fout = open("b.txt", "wt")
for line in fin:
    fout.write( line.replace('foo', 'bar') )
fin.close()
fout.close()
于 2008-09-02T09:42:57.213 回答
13

一种更 Pythonic 的方式是使用上下文管理器,如下面的代码:

from tempfile import mkstemp
from shutil import move
from os import remove

def replace(source_file_path, pattern, substring):
    fh, target_file_path = mkstemp()
    with open(target_file_path, 'w') as target_file:
        with open(source_file_path, 'r') as source_file:
            for line in source_file:
                target_file.write(line.replace(pattern, substring))
    remove(source_file_path)
    move(target_file_path, source_file_path)

你可以在这里找到完整的片段。

于 2013-09-07T18:39:01.017 回答
13

如果您想要一个用其他文本替换任何文本的通用函数,这可能是最好的方法,特别是如果您是正则表达式的粉丝:

import re
def replace( filePath, text, subs, flags=0 ):
    with open( filePath, "r+" ) as file:
        fileContents = file.read()
        textPattern = re.compile( re.escape( text ), flags )
        fileContents = textPattern.sub( subs, fileContents )
        file.seek( 0 )
        file.truncate()
        file.write( fileContents )
于 2014-02-18T14:43:02.213 回答
10

fileinput正如之前的答案所述,非常简单:

import fileinput

def replace_in_file(file_path, search_text, new_text):
    with fileinput.input(file_path, inplace=True) as file:
        for line in file:
            new_line = line.replace(search_text, new_text)
            print(new_line, end='')

解释:

  • fileinput可以接受多个文件,但我更喜欢在处理每个文件后立即关闭它。所以file_pathwith声明中单独放置。
  • print时语句不打印任何内容inplace=True,因为STDOUT正在转发到原始文件。
  • end=''inprint语句是消除中间空白新行。

您可以按如下方式使用它:

file_path = '/path/to/my/file'
replace_in_file(file_path, 'old-text', 'new-text')
于 2019-10-03T10:37:58.693 回答
4

创建一个新文件,将行从旧文件复制到新文件,并在将行写入新文件之前进行替换。

于 2008-09-02T09:24:20.953 回答
4

扩展@Kiran的答案,我同意它更简洁和Pythonic,这添加了编解码器以支持UTF-8的读写:

import codecs 

from tempfile import mkstemp
from shutil import move
from os import remove


def replace(source_file_path, pattern, substring):
    fh, target_file_path = mkstemp()

    with codecs.open(target_file_path, 'w', 'utf-8') as target_file:
        with codecs.open(source_file_path, 'r', 'utf-8') as source_file:
            for line in source_file:
                target_file.write(line.replace(pattern, substring))
    remove(source_file_path)
    move(target_file_path, source_file_path)
于 2014-05-02T11:15:57.690 回答
2

使用 hamishmcn 的答案作为模板,我能够在文件中搜索与我的正则表达式匹配的行并将其替换为空字符串。

import re 

fin = open("in.txt", 'r') # in file
fout = open("out.txt", 'w') # out file
for line in fin:
    p = re.compile('[-][0-9]*[.][0-9]*[,]|[-][0-9]*[,]') # pattern
    newline = p.sub('',line) # replace matching strings with empty string
    print newline
    fout.write(newline)
fin.close()
fout.close()
于 2014-04-17T02:13:32.717 回答
0

如果您在下面删除缩进,它将在多行中搜索和替换。例如,请参见下文。

def replace(file, pattern, subst):
    #Create temp file
    fh, abs_path = mkstemp()
    print fh, abs_path
    new_file = open(abs_path,'w')
    old_file = open(file)
    for line in old_file:
        new_file.write(line.replace(pattern, subst))
    #close temp file
    new_file.close()
    close(fh)
    old_file.close()
    #Remove original file
    remove(file)
    #Move new file
    move(abs_path, file)
于 2012-08-02T19:12:12.180 回答