String in Datei ersetzen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Firestone
User
Beiträge: 5
Registriert: Montag 21. November 2022, 17:58

Hallo zusammen,

ich habe mich erst ganz frisch mit Python beschäftigt, bin also absoluter Neuling, daher seht es mir bitte nach, wenn das zu einfach für euch ist.
Habe ein kleines Script gebastelt welches mir in vielen Dateien, automatisiert immer den gleichen String gegen nichts ersetzen soll (also einfach die Zeile löschen).
Mit ganz normalen Textdateien und einem ganz einfachen String (drei einfache Worte) hat alles super funktioniert, aber mit HTML Seiten, aus denen ich eine Zeile in Form eines Link Tags löschen möchte, geht es leider nicht. Folgenden Code habe ich bisher.

Code: Alles auswählen

import os

i=1 #nur debug
textout = '<link href="https://www.xyz.de/modules/mod_jbcookies/assets/css/jbcookies.css?6b6e0028f4c0076981af93b286e93945" rel="stylesheet" />'
textin = ''
extension_filter = '.html'
path = 'C:\\Users\\Firestone\\Desktop\\test1'

webfiles = [os.path.join(dirpath, f)
    for dirpath, dirnames, files in os.walk(path)
    for f in files if f.endswith(extension_filter)]

for datei in webfiles:
    
    if open(datei, "rt").read().find(textout) == 0:
        with open(datei, 'r', errors='ignore') as file:
            data = file.read()
            data = data.replace(textout, textin)
        with open(datei, 'w', errors='ignore') as file:
            file.write(data)
            i =i+1
            print(i) #sind Dateien angepasst worden?
Ich bekomme folgende Fehlermeldung.

Traceback (most recent call last):
File "c:\Users\Firestone\Desktop\test\import os.py", line 15, in <module>
if open(datei, "rt").read().find(textout) == 0:
File "C:\Users\Firestone\AppData\Local\Programs\Python\Python310\lib\encodings\cp1252.py", line 23, in decode
return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x90 in position 44665: character maps to <undefined>

Was ich schon probiert habe ist die open Methode mit encoding="utf8" und mit errors='ignore' zu nutzen.
Dann war zwar die Fehlermeldung weg und das Script läuft durch, aber der String wurde in den HTML Dateien nicht gelöscht.

Könnt ihr mir irgendwie mit einfachen Worten erklären was ich falsch mache, bzw. wie ich das Vorhaben umsetzen kann?

Herzlichen Dank und viele Grüße
Hans
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Firestone: Die Benennungen von `textout` und `textin` sehen irgendwie genau falsch herum aus. Bei den Namen fehlen Unterstriche. DennWortekannmandeutlichbesserlesenwennsieoptischgetrenntsind.

Beim öffnen von Textdateien sollte man immer die Kodierung mit angeben und `errors` auf "ignore" zu setzen ist keine gute Idee, weil dann Daten verändert werden können, weil bei fehlerhafter Kodierungsangabe oder fehlerhaften Daten dann einfach Daten still und leise ignoriert werden und am Ende fehlen.

Dateien die man öffnet, sollte man auch wieder schliessen. Das wird mit der Datei in der ``if``-Bedingung nicht gemacht. Das ist aber auch sowieso ineffizient erst die gesamte Datei einzulesen um einen Text zu finden, und falls der gefunden wurde, die gesamte Datei noch einmal einzulesen.

Der Test ob der Text enthalten ist, ist sehr wahrscheinlich auch falsch, denn `find()` ergibt ja nur 0 falls der gesuchte Text genau als allererstes in der Datei steht. Ob ein Text enthalten ist, prüft man normalerweise mit dem ``in``-Operator.

Wobei dieser Test auch nicht wirklich nötig/sinnvoll ist. Das sucht ja nach dem Text. Falls er gefunden wurde, wird er mit `replace()` ersetzt, aber `replace()` sucht dann selbst erst mal wieder nach dem Test. Ein zweites mal. Einfacher wäre einfach nur `replace()` aufzurufen und dann den alten mit dem neuen Text zu vergleichen. Falls er gleich ist, war der Text nicht enthalten. Falls er unterschiedlich ist, muss der geänderte Text in die Datei geschrieben werden.

In neuem Code würde man `pathlib` verwenden, anstelle von `os.path` & Co. `Path`-Objekte haben beispielsweise auch praktische Methoden zum einlesen und schreiben kompletter Dateien.

