Как изменить текстовый файл?

175

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

Мартино
источник
1
Вы можете сослаться на этот ответ Алекса Мартелли.
Alok
Возможный дубликат записи в верхнем ряду файла CSV в Python
Ани Менон
@ А другой пост в любом случае является копией вставки строки в указанном месте текстового файла, и, конечно, здесь есть четкие составные ответы. Почему бы не добавить свой ответ здесь, а не наоборот? Принятый ответ не является обязательным требованием для хорошего вопроса.
Бхаргав Рао
@BhargavRao Голосование отменено. Я должен был найти этот дубликат, хотя!
Ани Менон

Ответы:

134

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

Это вещь операционной системы, а не Python. Это одинаково на всех языках.

Обычно я читаю из файла, вносю изменения и записываю его в новый файл myfile.txt.tmp или что-то в этом роде. Это лучше, чем чтение всего файла в память, потому что файл может быть слишком большим для этого. Как только временный файл будет завершен, я переименую его так же, как и исходный файл.

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

Адам Пирс
источник
3
Инструменты Unix, такие как awk / sed, делают что-то похожее в своем коде?
Маниш Гилл
Это не правда, что это одинаково на всех языках. В ActionScript: fileStream.openAsync (имя файла, FileMode.UPDATE); Затем я могу пойти куда угодно в файле и изменить что угодно.
ЭндрюБенджамин
2
@AndrewBenjamin Знаете ли вы, какие системные вызовы делает ActionScript? Есть ли вероятность, что openAsync читает файл и записывает новый после вызова?
AlexLordThorsen
@Rawrgulmuffins я не знаю. Однако я знаю, что он не читает весь файл в память, так как я использовал его для обработки файлов размером в несколько ГБ. Я подозреваю, что это то же самое, что писать с помощью C # streamwriter. Я рассматриваю python как инструмент для быстрого выполнения небольших задач, а не для крупномасштабной разработки и манипулирования файлами.
ЭндрюБенджамин
4
@AndrewBenjamin, пользователь не спрашивает о поиске в файле и его изменении (каждый язык, на котором я знаю, может это сделать); он спрашивает о вставке текста, который отличается от простого изменения / перезаписи того, что уже есть в файле. Возможно, в практическом применении это отличается, но ничего, что я могу найти в API-интерфейсе ActionScript, не указывает на то, что он ведет себя иначе, чем любой другой язык в этом отношении.
eestrada
104

Зависит от того, что вы хотите сделать. Чтобы добавить, вы можете открыть его с «а»:

 with open("foo.txt", "a") as f:
     f.write("new line\n")

Если вы хотите подготовить что-то, сначала прочитайте файл:

with open("foo.txt", "r+") as f:
     old = f.read() # read everything in the file
     f.seek(0) # rewind
     f.write("new line\n" + old) # write the new line before
Армин Ронахер
источник
9
Просто небольшое добавление, чтобы использовать withоператор в Python 2.5, вам нужно добавить «из будущего импорта с_statement». Помимо этого, открытие файлов с помощью withоператора определенно более читабельно и менее подвержено ошибкам, чем закрытие вручную.
Александр Кожевников
2
Вы можете рассмотреть fileinputвспомогательную библиотеку с ручками для грязной процедуры открытия / чтения / изменения / записи / замены при использовании inline=Trueаргумента arg. Пример здесь: stackoverflow.com/a/2363893/47390
mikegreenberg
3
Только не забудьте закрыть файл. f.Close()
Д.Розадо
5
Я не использую стиль, Д.Розадо, но при использовании со стилем я не думаю, что вам нужно закрывать вручную. С отслеживает ресурс, который он создает.
Крис
4
Вам не нужно вручную закрывать файл. В этом весь смысл использования «с» здесь. (Ну, на самом деле, Python делает это, как только объект файла собирается сборщиком мусора, что в CPython происходит, когда привязанное к нему имя выходит из области видимости ... но другие реализации этого не делают, и CPython может перестать делать это когда-нибудь поэтому рекомендуется "с")
Юрген А. Эрхард
71

fileinputМодуль стандартной библиотеки Python перепишет файл InPlace , если вы используете INPLACE параметр = 1:

import sys
import fileinput

# replace all occurrences of 'sit' with 'SIT' and insert a line after the 5th
for i, line in enumerate(fileinput.input('lorem_ipsum.txt', inplace=1)):
    sys.stdout.write(line.replace('sit', 'SIT'))  # replace 'sit' and write
    if i == 4: sys.stdout.write('\n')  # write a blank line after the 5th line
Дейв
источник
1
Как это должно работать в python3? Я просто портировал приложение с таким кодом из python в python3, и я просто не мог заставить его работать правильно. Переменная 'line' является байтовым типом, я пытался декодировать ее в Unicode, а затем модифицировать и затем кодировать обратно в байты, но это просто не будет работать правильно. Это подняло какое-то исключение, которое я не могу вспомнить из головы. Люди с успехом используют fileinput inplace = 1 в python3?
Робру
1
@Robru: вот код Python 3
JFS
13
Но это не проблема, потому что вы сначала проверили его на неважном файле, верно?
Паула Ливингстон
33

Перезапись файла на месте часто выполняется путем сохранения старой копии с измененным именем. Unix люди добавляют, ~чтобы отметить старый. Пользователи Windows делают разные вещи - добавляют .bak или .old - или полностью переименовывают файл, или помещают ~ в начале имени.

import shutil
shutil.move( afile, afile+"~" )

destination= open( aFile, "w" )
source= open( aFile+"~", "r" )
for line in source:
    destination.write( line )
    if <some condition>:
        destination.write( >some additional line> + "\n" )
