Поиск и замена строки в файле в Python

293

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

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

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
PKIT
источник

Ответы:

192

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

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)
Томас Ватнедал
источник
5
Небольшой комментарий: fileскрывает предопределенный класс с тем же именем.
ездазузена
4
Этот код меняет права на исходный файл. Как я могу сохранить исходные разрешения?
Nic
1
какой смысл fh, вы используете его в вызове close, но я не вижу смысла в создании файла только для его закрытия ...
Wicelo
2
@ Wicelo Вы должны закрыть его, чтобы предотвратить утечку файлового дескриптора. Вот достойное объяснение: logilab.org/17873
Томас Ватнедал
1
Да, я обнаружил, что mkstemp()возвращается 2-кортеж, и (fh, abs_path) = fh, abs_pathя не знал этого, когда задавал вопрос.
Wicelo
272

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

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. Файл слишком велик для хранения в памяти; Вы можете переместить его во временный файл и открыть его, читая его построчно, записывая обратно в исходный файл. Обратите внимание, что для этого требуется вдвое больше места.
Эли Бендерский
источник
13
Я знаю, что в нем всего две строки, но я не думаю, что код сам по себе очень выразителен. Потому что, если вы задумаетесь на секунду, если вы не знали функцию, есть очень мало подсказок о том, что происходит. Печать номера строки и строки не совпадает с написанием этого ... если вы понимаете, суть ...
chutsu
14
Это делает запись в файл. Он перенаправляет стандартный вывод в файл. Взгляните на документы
Брайс
32
Ключевым битом здесь является запятая в конце оператора печати: она выходит за пределы оператора печати, добавляя еще одну новую строку (так как в строке она уже есть). Однако это не очень очевидно (именно поэтому Python 3 изменил этот синтаксис, к счастью).
VPeric
4
Обратите внимание, что это не работает, когда вы предоставляете открывающий хук для файла, например, когда вы пытаетесь читать / записывать файлы в кодировке UTF-16.
Bompf
5
Для python3,print(line, end='')
Ch.Idea
80

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

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.")
Джейсон
источник
23
Пример использования предоставляет регулярное выражение, но не является searchExp in lineни line.replaceоперациями с регулярными выражениями. Конечно, пример использования неверен.
Кодзиро
Вместо if searchExp in line: line = line.replace(searchExp, replaceExpr)тебя можно просто написать line = line.replace(searchExp, replaceExpr). Исключение не генерируется, строка просто остается неизменной.
Дэвид Уоллес
У меня отлично сработало. Я сталкивался с рядом других примеров, которые выглядели очень похоже на это, но хитрость заключалась в использовании sys.stdout.write(line). Еще раз спасибо!
Мудрец
Если я использую это, мой файл становится пустым. Любая идея?
Хавьер Лопес Томас
Я использую это
Ракиб Фиха
64

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

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

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

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

Также 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()
Тайс
источник
2
Возможно, вы захотите использовать rbи wbатрибуты при открытии файлов, так как это сохранит исходные окончания строк
Nux
В Python 3 вы не можете использовать 'wb' и 'rb' с 're'. Будет выдано сообщение об ошибке «Ошибка типа: невозможно использовать строковый шаблон в
15

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

fin = open("a.txt")
fout = open("b.txt", "wt")
for line in fin:
    fout.write( line.replace('foo', 'bar') )
fin.close()
fout.close()
hamishmcn
источник
12

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

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 )
starryknight64
источник
12

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

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 вы можете открыть два менеджера контекста в одной строке .
Флорисла
4

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

Лассе В. Карлсен
источник
4

Расширяя ответ @ 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)
igniteflow
источник
Собирается ли сохранить разрешение старого файла в новом файле?
Бидют
2

Используя ответ 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()
Эммануэль
источник
1
Вы должны скомпилировать регулярное выражение за пределами цикла for, иначе это потеря производительности
Аксель
2

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')
Акиф
источник
0

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

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 выглядит не совсем правильно ... (Я пытался исправить, но не был уверен, что было задумано)
Энди Хейден