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

person pkit    schedule 02.09.2008    source источник


Ответы (13)


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

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)
person Thomas Watnedal    schedule 02.09.2008
comment
Небольшой комментарий: file затеняет предопределенный одноименный класс. - person ezdazuzena; 24.01.2013
comment
@ezdazuzena Это хороший аргумент. Я заменил файл на file_path - person Thomas Watnedal; 25.01.2013
comment
Этот код изменяет права доступа к исходному файлу. Как я могу сохранить исходные разрешения? - person nic; 19.07.2013
comment
в чем смысл fh, вы используете его в вызове close, но я не вижу смысла создавать файл только для его закрытия ... - person Wicelo; 12.09.2014
comment
@Wicelo Вам необходимо закрыть его, чтобы предотвратить утечку файлового дескриптора. Вот хорошее объяснение: logilab.org/17873 - person Thomas Watnedal; 19.09.2014
comment
Да, я обнаружил, что mkstemp() возвращает 2-кортеж и (fh, abs_path) = fh, abs_path, я не знал этого, когда задавал вопрос. - person Wicelo; 20.09.2014
comment
Я не знаю почему, но для меня код не входит в строку for в цикле old_file. У кого-нибудь еще есть такая же проблема? - person Ram; 23.09.2015
comment
Похоже, что string.replace () устарела. См. Это в документации Python: docs.python.org/2 / library / - person markg; 19.06.2017
comment
Скопируйте права доступа к файлам через shutil.copymode(file_path, abs_path) перед remove() - person udondan; 16.01.2020

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

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. Файл слишком велик для хранения в памяти; вы можете переместить его во временный файл и открыть его, читая построчно, записывая обратно в исходный файл. Обратите внимание, что для этого требуется вдвое больше памяти.
person Eli Bendersky    schedule 14.11.2008
comment
Я знаю, что в нем всего две строчки, но я не думаю, что код сам по себе очень выразителен. Потому что, если вы на секунду задумаетесь, если вы не знали функции, очень мало ключей к разгадке происходящего. Распечатать номер строки и строку - это не то же самое, что написать ее ... если вы понимаете мою суть ... - person chutsu; 29.05.2010
comment
я согласен. как можно использовать fileinput для записи в файл? - person jml; 24.01.2011
comment
Это ДЕЛАЕТ запись в файл. Он перенаправляет стандартный вывод в файл. Ознакомьтесь с документами - person brice; 24.08.2011
comment
Кто-нибудь проводил тестирование времени / производительности для сравнения этих решений? Я не могу представить, что это решение будет таким же быстрым, как и решение выше (создание копии с последующей перезаписью оригинала). - person Jamie Czuy; 29.09.2011
comment
Ключевым битом здесь является запятая в конце оператора печати: она вытесняет оператор печати, добавляя еще одну новую строку (поскольку в строке уже есть одна). Однако это совсем не очевидно (вот почему Python 3, к счастью, изменил этот синтаксис). - person VPeric; 21.10.2011
comment
В ответе следует упомянуть комментарий Брайса. - person Chris Morris; 20.03.2012
comment
Обратите внимание, что это не работает, если вы предоставляете открывающую ловушку для файла, например. при попытке чтения / записи файлов в кодировке UTF-16. - person bompf; 01.07.2013
comment
Вы забыли о закрытии объекта. Лучше использовать fileinput.close () сразу после цикла. - person 0x6B6F77616C74; 01.05.2015
comment
Я обнаружил, что это фактически заменяет слово ... а не строку. - person Arindam Roychowdhury; 21.09.2015
comment
Для python3 print(line, end='') - person Ch.Idea; 14.01.2016
comment
Я думаю, что лучшая реализация для fileinput, чем возврат строки и перенаправление stdout, возвращала бы подкласс строки с методом, который фактически записывает в файл ... Я поместил этот код в файл, и хотя сам код действительно короткий , Мне также пришлось включить 4 строки комментариев, чтобы объяснить всю задействованную магию. Принятый ответ, вероятно, лучше, потому что он не требует комментариев. - person ArtOfWarfare; 19.01.2016
comment
Я чувствую, что просто использовать line.strip() лучше, чем подавлять новую строку с помощью этого подлого n в версии Python 2 или включать , end='' в версию Python 3 ... - person ArtOfWarfare; 19.01.2016
comment
Вот часть документации, объясняющая, почему файл записывается в: если аргумент ключевого слова inplace=True передается в fileinput.input() или в FileInput конструктор, файл перемещается в файл резервной копии, а стандартный вывод направляется во входной файл - person icc97; 29.11.2016
comment
поскольку добавлен any print, как его можно отключить после изменения файла? - person EsseTi; 09.01.2020
comment
следует отметить, что это решение НЕ является потокобезопасным - person GChamon; 24.01.2020

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

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.")
person Jason    schedule 24.11.2008
comment
Пример использования предоставляет регулярное выражение, но ни searchExp in line, ни line.replace не являются операциями с регулярным выражением. Конечно, использование примера неверно. - person kojiro; 14.11.2011
comment
Вместо if searchExp in line: line = line.replace(searchExp, replaceExpr) вы можете просто написать line = line.replace(searchExp, replaceExpr). Никаких исключений не создается, строка остается неизменной. - person David Wallace; 15.11.2017
comment
У меня тоже отлично сработало. Я наткнулся на ряд других примеров, очень похожих на этот, но хитрость заключалась в использовании sys.stdout.write(line). Спасибо еще раз! - person Sage; 16.01.2018
comment
Если я использую это, мой файл станет пустым. Любая идея? - person Javier López Tomás; 25.09.2019
comment
Я использую это - person Rakib Fiha; 20.01.2020

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

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"),
person Kinlan    schedule 07.09.2009
comment
+1. Также, если вы получили уже активную RuntimeError: input (), вызовите fileinput.close () - person geographika; 18.11.2011
comment
Обратите внимание, что files должен быть строкой, содержащей имя файла, не файловый объект. - person atomh33ls; 30.08.2013
comment
print добавляет новую строку, которая уже могла быть там. чтобы избежать этого, добавьте .rstrip () в конец ваших замен - person Guillaume Gendre; 21.12.2014
comment
Вместо этого используйте файлы arg в input (), это может быть fileinput.input (inplace = 1) и вызывать сценарий как ›python replace.py myfiles * .txt - person chespinoza; 24.02.2017

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

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

