Suchen und ersetzen Sie eine Zeile in einer Datei in Python

292

Ich möchte den Inhalt einer Textdatei durchlaufen und in einigen Zeilen suchen und ersetzen und das Ergebnis in die Datei zurückschreiben. Ich könnte zuerst die gesamte Datei in den Speicher laden und dann zurückschreiben, aber das ist wahrscheinlich nicht der beste Weg, dies zu tun.

Was ist der beste Weg, dies im folgenden Code zu tun?

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
quelle

Antworten:

191

Ich denke so etwas sollte es tun. Grundsätzlich schreibt es den Inhalt in eine neue Datei und ersetzt die alte Datei durch die neue Datei:

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)
Thomas Watnedal
quelle
5
Nur ein kleiner Kommentar: fileBeschattet vordefinierte Klasse mit dem gleichen Namen.
Ezdazuzena
4
Dieser Code ändert die Berechtigungen für die Originaldatei. Wie kann ich die ursprünglichen Berechtigungen behalten?
Nic
1
Was ist der Sinn von fh, Sie verwenden es im Aufruf zum Schließen, aber ich sehe keinen Sinn darin, eine Datei zu erstellen, nur um sie zu schließen ...
Wicelo
2
@Wicelo Sie müssen es schließen, um ein Auslaufen des Dateideskriptors zu verhindern. Hier ist eine anständige Erklärung: logilab.org/17873
Thomas Watnedal
1
Ja, ich habe festgestellt, dass mkstemp()ein 2-Tupel zurückgegeben wird, und (fh, abs_path) = fh, abs_pathdas wusste ich nicht, als ich die Frage stellte.
Wicelo
271

Der kürzeste Weg wäre wahrscheinlich die Verwendung des Dateieingabemoduls . Im Folgenden werden beispielsweise Zeilennummern direkt zu einer Datei hinzugefügt:

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

Was hier passiert ist:

  1. Die Originaldatei wird in eine Sicherungsdatei verschoben
  2. Die Standardausgabe wird innerhalb der Schleife in die Originaldatei umgeleitet
  3. Somit printschreiben alle Anweisungen zurück in die Originaldatei

fileinputhat mehr Schnickschnack. Beispielsweise können Sie damit automatisch alle Dateien in sys.args[1:]bearbeiten, ohne dass Sie diese explizit durchlaufen müssen. Ab Python 3.2 bietet es auch einen praktischen Kontextmanager zur Verwendung in einer withAnweisung.


Obwohl fileinputes sich hervorragend für Wegwerfskripte eignet, würde ich es nicht in echtem Code verwenden, da es zugegebenermaßen nicht sehr lesbar oder vertraut ist. In echtem (Produktions-) Code lohnt es sich, nur noch ein paar Zeilen Code auszugeben, um den Prozess explizit und damit lesbar zu machen.

Es gibt zwei Möglichkeiten:

  1. Die Datei ist nicht zu groß, und Sie können sie einfach vollständig in den Speicher lesen. Schließen Sie dann die Datei, öffnen Sie sie erneut im Schreibmodus und schreiben Sie den geänderten Inhalt zurück.
  2. Die Datei ist zu groß, um im Speicher gespeichert zu werden. Sie können es in eine temporäre Datei verschieben und diese öffnen, zeilenweise lesen und in die Originaldatei zurückschreiben. Beachten Sie, dass dies den doppelten Speicherplatz erfordert.
Eli Bendersky
quelle
13
Ich weiß, dass dies nur zwei Zeilen enthält, aber ich denke nicht, dass der Code an sich sehr ausdrucksstark ist. Denn wenn Sie eine Sekunde lang nachdenken und die Funktion nicht kennen, gibt es nur sehr wenige Hinweise darauf, was vor sich geht. Das Drucken der Zeilennummer und der Zeile ist nicht dasselbe wie das Schreiben ... wenn Sie meinen Kern verstehen ...
Chutsu
14
Dieses TUT in die Datei schreiben. Es leitet stdout in die Datei um. Schauen Sie sich die Dokumente an
brice
32
Das Schlüsselbit hier ist das Komma am Ende der print-Anweisung: Es unterdrückt die print-Anweisung und fügt eine weitere neue Zeile hinzu (da die Zeile bereits eine hat). Es ist jedoch überhaupt nicht sehr offensichtlich (weshalb Python 3 diese Syntax zum Glück geändert hat).
VPeric
4
Bitte beachten Sie, dass dies nicht funktioniert, wenn Sie einen Öffnungs-Hook für die Datei bereitstellen, z. B. wenn Sie versuchen, UTF-16-codierte Dateien zu lesen / schreiben.
Bompf
5
Für Python3,print(line, end='')
Ch.Idea
80

Hier ist ein weiteres Beispiel, das getestet wurde und mit Such- und Ersetzungsmustern übereinstimmt:

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)

Anwendungsbeispiel:

