Як змінити текстовий файл?


175

Я використовую Python і хотів би вставити рядок у текстовий файл, не видаляючи та не копіюючи файл. Як я можу це зробити?


1
Ви можете посилатися на цю відповідь Алекса Мартеллі.
Алок


Можливий дублікат програми Writing у верхньому ряду файлу csv у python
Ani Menon

@Ani інша публікація є дублікатом Вставки рядка у визначеному положенні текстового файлу все одно, і, безумовно, тут є чіткі складені відповіді. Чому б не додати тут свою відповідь замість іншого? Прийнята відповідь не є вимогою для хорошого запитання.
Bhargav Rao

@BhargavRao Голосування відкликано. Я мав би знайти цей дублікат, хоча!
Ані Менон

Відповіді:


134

На жаль, немає способу вставити в середину файлу, не перезаписавши його. Як було зазначено в попередніх плакатах, ви можете додати до файлу або перезаписати його частину за допомогою search, але якщо ви хочете додати матеріал на початку або в середині, вам доведеться переписати його.

Це річ операційної системи, а не річ Python. Це однаково у всіх мовах.

Що я зазвичай роблю, це читати з файлу, вносити зміни та записувати їх у новий файл під назвою myfile.txt.tmp або щось подібне. Це краще, ніж читати весь файл в пам'яті, тому що файл може бути занадто великим для цього. Після завершення тимчасового файлу я перейменую його так само, як і вихідний файл.

Це хороший, безпечний спосіб зробити це, тому що якщо файл з будь-якої причини виходить з ладу або перериває роботу, у вас все ще залишився недоторканий оригінальний файл.


3
Чи роблять інструменти unix на зразок awk / sed щось подібне у своєму коді?
Manish Gill

Неправда, що це однаково у всіх мовах. У ActionScript: fileStream.openAsync (назва файлу, FileMode.UPDATE); Тоді я можу перейти куди завгодно у потрібному файлі і щось змінити.
ЕндрюБенджамін

2
@AndrewBenjamin Чи знаєте ви, що робить системні виклики ActionScript? Чи існує можливість openAsync зчитувати файл і записувати новий після виклику?
AlexLordThorsen

@Rawrgulmuffins Я ні. Однак я знаю, що він не читає весь файл у пам'яті, так як я використовував його для обробки розмірів файлів у декілька ГБ. Я підозрюю, що це те саме, що писати з C # стріммейстером. Я розглядаю python як інструмент для швидкого ведення дрібних речей, а не розробки великих масштабів та маніпулювання файлами.
AndrewBenjamin

4
@AndrewBenjamin, користувач не запитує про пошук файлу та його зміну (кожна мова, яку я знаю, може це зробити); він запитує про вставку тексту, який відрізняється від простої зміни / перезапису того, що вже є у файлі. Можливо, у практичному застосуванні воно інше, але нічого, що я можу знайти в API ActionScript, не вказує на те, що він поводиться нічим не відрізняється від будь-якої іншої мови в цьому плані.
eestrada

104

Залежить від того, що ви хочете зробити. Щоб додати, ви можете відкрити його за допомогою "a":

 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, вам потрібно додати "з майбутнього імпорту з_держанням". Крім цього, відкриття файлів із withзаявою, безумовно, є більш читабельним та менш схильним до помилок, ніж закриття вручну.
Олександр Коєвніков

2
Ви можете врахувати, що fileinputвкладка-помічник із ручками брудно відкривається / читає / модифікує / записує / замінює рутину при використанні inline=Trueаргументу. Приклад тут: 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, і я просто не міг змусити це працювати правильно. Змінна 'рядок' - це тип байтів, я спробував розшифрувати її в unicode, а потім змінити її, а потім кодувати її назад до байтів, але вона просто не працюватиме правильно. Це призвело до певного винятку, якого я не можу згадати з голови Чи люди, які використовують fileinput inplace = 1 у python3, мають успіх?
robru

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 () кращий, ніж ітерація джерела?
bozdoz

2
@bozdoz: ітерація краще, оскільки читання читає весь файл. Не підходить для великих файлів. Звичайно, це передбачає, що ви можете робити свої модифікації таким чином локалізованим чином. Іноді ви не можете, або ваш код стає набагато складнішим.
Юрген А. Ерхард

@ S.Lott: os.rename(aFile, aFile + "~")змінить ім'я вихідного файлу, не створивши копію.
Patapoom

14

Mmap-модуль Python дозволить вам вставити у файл. Наступний зразок показує, як це можна зробити в Unix (mmap Windows може бути різним). Зауважте, що це не обробляє всі умови помилки, і ви можете пошкодити або втратити оригінальний файл. Крім того, це не буде обробляти рядки 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 - що може бути величезним.


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
Це зовсім не піфонічне
DarkSuniuM
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.