Was im Code `webfiles` und `datei` genannt wird sind gar keine Dateien sondern Datei*namen*. Wenn man etwas `datei` oder `file` nennt, erwartet der Leser Objekte mit Methoden wie `read()`, `write()`, und `close()`.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Firestone
User
Beiträge: 5
Registriert: Montag 21. November 2022, 17:58

@blackjack
Hm, jetzt kenne ich alle Kosmetik und in und out ist auch nicht verkehrt herum.
Mit dem zig mal Öffnen und Schließen hast du Recht (danke dafür), das geht in der Tat viel besser wenn ich deine Info so lese, aber das Script funktioniert mit einem String, der nicht so cryptisch ist in txt Dateien hervorragend und ich brauche es auch nur einmal.
Mein eigentliches Problem (scheinbar zu cryptischer String in HTML Datei) ist mit dem neu erlangten Wissen jetzt allerdings nicht erklärt.

Hast du evtl. für mein eigentliches Problem eine Lösung?

Danke und Gruß
Hans
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Firestone: So wie ich die Aufgabe verstanden habe, funktioniert das auch nicht mit anderen Zeichenketten. Es sei denn, und darauf bis Du nicht eingegangen, die gesuchte Zeichenkette steht immer direkt am Anfang der Datei. Was ich für den gezeigten gesuchten Text für unwahrscheinlich halte. Und dafür habe ich eine Lösung genannt. Zumindest ich persönlich empfinde die auch nicht als Kosmetik, denn `find()` hat eine schlechte, fehleranfällige API und halt das falsche Mittel um ”enthaltensein” zu testen. Dafür gibt es *einen* vorgesehenen Weg.

Schliessen von Dateien ist keine Kosmetik und `pathlib` würde ich auch nicht als Kosmetik sehen. Es ist eine bessere API, die Sachen vereinfacht, und damit lesbarer und weniger fehleranfällig macht. Das ist nicht einfach nur irgendwie ”hübscher”, als wenn jemand ein buntest Schleifchen ohne weiteren Mehrwert dran gebunden hätte.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Firestone
User
Beiträge: 5
Registriert: Montag 21. November 2022, 17:58

Ich habe ein anderes Verzeichnis referenziert in dem ich 4 Text Dateien angelegt habe. In der ersten Datei habe ich einen Text rein kopiert und daraus einen Teilstring benutzt. In der zweiten Datei, den gleichen Text zwei mal, in der dritten Datei drei mal, usw. Dann habe ich noch ein Verzeichnis in den Verzeichnis angelegt und die gleichen Dateien dort rein kopiert, um zu testen, ob das auch mit Unterordnern funktioniert.
Hat in allen Dateien immer den gesuchten Teilstring ersetzt, ganz egal wo in der Datei der String steht.
Probiere es gern selbst, dann brauchst du es nicht mehr anzweifeln.
Ich werde das aber morgen auf alle Fälle mal so umbauen wie du es beschrieben hast (außer vielleicht die Dateinamenkosmetik) und auch die Datei wieder schließen
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich kann mir schon denken, warum der falsche Code bei Dir scheinbar richtige Ergebnisse liefert.

Und auch wenn etwas funktioniert, sollte man es nicht so umständlich kompliziert schreiben, denn irgendwer will das ja auch lesen und verstehen, um sicher zu gehen, dass es keine Fehler enthält.

Code: Alles auswählen

from pathlib import Path

PATH = Path.home() / 'Desktop' / 'test1'
LINK = '<link href="https://www.xyz.de/modules/mod_jbcookies/assets/css/jbcookies.css?6b6e0028f4c0076981af93b286e93945" rel="stylesheet" />'
REPLACEMENT = ''

def main():
    for path in PATH.rglob('*.html'):
        data = path.read_text(encoding='cp1251', errors='ignore')
        if LINK in data:
            data = data.replace(LINK, REPLACEMENT)
            path.write_text(data, encoding='cp1251')

if __name__ == "__main__":
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Firestone: Das hier gezeigte Programm findet den Text nur exakt am Anfang der Datei. Probiere es gerne selbst. Die ``if``-Bedingung sagt das *sehr* deutlich aus. Ich habe da keine Zweifel.

