在 Python 中搜索和替换文件中的一行
Posted
技术标签:
【中文标题】在 Python 中搜索和替换文件中的一行【英文标题】:Search and replace a line in a file in Python 【发布时间】:2010-09-07 12:49:06 【问题描述】:我想遍历文本文件的内容并在某些行上进行搜索和替换,然后将结果写回文件。我可以先将整个文件加载到内存中,然后再将其写回,但这可能不是最好的方法。
在以下代码中最好的方法是什么?
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
【问题讨论】:
【参考方案1】:最短的方法可能是使用fileinput module。例如,以下将行号原地添加到文件中:
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
这里发生的是:
-
原始文件已移至备份文件
标准输出被重定向到循环内的原始文件
因此任何
print
语句都会写回到原始文件中
fileinput
有更多的花里胡哨。例如,它可用于自动操作sys.args[1:]
中的所有文件,而无需显式地迭代它们。从 Python 3.2 开始,它还提供了一个方便的上下文管理器,可在 with
语句中使用。
虽然fileinput
非常适合一次性脚本,但在实际代码中使用它时我会保持警惕,因为不可否认,它的可读性或熟悉度都不是很高。在实际(生产)代码中,值得多花几行代码来使过程明确,从而使代码可读。
有两种选择:
-
该文件并不过大,您可以将其全部读入内存。然后关闭文件,以写入模式重新打开,将修改后的内容写回去。
文件太大,无法存储在内存中;您可以将其移至临时文件并打开它,逐行读取,然后写回原始文件。请注意,这需要两倍的存储空间。
【讨论】:
我知道它只有两行,但我不认为代码本身很有表现力。因为如果你想一想,如果你不知道这个函数,那么关于发生了什么的线索很少。打印行号和行不一样写...如果你明白我的要点... 我同意。如何使用 fileinput 写入文件? 这确实写入文件。它将标准输出重定向到文件。看看docs 这里的关键位是 print 语句末尾的逗号:它隐藏了 print 语句添加另一个换行符(因为 line 已经有一个)。不过,这根本不是很明显(这就是 Python 3 改变了这种语法的原因,幸运的是)。 请注意,当您为文件提供打开钩子时,这不起作用,例如当您尝试读取/写入 UTF-16 编码文件时。【参考方案2】:我想这样的事情应该这样做。它基本上将内容写入新文件并用新文件替换旧文件:
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)
【讨论】:
只是一个小评论:file
正在隐藏同名的预定义类。
这段代码改变了原始文件的权限。如何保留原有权限?
fh 有什么意义,你在 close 调用中使用它,但我没有看到创建文件只是为了关闭它的意义......
@Wicelo 您需要关闭它以防止文件描述符泄漏。这是一个不错的解释:logilab.org/17873
是的,我发现 mkstemp()
正在返回一个 2 元组和 (fh, abs_path) = fh, abs_path
,当我问这个问题时我不知道。【参考方案3】:
这是另一个经过测试的示例,将匹配搜索和替换模式:
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.")
【讨论】:
示例使用提供了正则表达式,但searchExp in line
和line.replace
都不是正则表达式操作。示例使用肯定是错误的。
你可以写line = line.replace(searchExp, replaceExpr)
而不是if searchExp in line: line = line.replace(searchExp, replaceExpr)
。不产生异常,该行保持不变。
对我来说也很完美。我遇到过许多与此非常相似的其他示例,但诀窍是使用sys.stdout.write(line)
。再次感谢!
如果我使用它,我的文件会变成空白。有什么想法吗?【参考方案4】:
这应该可以工作:(就地编辑)
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"),
【讨论】:
+1。此外,如果您收到 RuntimeError: input() already active 然后调用 fileinput.close() 注意files
应该是一个包含文件名的字符串not a file object。
print 添加一个可能已经存在的换行符。为避免这种情况,请在替换结束时添加 .rstrip()
在 input() 中使用 files arg,它可以是 fileinput.input(inplace=1) 并将脚本调用为 > python replace.py myfiles*.txt【参考方案5】:
基于 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()
【讨论】:
您可能希望在打开文件时使用rb
和wb
属性,因为这将保留原始行结尾
在 Python 3 中,不能将 'wb' 和 'rb' 与 're' 一起使用。它会给出错误“TypeError: cannot use a string pattern on a bytes-like object”【参考方案6】:
正如 lassevk 建议的那样,随手写出新文件,这里是一些示例代码:
fin = open("a.txt")
fout = open("b.txt", "wt")
for line in fin:
fout.write( line.replace('foo', 'bar') )
fin.close()
fout.close()
【讨论】:
【参考方案7】:如果您想要一个用其他文本替换 任何 文本的通用函数,这可能是最好的方法,特别是如果您是正则表达式的粉丝:
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 )
【讨论】:
【参考方案8】:一种更 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)
您可以找到完整的 sn-p here。
【讨论】:
在 Python >=3.1 中你可以打开two context managers on the same line。【参考方案9】:fileinput
非常简单,正如之前的答案所述:
import fileinput
def replace_in_file(file_path, search_text, new_text):
with fileinput.input(file_path, inplace=True) as f:
for line in f:
new_line = line.replace(search_text, new_text)
print(new_line, end='')
解释:
fileinput
可以接受多个文件,但我更喜欢在处理每个文件后立即关闭它。所以在with
语句中放置单个file_path
。
print
语句在 inplace=True
时不会打印任何内容,因为 STDOUT
正在被转发到原始文件。
end=''
在print
语句中是为了消除中间空白新行。
可以如下使用:
file_path = '/path/to/my/file'
replace_in_file(file_path, 'old-text', 'new-text')
【讨论】:
如果新文本中包含日文字形等特殊字符,字符将无法正常显示。它们的写法类似于\xe8
。【参考方案10】:
创建一个新文件,将行从旧文件复制到新文件,并在将行写入新文件之前进行替换。
【讨论】:
【参考方案11】:扩展@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)
【讨论】:
是否会在新文件中保留旧文件的权限?【参考方案12】:使用 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()
【讨论】:
你应该在for循环之外编译正则表达式,否则会浪费性能【参考方案13】:如果您在下面删除缩进,它将在多行中搜索和替换。 例如,请参见下文。
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)
【讨论】:
这段 Python 代码的格式看起来不太正确...(我试图修复,但不确定是什么意思)以上是关于在 Python 中搜索和替换文件中的一行的主要内容,如果未能解决你的问题,请参考以下文章