Поиск и замена строки в файле в 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
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
318
0
503 812
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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)

Небольшой комментарий: file дублирует одноименный предопределенный класс.

ezdazuzena 24.01.2013 19:24

@ezdazuzena Это хороший аргумент. Я заменил файл на file_path

Thomas Watnedal 25.01.2013 02:38

Этот код изменяет права доступа к исходному файлу. Как я могу сохранить исходные разрешения?

nic 19.07.2013 01:35

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

Wicelo 12.09.2014 10:24

@Wicelo Вам необходимо закрыть его, чтобы предотвратить утечку файлового дескриптора. Вот достойное объяснение: logilab.org/17873

Thomas Watnedal 19.09.2014 15:52

Да, я обнаружил, что mkstemp() возвращает 2-кортеж, а (fh, abs_path) = fh, abs_path, я не знал этого, когда задавал вопрос.

Wicelo 20.09.2014 07:31

Я не знаю почему, но для меня код не входит в строку for в цикле old_file. У кого-нибудь еще есть такая же проблема?

Ram 23.09.2015 08:30

Похоже, что string.replace () устарела. См. Это в документации Python: docs.python.org/2/library/…

markg 19.06.2017 07:26

Скопируйте права доступа к файлам через shutil.copymode(file_path, abs_path) до remove()

udondan 16.01.2020 15:41

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

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

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

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

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

chutsu 29.05.2010 23:12

я согласен. как можно использовать fileinput для записи в файл?

jml 24.01.2011 07:50

Это ДЕЛАЕТ записи в файл. Он перенаправляет стандартный вывод в файл. Взгляните на документы

brice 24.08.2011 20:17

Кто-нибудь проводил тестирование времени / производительности для сравнения этих решений? Я не могу представить, что это решение будет таким же быстрым, как и решение выше (создание копии с последующей перезаписью оригинала).

Jamie Czuy 29.09.2011 01:39

Ключевым битом здесь является запятая в конце оператора печати: она вытесняет оператор печати, добавляя еще одну новую строку (поскольку в строке уже есть одна). Однако это совсем не очевидно (вот почему Python 3, к счастью, изменил этот синтаксис).

VPeric 21.10.2011 18:24

В ответе следует упомянуть комментарий Брайса.

Chris Morris 20.03.2012 20:21

Обратите внимание, что это не работает, когда вы предоставляете открывающую ловушку для файла, например при попытке чтения / записи файлов в кодировке UTF-16.

bompf 01.07.2013 16:19

Вы забыли о закрытии объекта. Лучше использовать fileinput.close () сразу после цикла.

0x6B6F77616C74 01.05.2015 01:11

Я обнаружил, что это фактически заменяет слово ... а не строку.

Arindam Roychowdhury 21.09.2015 15:24

Для python3, print(line, end='')

Ch.Idea 14.01.2016 14:50

Я думаю, что лучшая реализация для fileinput, чем возврат строки и перенаправление stdout, возвращала бы подкласс строки с методом, который фактически записывает в файл ... Я поместил этот код в файл, и хотя сам код действительно короткий , Мне также пришлось включить 4 строки комментариев, чтобы объяснить всю задействованную магию. Принятый ответ, вероятно, лучше, потому что он не требует комментариев.

ArtOfWarfare 19.01.2016 18:25

Я чувствую, что просто использование line.strip() лучше, чем подавление новой строки с этим подлым n в версии Python 2 или включение , end='' в версию Python 3 ...

ArtOfWarfare 19.01.2016 18:33

Здесь часть документации указывает на то, почему файл записывается в: если аргумент ключевого слова inplace=True передается в fileinput.input() или в конструктор FileInput, файл перемещается в файл резервной копии, а стандартный вывод направляется во входной файл

icc97 29.11.2016 10:28

поскольку добавлена ​​печать любой, как ее можно отключить после изменения файла?

EsseTi 09.01.2020 18:23

Следует отметить, что это решение НЕ является потокобезопасным

GChamon 24.01.2020 18:44

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

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 не являются операциями с регулярным выражением. Конечно, использование примера неверно.

kojiro 14.11.2011 22:18

Вместо if searchExp in line: line = line.replace(searchExp, replaceExpr) можно просто написать line = line.replace(searchExp, replaceExpr). Никаких исключений не создается, строка остается неизменной.

David Wallace 15.11.2017 19:07

У меня тоже отлично сработало. Я наткнулся на ряд других примеров, которые были очень похожи на этот, но уловка заключалась в использовании sys.stdout.write(line). Еще раз спасибо!

Sage 16.01.2018 20:23

Если я использую это, мой файл станет пустым. Любая идея?

Javier López Tomás 25.09.2019 19:11

Я использую это

Rakib Fiha 20.01.2020 16:41

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

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 (), вызовите fileinput.close ()

geographika 18.11.2011 13:24

Обратите внимание, что files должен быть строкой, содержащей имя файла не файловый объект.

atomh33ls 30.08.2013 14:00

print добавляет новую строку, которая уже могла быть там. чтобы избежать этого, добавьте .rstrip () в конец ваших замен

Guillaume Gendre 21.12.2014 17:09

Вместо этого используйте файлы arg в input (), это может быть fileinput.input (inplace = 1) и вызывать сценарий как> python replace.py myfiles * .txt

chespinoza 24.02.2017 20:45

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

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 30.09.2012 22:18

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

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

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

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

Nux 01.06.2016 17:35

В Python 3 нельзя использовать «wb» и «rb» с «re». Будет выдана ошибка «TypeError: невозможно использовать строковый шаблон для байтового объекта».

user5074403 24.10.2017 16:22

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

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 06.02.2018 14:05

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

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 )

Используя ответ 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, иначе это потеря производительности

Axel 04.02.2016 20:49

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

Будет ли сохранено разрешение старого файла в новом файле?

Bidyut 22.08.2017 13:27

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

Если в новом тексте есть специальные символы, такие как японские глифы, символы отображаются неправильно. Они написаны в форме, аналогичной \xe8.

Unknow0059 13.11.2020 14:26

Другие вопросы по теме