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






Создайте новый файл, скопируйте строки из старого в новый и выполните замену перед записью строк в новый файл.
Думаю, что-то вроде этого должно сработать. Он в основном записывает содержимое в новый файл и заменяет старый файл новым:
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)
@ezdazuzena Это хороший аргумент. Я заменил файл на file_path
Этот код изменяет права доступа к исходному файлу. Как я могу сохранить исходные разрешения?
в чем смысл fh, вы используете его в вызове close, но я не вижу смысла создавать файл только для его закрытия ...
@Wicelo Вам необходимо закрыть его, чтобы предотвратить утечку файлового дескриптора. Вот достойное объяснение: logilab.org/17873
Да, я обнаружил, что mkstemp() возвращает 2-кортеж, а (fh, abs_path) = fh, abs_path, я не знал этого, когда задавал вопрос.
Я не знаю почему, но для меня код не входит в строку for в цикле old_file. У кого-нибудь еще есть такая же проблема?
Похоже, что string.replace () устарела. См. Это в документации Python: docs.python.org/2/library/…
Скопируйте права доступа к файлам через shutil.copymode(file_path, abs_path) до remove()
Как предлагает 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
Здесь происходит следующее:
print записываются обратно в исходный файл.fileinput имеет больше наворотов. Например, его можно использовать для автоматической работы со всеми файлами в sys.args[1:], без необходимости явно перебирать их. Начиная с Python 3.2, он также предоставляет удобный менеджер контекста для использования в операторе with.
Хотя fileinput отлично подходит для одноразовых сценариев, я бы с осторожностью использовал его в реальном коде, потому что, по общему признанию, он не очень удобочитаем или знаком. В реальном (производственном) коде стоит потратить еще несколько строк кода, чтобы сделать процесс явным и, таким образом, сделать код читабельным.
Есть два варианта:
Я знаю, что в нем всего две строчки, но я не думаю, что код сам по себе очень выразителен. Потому что, если вы задумаетесь на секунду, если вы не знаете функцию, очень мало ключей к разгадке происходящего. Распечатать номер строки и строку - это не то же самое, что написать ее ... если вы понимаете мою суть ...
я согласен. как можно использовать fileinput для записи в файл?
Это ДЕЛАЕТ записи в файл. Он перенаправляет стандартный вывод в файл. Взгляните на документы
Кто-нибудь проводил тестирование времени / производительности для сравнения этих решений? Я не могу представить, что это решение будет таким же быстрым, как и решение выше (создание копии с последующей перезаписью оригинала).
Ключевым битом здесь является запятая в конце оператора печати: она вытесняет оператор печати, добавляя еще одну новую строку (поскольку в строке уже есть одна). Однако это совсем не очевидно (вот почему Python 3, к счастью, изменил этот синтаксис).
В ответе следует упомянуть комментарий Брайса.
Обратите внимание, что это не работает, когда вы предоставляете открывающую ловушку для файла, например при попытке чтения / записи файлов в кодировке UTF-16.
@bompf: вот Версия на основе NamedTemporaryFile, поддерживающая произвольную кодировку символов.
Вы забыли о закрытии объекта. Лучше использовать fileinput.close () сразу после цикла.
Я обнаружил, что это фактически заменяет слово ... а не строку.
Для python3, print(line, end='')
Я думаю, что лучшая реализация для fileinput, чем возврат строки и перенаправление stdout, возвращала бы подкласс строки с методом, который фактически записывает в файл ... Я поместил этот код в файл, и хотя сам код действительно короткий , Мне также пришлось включить 4 строки комментариев, чтобы объяснить всю задействованную магию. Принятый ответ, вероятно, лучше, потому что он не требует комментариев.
Я чувствую, что просто использование line.strip() лучше, чем подавление новой строки с этим подлым n в версии Python 2 или включение , end='' в версию Python 3 ...
Здесь часть документации указывает на то, почему файл записывается в: если аргумент ключевого слова inplace=True передается в fileinput.input() или в конструктор FileInput, файл перемещается в файл резервной копии, а стандартный вывод направляется во входной файл
поскольку добавлена печать любой, как ее можно отключить после изменения файла?
Следует отметить, что это решение НЕ является потокобезопасным
Вот еще один протестированный пример, который будет соответствовать шаблонам поиска и замены:
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 не являются операциями с регулярным выражением. Конечно, использование примера неверно.
Вместо if searchExp in line: line = line.replace(searchExp, replaceExpr) можно просто написать line = line.replace(searchExp, replaceExpr). Никаких исключений не создается, строка остается неизменной.
У меня тоже отлично сработало. Я наткнулся на ряд других примеров, которые были очень похожи на этот, но уловка заключалась в использовании sys.stdout.write(line). Еще раз спасибо!
Если я использую это, мой файл станет пустым. Любая идея?
Я использую это
Это должно работать: (редактирование на месте)
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 ()
Обратите внимание, что files должен быть строкой, содержащей имя файла не файловый объект.
print добавляет новую строку, которая уже могла быть там. чтобы избежать этого, добавьте .rstrip () в конец ваших замен
Вместо этого используйте файлы arg в input (), это может быть fileinput.input (inplace = 1) и вызывать сценарий как> python replace.py myfiles * .txt
если вы удалите отступ, как показано ниже, поиск и замена будут выполняться в нескольких строках. См., Например, ниже.
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 выглядит не совсем правильно ... (я пытался исправить, но не знал, что было задумано)
На основе ответа Томаса Уотнедала. Однако это не дает точного ответа на построчную часть исходного вопроса. Функция все еще может заменять построчно
Эта реализация заменяет содержимое файла без использования временных файлов, как следствие, права доступа к файлам остаются неизменными.
Также 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 при открытии файлов, так как это сохранит исходные окончания строк.
В Python 3 нельзя использовать «wb» и «rb» с «re». Будет выдана ошибка «TypeError: невозможно использовать строковый шаблон для байтового объекта».
Более питоническим способом было бы использовать менеджеры контекста, подобные приведенному ниже коду:
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 вы можете открыть файл два менеджера контекста на одной линии.
Если вам нужна универсальная функция, которая заменяет текст любой каким-либо другим текстом, это, вероятно, лучший способ, особенно если вы поклонник регулярных выражений:
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, иначе это потеря производительности
Расширяя ответ @ 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)
Будет ли сохранено разрешение старого файла в новом файле?
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.
Небольшой комментарий:
fileдублирует одноименный предопределенный класс.