Bagaimana cara memodifikasi file teks?

175

Saya menggunakan Python, dan ingin memasukkan string ke file teks tanpa menghapus atau menyalin file. Bagaimana saya bisa melakukan itu?

martineau
sumber
1
Anda dapat merujuk ke jawaban ini oleh Alex Martelli.
Alok
@Ani posting lainnya adalah duplikat dari Memasukkan Baris di Posisi Tertentu dari File Teks dan tentu saja ada jawaban yang jelas terdiri di sini, Mengapa tidak menambahkan jawaban Anda di sini alih-alih dengan cara lain? Jawaban yang diterima bukan persyaratan untuk pertanyaan yang bagus.
Bhargav Rao
@BhargavRao Vote ditarik. Saya seharusnya menemukan duplikat itu!
Ani Menon

Jawaban:

134

Sayangnya tidak ada cara untuk menyisipkan ke tengah file tanpa menulis ulang. Seperti yang ditunjukkan oleh poster sebelumnya, Anda dapat menambahkan file atau menimpa sebagian dari file tersebut menggunakan seek tetapi jika Anda ingin menambahkan barang di awal atau tengah, Anda harus menulis ulang.

Ini adalah masalah sistem operasi, bukan masalah Python. Itu sama dalam semua bahasa.

Apa yang biasanya saya lakukan adalah membaca dari file, membuat modifikasi dan menuliskannya ke file baru bernama myfile.txt.tmp atau sesuatu seperti itu. Ini lebih baik daripada membaca seluruh file ke dalam memori karena file tersebut mungkin terlalu besar untuk itu. Setelah file sementara selesai, saya ganti namanya sama dengan file aslinya.

Ini adalah cara yang baik dan aman untuk melakukannya karena jika file tulis macet atau dibatalkan karena alasan apa pun, Anda masih memiliki file asli Anda yang belum tersentuh.

Adam Pierce
sumber
3
Apakah alat unix seperti awk / sed melakukan sesuatu yang serupa dalam kode mereka?
Manish Gill
Tidak benar bahwa ini sama di semua bahasa. Dalam ActionScript: fileStream.openAsync (nama file, FileMode.UPDATE); Lalu saya bisa pergi ke mana saja dalam file yang saya inginkan dan mengubah apa pun.
AndrewBenjamin
2
@AndrewBenjamin. Apakah Anda tahu panggilan sistem apa yang dibuat ActionScript? Apakah ada kemungkinan openAsync membaca file dan menulis yang baru setelah panggilan?
AlexLordThorsen
@Rawrgulmuffins saya tidak. Namun, saya tahu itu tidak membaca seluruh file ke dalam memori, karena saya telah menggunakannya untuk menangani ukuran file beberapa GB. Saya menduga itu sama dengan menulis dengan C # streamwriter. Saya melihat python sebagai alat untuk melakukan hal-hal kecil dengan cepat, daripada pengembangan skala besar dan manipulasi file.
AndrewBenjamin
4
@AndrewBenjamin, pengguna tidak bertanya tentang mencari-cari dalam file dan mengubahnya (setiap bahasa yang saya tahu bisa melakukan itu); ia bertanya tentang memasukkan teks, yang berbeda dari sekadar mengubah / menimpa apa yang sudah ada dalam file. Mungkin dalam aplikasi praktis itu berbeda, tetapi tidak ada yang dapat saya temukan di API ActionScript menunjukkan bahwa ia berperilaku berbeda dari bahasa lain dalam hal ini.
eestrada
104

Tergantung pada apa yang ingin Anda lakukan. Untuk menambahkan Anda dapat membukanya dengan "a":

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

Jika Anda ingin preprend sesuatu yang harus Anda baca dari file terlebih dahulu:

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
Armin Ronacher
sumber
9
Hanya tambahan kecil, untuk menggunakan withpernyataan dalam Python 2.5 Anda perlu menambahkan "dari impor with_statement" di masa depan . Selain itu, membuka file dengan withpernyataan jelas lebih mudah dibaca dan lebih rentan kesalahan daripada penutupan manual.
Alexander Kojevnikov
2
Anda mungkin mempertimbangkan fileinputlib pembantu dengan menangani rutin buka / baca / modifikasi / tulis / ganti yang kotor saat menggunakan inline=Truearg. Contoh di sini: stackoverflow.com/a/2363893/47390
mikegreenberg
3
Hanya saja, jangan lupa untuk menutup file. f.Close()
D.Rosado
5
Ini bukan gaya yang saya gunakan, D.Rosado, tetapi ketika menggunakan gaya with, saya tidak berpikir Anda harus menutupnya secara manual. Dengan melacak sumber daya yang dibuatnya.
Chris
4
Anda tidak perlu menutup file secara manual. Itulah inti menggunakan "dengan" di sini. (Yah, sebenarnya, Python melakukan ini segera setelah objek file dikumpulkan sampah, yang dalam CPython terjadi ketika nama terikat padanya keluar dari ruang lingkup ... tetapi implementasi lainnya tidak, dan CPython mungkin berhenti melakukannya suatu hari , jadi "dengan" disarankan)
Jürgen A. Erhard
71

The fileinputmodul Python perpustakaan standar akan menulis ulang inplace berkas jika Anda menggunakan inplace = 1 parameter:

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
Dave
sumber
1
Bagaimana ini diharapkan bekerja di python3? Saya baru saja mem-porting aplikasi yang memiliki beberapa kode seperti ini dari python ke python3 dan saya tidak bisa menjalankannya sama sekali. Variabel 'line' adalah tipe byte, saya mencoba mendekodekannya menjadi unicode dan kemudian memodifikasinya lalu mengkodekannya kembali ke byte tetapi itu tidak akan berfungsi dengan baik. Itu menimbulkan beberapa pengecualian yang tidak bisa saya ingat dari atas kepala saya. Apakah orang yang menggunakan fileinput inplace = 1 di python3 berhasil?
Robru
1
@Robru: ini kode Python 3
jfs
13
Tapi tidak masalah karena Anda mengujinya terlebih dahulu pada file yang tidak penting, kan?
Paula Livingstone
33

Menulis ulang file di tempat sering dilakukan dengan menyimpan salinan lama dengan nama yang dimodifikasi. Orang Unix menambahkan ~untuk menandai yang lama. Orang-orang Windows melakukan semua hal - menambahkan .bak atau .old - atau mengganti nama file seluruhnya atau meletakkan ~ di bagian depan nama.

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

Alih-alih shutil, Anda dapat menggunakan yang berikut ini.

import os
os.rename( aFile, aFile+"~" )
S.Lott
sumber
1
Kelihatan bagus. Ingin tahu apakah .readlines () lebih baik daripada iterasi sumbernya?
bozdoz
2
@ bozdoz: iterasi lebih baik karena readlines membaca seluruh file. Tidak bagus untuk file besar. Tentu saja, ini mengandaikan Anda dapat melakukan modifikasi dengan cara yang terlokalisasi. Terkadang Anda tidak bisa, atau kode Anda menjadi jauh lebih rumit.
Jürgen A. Erhard
@ S.Lott: os.rename(aFile, aFile + "~")akan mengubah nama file sumber, bukan membuat salinan.
Patapoom
14

Modul mmap Python akan memungkinkan Anda untuk memasukkan ke dalam file. Sampel berikut menunjukkan bagaimana hal itu dapat dilakukan di Unix (Windows mmap mungkin berbeda). Perhatikan bahwa ini tidak menangani semua kondisi kesalahan dan Anda mungkin merusak atau kehilangan file asli. Juga, ini tidak akan menangani string 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()

Dimungkinkan juga untuk melakukan ini tanpa mmap dengan file dibuka dalam mode 'r +', tetapi lebih tidak nyaman dan kurang efisien karena Anda harus membaca dan menyimpan sementara file dari posisi penyisipan ke EOF - yang mungkin menjadi besar.

mhawke
sumber
14

Seperti yang disebutkan oleh Adam Anda harus mempertimbangkan keterbatasan sistem Anda sebelum Anda dapat memutuskan pendekatan apakah Anda memiliki cukup memori untuk membaca semuanya ke dalam memori, ganti bagian-bagiannya dan tulis ulang.

Jika Anda berurusan dengan file kecil atau tidak memiliki masalah memori ini dapat membantu:

Opsi 1) Baca seluruh file ke dalam memori, lakukan substitusi regex pada seluruh atau sebagian dari baris dan ganti dengan garis itu ditambah garis tambahan. Anda harus memastikan bahwa 'garis tengah' unik dalam file atau jika Anda memiliki cap waktu di setiap baris, ini harus cukup andal.

# 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()

Opsi 2) Mencari tahu garis tengah, dan menggantinya dengan garis itu ditambah garis tambahan.

# 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()
Maxime R.
sumber
2

Menulis kelas kecil untuk melakukan ini dengan bersih.

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

Maka Anda dapat menggunakannya dengan cara ini:

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
ananth krishnan
sumber
Ini tidak berfungsi untuk saya secara pribadi, itu memang menambahkan teks ke file tetapi menghapus semuanya terlebih dahulu!
Bret Hawker
Memang, ini tidak berhasil sama sekali. Malu, karena sepertinya ide yang bagus.
Mario Krušelj
0

Jika Anda tahu beberapa unix, Anda bisa mencoba yang berikut:

Catatan: $ berarti prompt perintah

Katakanlah Anda memiliki file my_data.txt dengan konten seperti itu:

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

Kemudian menggunakan osmodul Anda dapat menggunakan sedperintah yang biasa

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)

Jika Anda tidak menyadari sed, periksa, ini sangat berguna.

G. LC
sumber
3
Ini sama sekali bukan Pythonic
DarkSuniuM