Поиск и замена строки в файле в 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

13 ответов

Решение

Я думаю, что-то подобное должно сделать это. Он в основном записывает содержимое в новый файл и заменяет старый файл новым файлом:

from tempfile import mkstemp
from shutil import move
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))
    #Remove original file
    remove(file_path)
    #Move new file
    move(abs_path, file_path)

Самый короткий способ, вероятно, будет использовать модуль fileinput. Например, следующий код добавляет номера строк в файл на месте:

import fileinput

for line in fileinput.input("test.txt", inplace=True):
    print "%d: %s" % (fileinput.filelineno(), line),

Что происходит здесь:

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

Основано на ответе Томаса Ватнедала. Тем не менее, это не дает точного ответа на одну и ту же часть исходного вопроса. Функция все еще может заменять построчно

Эта реализация заменяет содержимое файла без использования временных файлов, в результате чего права доступа к файлам остаются неизменными.

Также 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()

Как предлагает 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

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_path в with заявление.
  • print оператор ничего не печатает, когда inplace=True, потому как STDOUT перенаправляется в исходный файл.
  • end='' в print оператор должен исключить промежуточные пустые новые строки.

Может использоваться следующим образом:

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

Более питоническим способом было бы использовать менеджеры контекста, такие как код ниже:

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 )

Расширяя ответ @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)

Создайте новый файл, скопируйте строки из старого в новый и выполните замену, прежде чем записывать строки в новый файл.

Используя ответ 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()

Если вы удалите отступ, как показано ниже, он будет искать и заменять в несколько строк. Смотрите ниже, например.

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)

Для пользователей Linux:

import os
os.system('sed -i \'s/foo/bar/\' '+file_path)
Другие вопросы по тегам