Ordner überwachen und Zeichen 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
Nierewa
User
Beiträge: 6
Registriert: Mittwoch 10. Juli 2019, 10:24

Hallo,

ich bin blutiger Anfänger in Python und gehe den Weg "learning by doing".

Wa will ich eigentlich machen?
Ich möchte einen Ordner überwachen und wenn darin eine Textdateie erstellt wird, möchte ich in dieser das #-Zeichen durch ein |-Zeichen ersetzen.

Zu erst einmal, ich habe Windows. Dann habe ich habe unter c:\ das Script zeichen_ersetzen.py
Der Ordner der überwacht werden soll ist C:\test

Das Ersetzen funktioniert so sehr schön:

Code: Alles auswählen

import os, sys

path = "C:\\test"
FileName=""

for root, dirs, files in os.walk(path):
    for filename in files:
        if filename.endswith(".txt"): #alle Dateien die auf *.txt enden
           FileName=filename

    with open(FileName) as f:

        newText=f.read().replace('#','|') # alle #-Zeichen durch | erstezen

    with open(FileName, "w") as f: # wieder in die Datei schreiben
        f.write(newText)
        
Aber jetzt zur Überwachung mit watchdog, nach einem Tutorial von Bruno Rocha:

Code: Alles auswählen

import os, sys
import time  
from watchdog.observers import Observer  
from watchdog.events import PatternMatchingEventHandler
path = "C:\\test"
FileName=""

class MyHandler(PatternMatchingEventHandler):
    patterns = ["*.txt"]

    def process(self, event):
        
        """
        event.event_type 
            'modified' | 'created' | 'moved' | 'deleted'
        event.is_directory
            True | False
        event.src_path
            path/to/observed/file
        """
        
        # the file will be processed there
        
        print (event.src_path, event.event_type)  # print now only for degug
        
        for root, dirs, files in os.walk(path):
            for filename in files:
                if filename.endswith(".txt"): #alle Dateien die auf *.txt enden
                   FileName=filename
                 
        with open(FileName) as f:

            newText=f.read().replace('#','|') # alle #-Zeichen durch | erstezen

        with open(FileName, "w") as f: # wieder in die Datei schreiben
            f.write(newText)

    def on_created(self, event):
        self.process(event)

if __name__ == '__main__':
    args = sys.argv[1:]
    observer = Observer()
    observer.schedule(MyHandler(), path)
    observer.start()

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()

    observer.join()
Der Ordner c:\test wird überwacht und es wird auch die Ersetzung ausgeführt, aber nicht im Ordner c:\test sondern in dem Ordner, wo das Sript liegt, also unter c:
Kann mir jemand sagen wo der Fehler liegt?
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Der Code ist ja so wenig sinnvoll. Wenn du auf Ereignisse wartest, dann solltest du auch deren Ergebnisse betrachten, statt einfach alles genauso durchzusieben. Da steht ja schon "event.src_path" - benutz das auch. Was passiert, wenn du deinen eigenen Kram aus dem Beispiel wieder entfernst, und dir nur die Testausgaben geben laesst? Welche Pfade kommen dabei rum?
Nierewa
User
Beiträge: 6
Registriert: Mittwoch 10. Juli 2019, 10:24

Hi,
der Pfad der bei der Ordnerüberwachung rauskommt ist immer richtig.
Nämlich das was ich als pfad="xyz" angegeben habe.

Ich hab es jetzt so gemacht und es funktioniert:

Code: Alles auswählen

FileName=""
        for root, dirs, files in os.walk(path):
            for filename in files:
                if filename.endswith(".txt"): #alle Dateien die auf *.txt enden
                   FileName=filename
        print(FileName)           
        with open([b]path+[/b]FileName) as f:

            newText=f.read().replace('#','|') # alle #-Zeichen durch | erstezen

        with open([b]path+[/b]FileName, "w") as f: # wieder in die Datei schreiben
            f.write(newText)
... statt einfach alles genauso durchzusieben. ...
Wie meinst Du das?
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Nierewa: Das erste Skript funktioniert sicher nicht sehr schön. Das sucht rekursiv nach Dateinamen die auf '.txt' enden. Wenn keine gefunden wird, dann wird versucht eine Datei mit dem Namen '' (leere Zeichenkette) zu öffnen oder mit dem namen der letzten gefundenen Datei aus einem anderen Verzeichnis, was beides kaputt ist.

