在Python中搜索并替换文件中的一行

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

在下面的代码中,做到这一点的最佳方法是什么?

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
575438 次浏览

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

我想像这样的东西就可以了。它基本上将内容写入一个新文件,并用新文件替换旧文件:

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)

正如lassevk所建议的,在运行时写出新文件,下面是一些示例代码:

fin = open("a.txt")
fout = open("b.txt", "wt")
for line in fin:
fout.write( line.replace('foo', 'bar') )
fin.close()
fout.close()

最短的方法可能是使用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. 文件太大,无法存储在内存中;您可以将其移动到一个临时文件并打开它,逐行读取它,然后将其写回原始文件。注意,这需要两倍的存储空间。

这是另一个测试的例子,将匹配搜索&替换模式:

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.")

这应该工作:(就地编辑)

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"),

如果你删除缩进如下所示,它将在多行中搜索和替换。

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)

根据Thomas Watnedal的回答。 然而,这并没有准确地回答原始问题的行对行部分。函数仍然可以在行到行的基础上替换

此实现替换文件内容而不使用临时文件,因此文件权限保持不变。

此外,re.sub代替replace,允许正则表达式替换而不是纯文本替换。

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

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()

更python化的方法是使用上下文管理器,如下所示:

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)

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

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

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 )

使用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()

扩展@Kiran的回答,我认为它更简洁和python化,这增加了编解码器来支持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)

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_path放在with语句中。
  • print语句在inplace=True时不打印任何东西,因为STDOUT被转发到原始文件。
  • print语句中的end=''是消除中间空白换行。

你可以这样使用它:

file_path = '/path/to/my/file'
replace_in_file(file_path, 'old-text', 'new-text')