Поиск и замена строки в файле в Python - PullRequest
251 голосов
/ 02 сентября 2008

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

Каков наилучший способ сделать это в следующем коде?

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 ]

240 голосов
/ 14 ноября 2008

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

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. Файл слишком велик для хранения в памяти; Вы можете переместить его во временный файл и открыть его, читая его построчно, записывая обратно в исходный файл. Обратите внимание, что для этого требуется вдвое больше места.
168 голосов
/ 02 сентября 2008

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

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)
71 голосов
/ 24 ноября 2008

Вот еще один пример, который был протестирован и будет соответствовать шаблонам поиска и замены:

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.")
58 голосов
/ 07 сентября 2009

Это должно работать: (редактирование на месте)

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"),
22 голосов
/ 30 ноября 2012

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

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

Также 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()
11 голосов
/ 18 февраля 2014

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

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 )
11 голосов
/ 02 сентября 2008

Как предполагает lassevk, запишите новый файл по ходу работы, вот пример кода:

fin = open("a.txt")
fout = open("b.txt", "wt")
for line in fin:
    fout.write( line.replace('foo', 'bar') )
fin.close()
fout.close()
8 голосов
/ 07 сентября 2013

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

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)

Вы можете найти полный фрагмент здесь .

3 голосов
/ 02 мая 2014

Расширяя ответ @ 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)
3 голосов
/ 02 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...