Falls es mehrere Dateien im aktuell von `os.walk()` besuchten Verzeichnis gibt, deren Dateiname mit '.txt' endet, wird nur der letzte Dateiname weiterverarbeitet. Wobei `os.walk()` keine Reihenfolge der Namen innerhalb einer Verzeichnisses garantiert, der letzte Dateiname ist also relativ zufällig. Und der Dateiname sollte dann im gleichen Verzeichnis liegen wie das Arbeitsverzeichnis des Prozesses, beides also zwingend ``C:\test``, denn sonst wird die Datei nur mit ihrem Namen beim öffnen nicht gefunden werden. Was das `os.walk()` ziemlich sinnfrei erscheinen lässt.

Sonstiges: `sys` wird importiert aber nicht verwendet. Man importiert Module in der Regel auch in einzelnen Anweisungen.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Für Dateinamen innerhalb des selben Namensraums `filename` und `FileName` zu nehmen um die ”auseinander zu halten” geht gar nicht, denn die kann man nur sehr schwer auseinander halten und selbst dann hat man alleine vom Namen keine Ahnung was der semantische Unterschied zwischen den beiden ist.

Einbuchstabige Namen sind selten gute Namen. Wenn man `file` meint, sollte man nicht `f` schreiben sondern `file`.

Wenn man Dateien als Textdateien öffnet, sollte man immer eine Kodierung angeben. Wenn man beliebige Verzeichnishierarchien verarbeitet, kann man in der Regel nicht garantieren, dass die Textdateien alle die gleiche Kodierung haben. Da wäre es sicherer sie als Binärdateien zu öffnen und zu verarbeiten und zu hoffen, das die tatsächliche Kodierung der jeweiligen Datei wenigstens ASCII als Untermenge hat. Ansonsten könnte es Sinn machen vor dem Ersetzen noch auf „byte order marks“ für UTF-16 und UTF-32 zu testen und darauf passend zu reagieren. Gerade unter Windows begegnet man ja doch öfter mal UTF-16.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import os

PATH = r'C:\test'


def main():
    for root, _, filenames in os.walk(PATH):
        for filename in filenames:
            if filename.endswith('.txt'):
                path = os.path.join(root, filename)
                with open(path, 'rb') as file:
                    content = file.read()
                # 
                # TODO An dieser Stelle auf UTF BOMs testen und entsprechend
                #   reagieren.
                # 
                content = content.replace(b'#', b'|')
                
                with open(path, 'wb') as file:
                    file.write(content)


if __name__ == '__main__':
    main()
Bei dem Code mit der Überwachung ist so eine Schleife aber sinnfrei weil Du von der Überwachung ja bereits den Namen der Datei bekommst, und gar nicht mehr alle suchen musst. Das meinte __deets__ mit „statt einfach alles genauso durchzusieben“.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ein weiteres Problem stellt natuerlich auch die fehlende Information daruebe, dass eine Datei *fertig* geschrieben ist dar. Da kommt's dann drauf an, wie das System reagiert, das kann ich nicht sagen. Es kann sein, dass der schreibende Prozess die Datei die er gerade schreibt exklusiv geoeffnet hat. Dann schlaegt dein Versuch die zu oeffnen fehl. Dann musst du immer wieder probieren sie zu oeffnen. Oder er tut das NICHT, und das ist viel schlimmer, denn dann schreibst du eine Datei um, die noch gar nicht zu Ende geschrieben ist. Und endest mit Datensalat.

Wer schreibt denn die Dateien?
Nierewa
User
Beiträge: 6
Registriert: Mittwoch 10. Juli 2019, 10:24

@__blackjack__:
Das nenn ich mal eingekürzt und übersichtlich.
Ws bedeuten die BUchstaben vor den Strings, also zum Beispiel das r vor r'C:\test' oder das b vor b'#'?

@__deets__:
Aus einem Programm wird eine Textdatei erzeugt. Diese ist eigentlich eine csv, Trennzeichen #.
Jetzt kommt ein weiteres Programm welches bereits den Ordner überwacht, nimmt die Textdatei, erstellt einen Bericht und druckt diesen aus. Danach löscht das Programm die Textdatei.
Leider kann die neue Version des Programms mit dem #- Zeichen nichts mehr anfangen und braucht das |-Zeichen. Leider kann das im ersten Programm nicht geändert werden,
so das ich in der Textdatei manuell das Zeichen ersetzen muß.