@Sirius3: Sollte ich doch welche haben?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Unter der Annahme, dass die Dateien eine Kodierung verwenden die ASCII als Untermenge hat, das ganze auf Bytes-Basis (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
from pathlib import Path

PATH = Path.home() / "Desktop" / "test1"
LINK = (
    b'<link href="https://www.xyz.de/modules/mod_jbcookies/assets/css/'
    b'jbcookies.css?6b6e0028f4c0076981af93b286e93945" rel="stylesheet" />'
)
REPLACEMENT = b""


def main():
    for path in PATH.rglob("*.html"):
        if path.is_file():
            old_data = path.read_bytes()
            new_data = old_data.replace(LINK, REPLACEMENT)
            if new_data != old_data:
                path.write_bytes(new_data)


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Firestone
User
Beiträge: 5
Registriert: Montag 21. November 2022, 17:58

Hallo zusammen,
wenn ich euch irgendwo ein Video hoch laden kann, dann zeige ich gern das definitiv alle Strings in einer Datei ersetzt werden, ich beharre doch hier nicht ewig auf einer Lüge. Ich glaube aber sofort, dass das so nicht sauber programmiert ist.
Ich schaue mir heute Abend euern Code einmal genau an, will es ja auch verstehen.
Ich sage schon mal herzlichen Dank und werde berichten.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Firestone: Deine Dateien fangen mit dem Suchtext an, so dass find den Index 0 liefert. Wenn das bei Dir immer der Fall ist, wäre die Prüfung data.startswith(LINK) klarer.
Hier nochmal zur Verdeutlichung:

Code: Alles auswählen

In [1]: textout = '<link href="https://www.xyz.de/modules/mod_jbcookies/assets/css/jbcookies.css?6b6e0028f4c0076981af93b286e93945" rel="stylesheet" />'

In [2]: textout.find(textout) == 0
Out[2]: True

In [3]: ("<body>"+textout).find(textout) == 0
Out[3]: False

In [4]: ("<body>"+textout).find(textout)
Out[4]: 6
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Dazu braucht es kein Video, es reicht das Du zeigen kannst wie Du mit *der* ``if``-Bedingung Text finden kannst der nicht am Anfang sondern mitten drin steht. Das geht nicht. Ich beharre ja auch nicht auf einer Lüge. `find()` liefert den Index der Fundstelle und der Code vergleicht den mit 0, also mit dem Anfang. Sollte der Text an einer anderen Stelle stehen, liefert `find()` nicht 0 und damit trifft die Bedingung nicht zu und damit ersetzt das nix, weil der ``if``-Zweig nicht betreten wird.

Code: Alles auswählen

In [302]: "abc".find("a")
Out[302]: 0

In [303]: "abc".find("b")
Out[303]: 1

In [304]: "abc".find("c")
Out[304]: 2

In [305]: "abc".find("a") == 0
Out[305]: True

In [306]: "abc".find("b") == 0
Out[306]: False

In [307]: "abc".find("c") == 0
Out[307]: False
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Firestone
User
Beiträge: 5
Registriert: Montag 21. November 2022, 17:58

Hallo zusammen,

sorry, dass ich gestern nicht mehr dazu gekommen bin, aber herzlichen Dank schonmal vorab an euch beide.
Auch für die guten Erklärungen.

@Sirius3
Dein Code hat sofort auf anhieb funktioniert, nur den korrekten Link rein kopiert (xyz gegen die richtige URL), gestartet und schon hat es aus den unzähligen HTMLs überall den String entfernt.
Ich werde mich jetzt einmal mit dem Code genauer auseinandersetzen, das ist so sehr interessant.
Vom Gefühl her und der Fehlermeldung in meinem besch. Code und was ich dazu auf Stackoverflow gefunden habe musste es bei mir (neben meinem besch. Code) an dem "encoding" gelegen haben, ich konnte nur nicht finden wie es richtig geht.

@blackjack
Der Code funktioniert genau so gut wie der von Sirius.
Hab hier auch den Link eingefügt (so wie du ihn aufgetrennt hattest). Komischerweise hast du die o.g. Theorie mit dem "encoding" widerlegt und es irgendwie ohne diesen Parameter gemacht. Irgendwie hast du es scheinbar nicht als encodierter Text, sondern byteweise eingelesen.
Würde mich interessieren, ob ich das so richtig interpretiere??? Gibt es diese zwei Wege eine Datei einzulesen und zu schreiben?

Alles in allem hochinteressant und euer Code sieht natürlich super aus.

ABER @blackjack
Und wenn du dich auf den Kopf stellst, mein besch. Code replaced nicht nur am Anfang der Datei, sondern auch alle Zeilen mit dem gesuchten String die mitten drin sind.
Du wolltest das ich es zeige, ich habe die Screenshots in einer Dropbox als Beweis zur Verfügung gestellt. (Edit: habe leider erst jetzt gemerkt, dass man hier keine Screenshots einfügen kann). Ich habe mir jetzt extra nochmal die Mühe gemacht und alles so erstellt, dass man es schön zeigen kann.
Das kann ich so einfach nicht stehen lassen und ich verstehe nicht warum du nicht einfach einmal copy und paste machst und es selbst versuchst.
Das ist ein ganz klarer Fall von "Theoretisch gibt es zwischen Theorie und Praxis keinen Unterschied. Praktisch gibt es ihn aber."
Wie gesagt ich mache gern ein Video und stelle es dort rein, wenn du mir das noch immer nicht glaubst.
Mich würde jetzt vielmehr interessieren, wie das sein kann, nach eurer durchaus plausiblen Erklärung?
Geh bitte einmal davon aus, dass ich euch hier nicht irgend etwas vormache. Hättest du eine Erklärung, wieso das trotzdem funktioniert?

Hier der Link zur Box: https://app.box.com/s/gagoayh7q0e78vxb77a93hwgqntu1i9v

Danke euch nochmals und Gruß
Hans
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Niemand hier hat behauptet, dass nicht alle Vorkommen ersetzt werden, wir haben Dir nur die ganze Zeit gesagt, dass um überhaupt in den if-Block zu kommen, es zwingend ist, dass der zu ersetzende Text auch ganz am Anfang steht. Das heißt, Deine Dateien müssen diese spezielle Eigenschaft haben. Dass Deine Test-Dateien so aufgebaut sind, mag ja sein, die Frage ist, ob die richtigen Dateien auch den zu ersetzenden Text ganz am Anfang stehen haben?

Da kannst Du Dich auf den Kopf stellen, nur das hatten wir behauptet, und auch bewiesen.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Firestone: Ich habe die Theorie mit der Kodierung nicht widerlegt, weil in meinem Code gar nicht (de)kodiert wird. Dateien enthalten Bytes, also eine Zahlenfolge mit Werten zwischen 0 und 255. Text kennt der Rechner nicht. Das passiert erst dadurch, das man Zahlen oder Zahlenfolgen Zeichen zuordnet. Also beispielsweise das der Wert 65 für ein grosses "A" steht, oder die Zahlenfolge 195 und 159 für ein kleines "ß". Und diese Zuordnung ist die Kodierung. Da gibt es aber leider nicht *eine*, sondern viele verschiedene, historisch teilweise je nach Computer- oder Betriebssystem und dort dann oft auch je nach eingestellter Sprache oder Gebietsschema unterschiedliche. Weil man mit Zuordnungen die genau ein Byte einem Zeichen zuordnen, nur 256 mögliche Zeichen hat und neben Buchstaben, Ziffern, und Sonderzeichen für Interpunktion, Symbolen für Rechenoperationen und so weiter, in der Regel auch noch ein paar Steuerzeichen und oft auch ein paar grafische Symbole, zum Beispiel zum ”zeichnen” von Rahmen in diesen Zeichen untergebracht werden. Alleine für Europa wird es eng bis überfüllt, wenn da alle möglichen Varianten von lateinischen Buchstaben, also Umlaute, Zeichen mit Accents, das griechische Alphabet in Klein- und Grossbuchstaben, und so weiter.

Was viele Kodierungen gemeinsam haben, ist das die erste Hälfte, also die ersten 128 Bytewerte der ASCII-Kodierung entsprechen, also das sich die meisten Kodierungen einig darüber sind welcher Zahlenfolge Dein gesuchter Text entspricht. Und Python erlaubt es Bytefolgen als ASCII-Zeichen anzugeben, was ich in meiner Programmvariante auch gemacht habe.

Und dann kann man die Bytes aus der Datei als solche einlesen ohne sie in eine Zeichenkette umwandeln zu müssen, also ohne das man eine Kodierung braucht nach der Python dann die Bytefolge aus der Datei in eine Zeichenfolge im Programm zu wandeln. Und in der Rückrichtung muss dann aus der Zeichenfolge nicht wieder eine Bytefolge gemacht werden, sondern die Bytewerte aus dem Speicher können einfach 1:1 wieder in eine Datei geschrieben werden.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten