Wie ändere ich eine Textdatei?

175

Ich verwende Python und möchte eine Zeichenfolge in eine Textdatei einfügen, ohne die Datei zu löschen oder zu kopieren. Wie kann ich das machen?

Martineau
quelle
1
Sie können sich auf diese Antwort von Alex Martelli beziehen .
Alok
@Ani der andere Beitrag ist sowieso ein Duplikat des Einfügens einer Zeile an der angegebenen Position einer Textdatei, und sicherlich gibt es hier klar zusammengesetzte Antworten. Warum fügen Sie Ihre Antwort hier nicht statt andersherum hinzu? Eine akzeptierte Antwort ist keine Voraussetzung für eine gute Frage.
Bhargav Rao
@ BhargavRao Abstimmung zurückgezogen. Ich hätte dieses Duplikat allerdings finden sollen!
Ani Menon

Antworten:

134

Leider gibt es keine Möglichkeit, in die Mitte einer Datei einzufügen, ohne sie neu zu schreiben. Wie in früheren Postern angegeben, können Sie eine Datei anhängen oder einen Teil davon mit search überschreiben. Wenn Sie jedoch am Anfang oder in der Mitte Inhalte hinzufügen möchten, müssen Sie diese neu schreiben.

Dies ist eine Betriebssystemsache, keine Python-Sache. Es ist in allen Sprachen gleich.

Normalerweise lese ich aus der Datei, nehme die Änderungen vor und schreibe sie in eine neue Datei namens myfile.txt.tmp oder ähnliches. Dies ist besser als das Einlesen der gesamten Datei in den Speicher, da die Datei möglicherweise zu groß dafür ist. Sobald die temporäre Datei fertig ist, benenne ich sie genauso um wie die Originaldatei.

Dies ist ein guter und sicherer Weg, denn wenn der Dateischreibvorgang aus irgendeinem Grund abstürzt oder abgebrochen wird, haben Sie immer noch Ihre unberührte Originaldatei.

Adam Pierce
quelle
3
Tun Unix-Tools wie awk / sed etwas Ähnliches in ihrem Code?
Manish Gill
Es ist nicht wahr, dass dies in allen Sprachen gleich ist. In ActionScript: fileStream.openAsync (Dateiname, FileMode.UPDATE); Dann kann ich überall in der gewünschten Datei etwas ändern.
AndrewBenjamin
2
@AndrewBenjamin Wissen Sie, welche Systemaufrufe ActionScript macht? Gibt es eine Möglichkeit, dass openAsync die Datei liest und nach dem Aufruf eine neue schreibt?
AlexLordThorsen
@Rawrgulmuffins mache ich nicht. Ich weiß jedoch, dass nicht die gesamte Datei in den Speicher eingelesen wird, da ich damit Dateigrößen von mehreren GB verarbeitet habe. Ich vermute, es ist dasselbe wie mit C # Streamwriter zu schreiben. Ich betrachte Python als ein Werkzeug, um kleine Dinge schnell zu erledigen, anstatt große Entwicklung und Dateimanipulation.
AndrewBenjamin
4
@AndrewBenjamin, der Benutzer fragt nicht danach, in der Datei herumzusuchen und sie zu ändern (jede Sprache, die ich kenne, kann das); Er fragt nach dem Einfügen von Text, was sich von dem einfachen Ändern / Überschreiben der bereits in der Datei enthaltenen Elemente unterscheidet. Vielleicht ist es in der praktischen Anwendung anders, aber nichts, was ich in der ActionScript-API finden kann, weist darauf hin, dass es sich in dieser Hinsicht anders verhält als jede andere Sprache.
Eestrada
104

Kommt darauf an, was du machen willst. Zum Anhängen können Sie es mit "a" öffnen:

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

Wenn Sie etwas vorgeben möchten, müssen Sie zuerst aus der Datei lesen:

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
quelle
9
Nur eine kleine Ergänzung, um die withAnweisung in Python 2.5 zu verwenden, müssen Sie "from future import with_statement" hinzufügen. Abgesehen davon ist das Öffnen von Dateien mit der withAnweisung definitiv lesbarer und weniger fehleranfällig als das manuelle Schließen.
Alexander Kojevnikov
2
Sie könnten die fileinputHelfer-Bibliothek mit Handles für die schmutzige Routine zum Öffnen / Lesen / Ändern / Schreiben / Ersetzen in Betracht ziehen , wenn Sie das inline=TrueArgument verwenden. Beispiel hier: stackoverflow.com/a/2363893/47390
mikegreenberg
3
Vergessen Sie nicht, die Datei zu schließen. f.Close()
D. Rosado
5
Es ist kein Stil, den ich verwende, D.Rosado, aber wenn Sie den with-Stil verwenden, müssen Sie ihn nicht manuell schließen. Das with verfolgt die Ressource, die es erstellt.
Chris
4
Sie müssen die Datei nicht manuell schließen. Das ist der springende Punkt bei der Verwendung von "mit" hier. (Nun, tatsächlich tut Python dies, sobald das Dateiobjekt durch Müll gesammelt wurde, was in CPython passiert, wenn der daran gebundene Name den Gültigkeitsbereich verlässt ... aber andere Implementierungen tun dies nicht und CPython hört möglicherweise eines Tages damit auf , also "mit" wird empfohlen)
Jürgen A. Erhard
71

Das fileinputModul der Python-Standardbibliothek schreibt eine Datei neu, wenn Sie den Parameter inplace = 1 verwenden:

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
quelle
1
Wie soll das in Python3 funktionieren? Ich habe gerade eine App portiert, die Code wie diesen von Python auf Python3 hatte, und ich konnte dies einfach nicht richtig zum Laufen bringen. Die 'line'-Variable ist ein Bytetyp. Ich habe versucht, sie in Unicode zu dekodieren und sie dann zu ändern und dann wieder in Bytes zu kodieren, aber es würde einfach nicht richtig funktionieren. Es gab einige Ausnahmen, an die ich mich nicht mehr erinnern kann. Verwenden Leute, die in Python3 den Dateieingang inplace = 1 verwenden, mit Erfolg?
Robru
1
@Robru: hier ist Python 3-Code
jfs
13
Aber es ist kein Problem, denn Sie haben es zuerst an einer unwichtigen Datei getestet, oder?
Paula Livingstone
33

Das Umschreiben einer Datei erfolgt häufig durch Speichern der alten Kopie unter einem geänderten Namen. Unix-Leute fügen ein hinzu ~, um das alte zu markieren. Windows-Leute machen alle möglichen Dinge - fügen Sie .bak oder .old hinzu - oder benennen Sie die Datei vollständig um oder setzen Sie das ~ auf die Vorderseite des Namens.

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

Stattdessen shutilkönnen Sie Folgendes verwenden.

import os
os.rename( aFile, aFile+"~" )
S.Lott
quelle
1
Sieht gut aus. Sie fragen sich, ob .readlines () besser ist als die Quelle zu iterieren?
Bozdoz
2
@bozdoz: Iterieren ist besser, da readlines die gesamte Datei liest. Nicht gut für große Dateien. Dies setzt natürlich voraus, dass Sie Ihre Änderungen auf eine so lokalisierte Weise vornehmen können. Manchmal können Sie nicht oder Ihr Code wird viel komplizierter.
Jürgen A. Erhard
@ S.Lott: Ändert os.rename(aFile, aFile + "~")den Namen der Quelldatei und erstellt keine Kopie.
Patapoom
14

Mit dem mmap-Modul von Python können Sie in eine Datei einfügen. Das folgende Beispiel zeigt, wie dies unter Unix durchgeführt werden kann (Windows mmap kann unterschiedlich sein). Beachten Sie, dass dies nicht alle Fehlerbedingungen behandelt und Sie möglicherweise die Originaldatei beschädigen oder verlieren. Dies behandelt auch keine Unicode-Zeichenfolgen.

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

Es ist auch möglich, dies ohne mmap mit Dateien zu tun, die im 'r +' - Modus geöffnet sind. Dies ist jedoch weniger bequem und weniger effizient, da Sie den Inhalt der Datei von der Einfügeposition in EOF lesen und vorübergehend speichern müssten sei riesig.

mhawke
quelle
14

Wie von Adam erwähnt, müssen Sie Ihre Systembeschränkungen berücksichtigen, bevor Sie entscheiden können, ob Sie über genügend Speicher verfügen, um alles in den Speicher einzulesen, Teile davon zu ersetzen und neu zu schreiben.

Wenn Sie mit einer kleinen Datei arbeiten oder keine Speicherprobleme haben, kann dies helfen:

Option 1) Lesen Sie die gesamte Datei in den Speicher, ersetzen Sie die gesamte Zeile oder einen Teil der Zeile durch Regex und ersetzen Sie sie durch diese Zeile plus die zusätzliche Zeile. Sie müssen sicherstellen, dass die 'mittlere Zeile' in der Datei eindeutig ist. Wenn Sie in jeder Zeile Zeitstempel haben, sollte dies ziemlich zuverlässig sein.

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

Option 2) Finden Sie die mittlere Linie heraus und ersetzen Sie sie durch diese Linie plus die zusätzliche Linie.

# 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.
quelle
2

Schrieb eine kleine Klasse dafür sauber.

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

Dann können Sie es folgendermaßen verwenden:

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 Krishna
quelle
Dies funktioniert bei mir persönlich nicht, es fügt der Datei Text hinzu, aber es entfernt zuerst alles!
Bret Hawker
In der Tat funktioniert das überhaupt nicht. Schade, denn es schien eine gute Idee zu sein.
Mario Krušelj
0

Wenn Sie Unix kennen, können Sie Folgendes versuchen:

Anmerkungen: $ bedeutet die Eingabeaufforderung

Angenommen, Sie haben eine Datei my_data.txt mit Inhalten als solchen:

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

Dann können Sie mit dem osModul die üblichen sedBefehle verwenden

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)

Wenn Sie sich sed nicht bewusst sind, probieren Sie es aus, es ist äußerst nützlich.

G. LC
quelle
3
Es ist überhaupt nicht pythonisch
DarkSuniuM