Mein Plan ist jetzt diesen Ordner zu überwachen, alle Textdateien zu bearbeiten die es gibt, dann das zweite Programm zu starten welches die Textdateien verarbeitet und löscht.
Nierewa
User
Beiträge: 6
Registriert: Mittwoch 10. Juli 2019, 10:24

@__blackjack__:
Der code funktioniert super. Ich hab versucht diesen in der Ordnerüberwachung zu integrieren, bekomm es aber nicht hin.
An welche Stelle muß der Aufruf erfolgen? Innerhalb der class MyHandler(PatternMatchingEventHandler):

Noch eine Frage, was bedeutet dieser Ausdruck:

if __name__ == '__main__':
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Nierewa: r'…' sind ”rohe” (raw) Zeichenketten wo der Backslash (\) keine besondere Bedeutung hat, und b'…' sind literale `bytes`-Werte.

Wenn Du nur *einen* Ordner überwachen musst, dann brauchst Du kein `os.walk()` sondern einfach nur `os.scandir()` oder `os.listdir()`.

Unter Windows ist es leider schwierig bis unmöglich (wenn man im Userland bleiben möchte) allgemein zuverlässig zu erkennen ob/wann eine Datei fertig geschrieben ist. Einfach kann man nur erkennen wenn eine Datei umbenannt wird, und wann sie verändert wird. Zuverlässig geht also nur das der Prozess der die Datei erstellt, die woanders oder unter anderem Namen erstellt, und dann in das überwachte Verzeichnis hinein oder zum Endgültigen Namen umbenennt wenn sie fertig ist. Oder das der Code der auf die Dateiänderungen reagiert anhand der Daten innerhalb der Datei feststellen kann ob sie vollständig sind oder nicht.

Falls das Programm welches die Datei erstellt nach dem erstellen beendet wird, könnte man auch darauf prüfen. Also auf Dateiveränderungen warten und schauen ob ein Exemplar von dem Programm läuft testen kombinieren, und nur dann reagieren wenn es eine oder mehrere neue Dateien gibt, und kein Prozess von dem erzeugenden Programm mehr gibt, denn dann ist die Datei vollständig (sofern das Programm nicht mitten beim Erzeugen abgebrochen hat/wurde).

`__name__` hat als Wert den Namen des Moduls – ausser wenn das Modul nicht importiert, sondern als Programm gestartet wurde. In dem Fall ist `__name__` an den Wert '__main__' gebunden.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Nierewa
User
Beiträge: 6
Registriert: Mittwoch 10. Juli 2019, 10:24

Unter Windows ist es leider schwierig bis unmöglich (wenn man im Userland bleiben möchte) allgemein zuverlässig zu erkennen ob/wann eine Datei fertig geschrieben ist.
Ich verstehe. Hmm... das ist natürlich ein Pronlem.
Darüber hatte ich mir noch gar keine Gedanken gemacht bzw. wäre mir das gar nicht in den Sinn gekommen.
Das Programm läuft leider weiter, so das man darauf nicht prüfen kann.
...
Auf der anderen Seite funktioniert das Polling mit Programm 2 ja auch, nur dass das ja in der alten Version nicht mehr genutz werden kann.

Kommt wohl auf einen Versuch an.

Wenn ich das Script fertig habe, würde ich es gern mal hier einstellen.
Könntet ihr dann mal drüber schauen?
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

"Funktioniert seit zwei Jahren" ist natuerlich relativ. Wuesstest du denn, wenn da was schiefgeht? Und weisst du, was das Programm macht, um ggf sicherzustellen, das so etwas nicht passiert? Also zB interne Konsistenz der Datei irgendwie zu pruefen? Oder einfach lange zu warten, nachdem es eine Datei gesehen hat, um heuristisch davon auszugehen, dass die Datei dann fertig geschrieben wurde? Denn *DEIN* Skript macht all diese Dinge nicht, und produziert dadurch ggf. korrupte Daten.
Nierewa
User
Beiträge: 6
Registriert: Mittwoch 10. Juli 2019, 10:24

Da fällt mir ein, könnte man nicht nach dem EOF schauen wie bei PHP?
Geht das unter Python?
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Was soll das sein? Dein OS weiß nicht, wann eine date zu Ende beschrieben ist. Das einzige was sein kann, und was dich da ggf rettet, ist das die Dateien gesperrt sind während sie das schreibende Programm erstellt. Das musst du eben prüfen.
Antworten