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


292

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

在以下代码中,执行此操作的最佳方法是什么?

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

Answers:


191

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

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)

5
只是一个小小的评论:file隐藏了同名的预定义类。
ezdazuzena

4
此代码更改了原始文件的权限。如何保留原始权限?
nic

1
fh的意义是什么,您可以在close调用中使用它,但是我看不到仅仅为了关闭它而创建文件的意义……
Wicelo 2014年

2
@Wicelo您需要关闭它以防止文件描述符泄漏。这是一个不错的解释:logilab.org/17873
Thomas Watnedal 2014年

1
是的,我发现它mkstemp()返回一个2元组,并且(fh, abs_path) = fh, abs_path,当我问这个问题时我并不知道。
Wicelo 2014年

271

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

13
我知道其中只有两行,但是我认为代码本身并不具有很强的表现力。因为如果您想一会儿,如果您不了解该功能,那么发生的事情很少有线索。打印行号和行与写它不一样...如果您明白我的意思……
Chutsu 2010年

14
确实写入文件。它将标准输出重定向到该文件。看看文档
Brice

32
这里的关键位是print语句末尾的逗号:它压制了print语句并添加了另一个换行符(因为该行已经有一个换行符)。但这并不是很明显(这就是Python 3幸运地更改了该语法的原因)。
VPeric 2011年

4
请注意,当您为文件提供一个开始挂钩时,例如,当您尝试读取/写入UTF-16编码文件时,这将不起作用。
bompf

5
对于python3,print(line, end='')
Ch.Idea

80

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

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

23
该示例用法提供了一个正则表达式,但searchExp in line也不line.replace提供正则表达式操作。当然,示例使用是错误的。
kojiro 2011年

代替if searchExp in line: line = line.replace(searchExp, replaceExpr)您可以只写line = line.replace(searchExp, replaceExpr)。没有异常发生,该行保持不变。
David Wallace

对我来说也很完美。我遇到了许多其他看起来与此非常相似的示例,但诀窍是使用sys.stdout.write(line)。再次感谢!
Sage

如果使用此文件,我的文件将为空白。任何的想法?
哈维尔·洛佩斯·托马斯

我正在使用
Rakib Fiha

64

这应该起作用:(就地编辑)

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

5
+1。同样,如果您收到RuntimeError:input()已经激活,则调用fileinput.close()
geographika

1
请注意,files该字符串应该是包含文件名的字符串,而不是文件对象
atomh33ls 2013年

9
print添加一个可能已经存在的换行符。为避免这种情况,请在替换结束时添加.rstrip()
Guillaume Gendre 2014年

取而代之的是在input()中使用文件arg,它可以是fileinput.input(inplace = 1)并将脚本调用为> python
replace.py

24

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

2
您可能需要在打开文件时使用rbwb属性,因为这将保留原始的行尾
Nux

在Python 3中,不能将'wb'和'rb'与're'一起使用。它将给出错误“ TypeError:不能在类似字节的对象上使用字符串模式”

15

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

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

12

如果您想要一个通用函数来将任何文本替换为其他文本,那么这可能是最好的选择,特别是如果您是正则表达式的支持者:

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 )

12

更加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)

您可以在此处找到完整的代码段。


在Python> = 3.1中,您可以在同一行上打开两个上下文管理器
florisla

4

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


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)

是否要在新文件中保留旧文件的权限?
Bidyut

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

1
您应该在for循环之外编译regex,否则会浪费性能
Axel

2

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可以接受多个文件,但是我更喜欢在处理每个文件后立即将其关闭。所以,放置单file_pathwith声明。
  • printinplace=True,语句不会打印任何内容,因为STDOUT将其转发到原始文件。
  • end=''in print语句是消除中间的空白新行。

可以如下使用:

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

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)

该Python代码的格式看起来不太正确...(我尝试修复,但不确定要使用的是什么)
Andy Hayden 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.