replaceAll("/fooBar.txt","Hello\sWorld!$","Goodbye\sWorld.")
Jason
quelle
23
Die Beispielverwendung liefert einen regulären Ausdruck, aber weder Operationen searchExp in linenoch line.replacereguläre Ausdrücke. Sicher ist die Beispielverwendung falsch.
Kojiro
Stattdessen if searchExp in line: line = line.replace(searchExp, replaceExpr)kannst du einfach schreiben line = line.replace(searchExp, replaceExpr). Es wird keine Ausnahme generiert, die Zeile bleibt einfach unverändert.
David Wallace
Hat auch bei mir perfekt funktioniert. Ich war auf eine Reihe anderer Beispiele gestoßen, die dem sehr ähnlich sahen, aber der Trick war die Verwendung von sys.stdout.write(line). Danke noch einmal!
Salbei
Wenn ich dies verwende, wird meine Datei leer. Irgendeine Idee?
Javier López Tomás
Ich benutze dies
Rakib Fiha
64

Dies sollte funktionieren: (Inplace-Bearbeitung)

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
quelle
5
+1. Auch wenn Sie einen RuntimeError: input () erhalten, der bereits aktiv ist, rufen Sie die Datei fileinput.close ()
geographika
1
Beachten Sie, dass filesdies eine Zeichenfolge sein sollte, die den Dateinamen enthält, kein Dateiobjekt .
Atomh33ls
9
print fügt eine neue Zeile hinzu, die möglicherweise bereits vorhanden ist. Um dies zu vermeiden, fügen Sie .rstrip () am Ende Ihrer Ersetzungen hinzu
Guillaume Gendre
Verwenden Sie stattdessen die Dateien arg in input (), es könnte sich um fileinput.input (inplace = 1) handeln, und rufen Sie das Skript als> python replace.py myfiles * .txt
chespinoza
24

Basierend auf der Antwort von Thomas Watnedal. Dies beantwortet jedoch nicht den zeilenweisen Teil der ursprünglichen Frage genau. Die Funktion kann weiterhin von Zeile zu Zeile ersetzt werden

Diese Implementierung ersetzt den Dateiinhalt, ohne temporäre Dateien zu verwenden. Infolgedessen bleiben die Dateiberechtigungen unverändert.

Auch re.sub anstelle von replace ermöglicht das Ersetzen von Regex anstelle des Ersetzens nur von einfachem Text.

Das Lesen der Datei als einzelne Zeichenfolge anstelle von Zeile für Zeile ermöglicht eine mehrzeilige Übereinstimmung und Ersetzung.

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()
Thijs
quelle
2
Möglicherweise möchten Sie beim Öffnen von Dateien rbund wbAttribute verwenden, da dadurch die ursprünglichen Zeilenenden erhalten bleiben
Nux
In Python 3 können Sie 'wb' und 'rb' nicht mit 're' verwenden. Es wird den Fehler "TypeError: kann kein Zeichenfolgenmuster für ein
15

Wie lassevk vorschlägt, schreiben Sie die neue Datei währenddessen aus. Hier ist ein Beispielcode:

fin = open("a.txt")
fout = open("b.txt", "wt")
for line in fin:
    fout.write( line.replace('foo', 'bar') )
fin.close()
fout.close()
hamishmcn
quelle
12

Wenn Sie eine generische Funktion wollen , sind die ersetzt jeden Text mit einem anderen Text, dann ist dies wahrscheinlich der beste Weg zu gehen, vor allem , wenn Sie ein Fan von regex ist sind:

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
quelle
12

Ein pythonischerer Weg wäre die Verwendung von Kontextmanagern wie dem folgenden Code:

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)

Den vollständigen Ausschnitt finden Sie hier .

Kiran
quelle
In Python> = 3.1 können Sie die beiden Kontextmanager in derselben Zeile öffnen .
Florida
4

Erstellen Sie eine neue Datei, kopieren Sie die Zeilen von der alten in die neue und ersetzen Sie sie, bevor Sie die Zeilen in die neue Datei schreiben.

Lasse V. Karlsen
quelle
4

Wenn Sie die Antwort von @ Kiran erweitern, die meiner Meinung nach prägnanter und pythonischer ist, werden Codecs hinzugefügt, die das Lesen und Schreiben von UTF-8 unterstützen:

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)
Zündstrom
quelle
Wird die Berechtigung der alten Datei in der neuen Datei beibehalten?
Bidyut
2

Mit der Antwort von hamishmcn als Vorlage konnte ich in einer Datei nach einer Zeile suchen, die meinem regulären Ausdruck entspricht, und sie durch eine leere Zeichenfolge ersetzen.

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()
Emmanuel
quelle
1
Sie sollten den
Axel
2

fileinput ist ganz einfach, wie in früheren Antworten erwähnt:

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='')

Erläuterung:

  • fileinputkann mehrere Dateien akzeptieren, aber ich ziehe es vor, jede einzelne Datei zu schließen, sobald sie verarbeitet wird. Also einfach file_pathin withAussage gesetzt.
  • printAnweisung druckt nichts, wenn inplace=True, weil STDOUTan die Originaldatei weitergeleitet wird.
  • end=''in printAnweisung ist es, leere Zwischenzeilen zu entfernen.

Kann wie folgt verwendet werden:

file_path = '/path/to/my/file'
replace_in_file(file_path, 'old-text', 'new-text')
Akif
quelle
0

Wenn Sie den Einzug wie folgt entfernen, wird er in mehreren Zeilen gesucht und ersetzt. Siehe unten zum Beispiel.

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)
loi
quelle
Die Formatierung dieses Python-Codes sieht nicht ganz richtig aus ... (Ich habe versucht, das Problem zu beheben, war mir aber nicht sicher, was beabsichtigt war)
Andy Hayden