source.close()
destination.close()

Вместо этого shutilвы можете использовать следующее.

import os
os.rename( aFile, aFile+"~" )
С. Лотт
источник
1
Выглядит хорошо. Хотите знать, если .readlines () лучше, чем итерация источника?
Боздоз
2
@bozdoz: итерация лучше, так как readlines читает весь файл. Не подходит для больших файлов. Конечно, это предполагает, что вы можете делать свои модификации таким локализованным способом. Иногда вы не можете, или ваш код становится намного сложнее.
Юрген А. Эрхард
@ S.Lott: os.rename(aFile, aFile + "~")изменит имя исходного файла, не создавая копию.
Patapoom
14

Модуль mmap Python позволит вам вставить в файл. В следующем примере показано, как это можно сделать в Unix (в Windows mmap может отличаться). Обратите внимание, что это не обрабатывает все ошибки, и вы можете повредить или потерять исходный файл. Кроме того, это не будет обрабатывать строки Unicode.

import os
from mmap import mmap

def insert(filename, str, pos):
    if len(str) < 1:
        # nothing to insert
        return

    f = open(filename, 'r+')
    m = mmap(f.fileno(), os.path.getsize(filename))
    origSize = m.size()

    # or this could be an error
    if pos > origSize:
        pos = origSize
    elif pos < 0:
        pos = 0

    m.resize(origSize + len(str))
    m[pos+len(str):] = m[pos:origSize]
    m[pos:pos+len(str)] = str
    m.close()
    f.close()

Это также возможно сделать без mmap с файлами, открытыми в режиме «r +», но это менее удобно и менее эффективно, так как вам придется читать и временно сохранять содержимое файла из позиции вставки в EOF - что может быть огромным

mhawke
источник
14

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

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

Вариант 1) Считать весь файл в память, выполнить подстановку регулярных выражений для всей или части строки и заменить ее на эту строку плюс дополнительную строку. Вам нужно убедиться, что «средняя строка» уникальна в файле или если у вас есть временные метки в каждой строке, это должно быть довольно надежно.

# open file with r+b (allow write and binary mode)
f = open("file.log", 'r+b')   
# read entire content of file into memory
f_content = f.read()
# basically match middle line and replace it with itself and the extra line
f_content = re.sub(r'(middle line)', r'\1\nnew line', f_content)
# return pointer to top of file so we can re-write the content with replaced string
f.seek(0)
# clear file content 
f.truncate()
# re-write the content with the updated content
f.write(f_content)
# close file
f.close()

Вариант 2) Определите среднюю линию и замените ее на эту строку плюс дополнительную строку.

# open file with r+b (allow write and binary mode)
f = open("file.log" , 'r+b')   
# get array of lines
f_content = f.readlines()
# get middle line
middle_line = len(f_content)/2
# overwrite middle line
f_content[middle_line] += "\nnew line"
# return pointer to top of file so we can re-write the content with replaced string
f.seek(0)
# clear file content 
f.truncate()
# re-write the content with the updated content
f.write(''.join(f_content))
# close file
f.close()
Максим Р.
источник
2

Написал небольшой класс для этого чисто.

import tempfile

class FileModifierError(Exception):
    pass

class FileModifier(object):

    def __init__(self, fname):
        self.__write_dict = {}
        self.__filename = fname
        self.__tempfile = tempfile.TemporaryFile()
        with open(fname, 'rb') as fp:
            for line in fp:
                self.__tempfile.write(line)
        self.__tempfile.seek(0)

    def write(self, s, line_number = 'END'):
        if line_number != 'END' and not isinstance(line_number, (int, float)):
            raise FileModifierError("Line number %s is not a valid number" % line_number)
        try:
            self.__write_dict[line_number].append(s)
        except KeyError:
            self.__write_dict[line_number] = [s]

    def writeline(self, s, line_number = 'END'):
        self.write('%s\n' % s, line_number)

    def writelines(self, s, line_number = 'END'):
        for ln in s:
            self.writeline(s, line_number)

    def __popline(self, index, fp):
        try:
            ilines = self.__write_dict.pop(index)
            for line in ilines:
                fp.write(line)
        except KeyError:
            pass

    def close(self):
        self.__exit__(None, None, None)

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        with open(self.__filename,'w') as fp:
            for index, line in enumerate(self.__tempfile.readlines()):
                self.__popline(index, fp)
                fp.write(line)
            for index in sorted(self.__write_dict):
                for line in self.__write_dict[index]:
                    fp.write(line)
        self.__tempfile.close()

Тогда вы можете использовать это так:

with FileModifier(filename) as fp:
    fp.writeline("String 1", 0)
    fp.writeline("String 2", 20)
    fp.writeline("String 3")  # To write at the end of the file
Анант Кришнан
источник
Лично это не работает для меня, оно добавляет текст в файл, но сначала все удаляет!
Брет Хоукер
Действительно, это не работает вообще. Позор, потому что это казалось хорошей идеей.
Марио Крушель
0

Если вы знаете какой-нибудь Unix, вы можете попробовать следующее:

Примечания: $ означает командную строку

Допустим, у вас есть файл my_data.txt с таким содержимым:

$ cat my_data.txt
This is a data file
with all of my data in it.

Затем с помощью osмодуля вы можете использовать обычные sedкоманды

import os

# Identifiers used are:
my_data_file = "my_data.txt"
command = "sed -i 's/all/none/' my_data.txt"

# Execute the command
os.system(command)

Если вы не знаете о sed, проверьте это, это чрезвычайно полезно.

Г. Лк
источник
3
Это не Pythonic вообще
DarkSuniuM