Также 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()
person Thijs    schedule 30.11.2012
comment
Возможно, вы захотите использовать атрибуты rb и wb при открытии файлов, так как это сохранит исходные окончания строк. - person Nux; 01.06.2016
comment
В Python 3 нельзя использовать «wb» и «rb» с «re». Это выдаст ошибку TypeError: невозможно использовать строковый шаблон для байтового объекта. - person ; 24.10.2017

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

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

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

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)

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

person Kiran    schedule 07.09.2013
comment
В Python ›= 3.1 вы можете открыть два диспетчера контекста в одной строке. - person florisla; 06.02.2018

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

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 )
person starryknight64    schedule 18.02.2014

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')
person Akif    schedule 03.10.2019
comment
Если в новом тексте есть специальные символы, такие как японские глифы, символы отображаются неправильно. Они написаны в форме, аналогичной \xe8. - person Unknow0059; 13.11.2020

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

person Lasse V. Karlsen    schedule 02.09.2008

Расширяя ответ @ Kiran, который, я согласен, является более лаконичным и питоническим, это добавляет кодеки для поддержки чтения и записи 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)
person igniteflow    schedule 02.05.2014
comment
Будет ли сохранено разрешение старого файла в новом файле? - person Bidyut; 22.08.2017

Используя ответ 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()
person Emmanuel    schedule 17.04.2014
comment
Вы должны компилировать регулярное выражение ВНЕ цикла for, иначе это потеря производительности. - person Axel; 04.02.2016

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

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)
person loi    schedule 02.08.2012
comment
Форматирование этого кода Python выглядит не совсем правильно ... (я пытался исправить, но не знал, что было задумано) - person Andy Hayden; 30.09.2012