Verzeichnisbäume miteinander vergleichen

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
Spam&Eggs
User
Beiträge: 15
Registriert: Donnerstag 15. Februar 2018, 22:09

Hallo,
ich würde gerne überprüfen, welche Dateien sich in einem Verzeichnisbaum (dir_a) befinden, die nicht in einem anderen Verzeichnisbaum (dir_b) enthalten sind.

Hierzu habe ich u. a. kleines Programm geschrieben. Leider Funktioniert es nicht wie gedacht, da aufgrund des unterschiedlichen Wurzelverzeichnisses alle Dateien in dir_a ausgegeben werden.

Vielleicht kann mit einer bei der Lösung des Problems helfen.
Vielen Dank im Voraus
Helmut

Code: Alles auswählen

# !/usr/local/bin/python3
# coding: utf-8


"""
Vergleiche den Inhalt von zwei Verzeichnisbäumen.
"""

import os


def main():
    """
    Framework für den Aufruf der Funktion, die die beiden Verzeichnisse vergleicht.
    """

    dir_a, dir_b = "", ""

    # Hole den Pfad zum ersten Verzeichnisbaums vom Nutzer:
    pfad_ok = False
    while not pfad_ok:
        print("\nGebe den Pfad zum ersten Verzeichnis ein:\n")
        dir_a = input()

        # Prüfe, ob das eingegebene Verzeichnis existiert:
        if os.path.exists(dir_a):
            pfad_ok = True
        else:
            print("Das erste Verzeichnis existiert nicht, versuche es noch "
                  "einmal.")

    # Hole den Pfad zum zweiten Verzeichnisbaums vom Nutzer:
    pfad_ok = False
    while not pfad_ok:
        print("\nGebe den Pfad zum zweiten Verzeichnis ein:\n")
        dir_b = input()

        # Prüfe, ob der eingegebene Pfad existiert:
        if os.path.exists(dir_b):
            pfad_ok = True
        else:
            print("Das zweite Verzeichnis existiert nicht, versuche es "
                  "noch einmal.")

    # Generiere die Dateilisten:
    dateiliste_1 = get_filepaths(dir_a)
    dateiliste_2 = get_filepaths(dir_b)

    # Vergleiche die Dateilisten und gebe die Unterschiede aus:
    dateiset_1 = set(dateiliste_1)
    dateiset_2 = set(dateiliste_2)
    print(dateiset_1 - dateiset_2)


def get_filepaths(verzeichnis):
    """
    Erzeuge die Dateinamen in einem Verzeichnisbaum, indem der Baum von oben nach
    unten durchlaufen wird.
    Befülle für jedes durchlaufene Verzeichnis ein Tuple mit 'pfad', 'verzeichnisname'
    und 'dateiname'
    """

    dateiliste = []     # Liste, die alle Dateien im übergebenen Verzeichnis enthält.

    # Durchlaufe den Verzeichnisbaum:
    for root, directories, files in os.walk(verzeichnis):
        for filename in files:
            if filename.startswith("."):    # Ignoriere versteckte Dateien
                continue
            # Füge die beiden Strings für den kompletten Pfad zusammen:
            dateipfad = os.path.join(root, filename)
            dateiliste.append(dateipfad)

    return dateiliste


# Wenn der Code eigenständig laufen soll und nicht importiert werden soll, rufe die
# Funktion main() auf:
if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Initialisierung von `dir_a` und `dir_b` am Anfang von `main` sind unnötig.
Statt `pfad_ok` schreib eine while-True-Schleife, die Du per `break` verläßt.
Du mußt die Pfade relativ zu verzeichnis bestimmen, also os.path.relpath(dateipfad, verzeichnis)`.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Spam&Eggs: Du musst halt das jeweilige Wurzelverzeichnis aus den Pfaden entfernen.

Der Docstring von `get_filepaths()` ist falsch. Da wird was beschrieben was die Funktion gar nicht macht.

Der Code der die Pfade vom Benutzer erfragt steht fast gleich zweimal da, das sollte man in eine Funktion auslagern.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Spam&Eggs
User
Beiträge: 15
Registriert: Donnerstag 15. Februar 2018, 22:09

@__blackjack__
@sirius3

Vielen Dank für Eure Hilfe! Ich glaube so ist es jetzt Ok (s. u.), es funktioniert zumindest.

Code: Alles auswählen

# !/usr/local/bin/python3
# coding: utf-8


"""
Vergleiche den Inhalt von zwei Verzeichnisbäumen.
"""

import os


def main():
    """
    Framework für den Aufruf der Funktion, die die beiden Verzeichnisse vergleicht.
    """

    dir_a = get_pathname(1)
    dir_b = get_pathname(2)

    # Generiere die Dateilisten:
    dateiliste_1 = get_filepaths(dir_a)
    dateiliste_2 = get_filepaths(dir_b)

    # Vergleiche die Dateilisten und gebe die Unterschiede aus:
    dateiset_1 = set(dateiliste_1)
    dateiset_2 = set(dateiliste_2)
    print(dateiset_1 - dateiset_2)


def get_pathname(nr):
    """Hole den Pfad vom Nutzer"""
    while True:
        print("\nGebe den Pfad zum ", str(nr) + ".", "Verzeichnis ein:\n")
        pfad = input()

        # Prüfe, ob das eingegebene Verzeichnis existiert:
        if os.path.exists(pfad):
            break
        else:
            print("Das Verzeichnis existiert nicht, versuche es noch "
                  "einmal.")
    return pfad


def get_filepaths(verzeichnis):
    """
    Erzeuge eine Dateiliste mit allen Dateipfaden in dem übergebenen Verzeichnis
    """

    dateiliste = []     # Liste, die alle Dateien im übergebenen Verzeichnis enthält.

    # Durchlaufe den Verzeichnisbaum:
    for root, directories, files in os.walk(verzeichnis):
        for filename in files:
            if filename.startswith("."):    # Ignoriere versteckte Dateien
                continue
            # Füge die beiden Strings für den kompletten Pfad zusammen:
            dateipfad = os.path.join(filename)
            dateiliste.append(dateipfad)

    return dateiliste


# Wenn der Code eigenständig laufen soll und nicht importiert werden soll, rufe die
# Funktion main() auf:
if __name__ == '__main__':
    main()
Spam&Eggs
User
Beiträge: 15
Registriert: Donnerstag 15. Februar 2018, 22:09

@sinus3

Funktioniert doch noch nicht wie gewünscht. Dateien die in einem Unterverzeichnis von dir_a liegen werden nun nicht mehr angezeigt. Ich vermute es liegt an:
Du mußt die Pfade relativ zu verzeichnis bestimmen, also os.path.relpath(dateipfad, verzeichnis)
Das habe ich noch nicht verstanden. Vielleicht könntest Du den Teil meines Programms einmal korrigieren und posten.

Helmut
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Hast Du in der Dokumentation nachgeschaut, was relpath macht, und es dann interaktiv ausprobiert?
Spam&Eggs
User
Beiträge: 15
Registriert: Donnerstag 15. Februar 2018, 22:09

Hallo Sirus3,

ja, habe ich gemacht. Habe auch versucht es in meinem Programm umzusetzen, kann aber nicht die richtige Stelle finden, wo ich os.path.relpath() einsetzen muss.

Hier noch einmal meine Aufgabenstellung zur Verdeutlichung:
  • Es existiert ein Verzeichnis "dir_A"
  • "dir_A" wird nach "dir_B" kopiert, um den Inhalt von "dir_A" zu einem bestimmten Zeitpunkt zu sichern.
  • Zu einem späteren Zeitpunkt wird "dir_A" in irgendeinem Unterverzeichnis um eine Datei "file_neu" ergänzt.
  • Das Programm soll nun "dir_A" mit "dir_B" vergleichen und nur die Änderungen, also in diesem Bsp. "file_neu" ausgeben.
Z.Zt. werden eben alle Dateien in allen Unterverzeichnissen von "dir_A" ausgegeben.

Irgendwo mache ich offensichtlich einen fundamentalen Denkfehler.

Vielleicht hast Du ja noch einen Tipp oder kannst mir sagen, wo ich mein Programm anpassen muss.

Gruß Helmut
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Spam&Eggs: die Aufgabenstellung ist zumindest mir klar. Die Lösung ist auch relativ einfach, wenn man den Ablauf des Programms verstanden hat.
Daher meine Fragen an Dich:
- Was macht relpath?
- Was ist die Ausgabe von get_filepaths jeweils für die beiden Verzeichnisse?
- Warum funktioniert der Abgleich folglich nicht?
Spam&Eggs
User
Beiträge: 15
Registriert: Donnerstag 15. Februar 2018, 22:09

Also dann versuche ich es noch einmal.
- Was macht relpath?
os.path.relpath(pfad) gibt den relativen Pfad zu dem als Argument übergebenen absoluten Pfad zurück.
- Was ist die Ausgabe von get_filepaths jeweils für die beiden Verzeichnisse?
Mit dem derzeitigen Code und dem Test-Filesystem auf meinem Desktop enthält die dateiliste_1alle Files in der Form:
"'/Users/helmut/Desktop/dir_A/file_A.jpg', '/Users/helmut/Desktop/dir_A/dir_2/file_2.jpg', ... usw."

Die dateiliste_2 enthält mit Ausnahme des neuen Files das Gleiche nur eben in der Form:
'/Users/helmut/Desktop/dir_B/file_A.jpg', '/Users/helmut/Desktop/dir_B/dir_2/file_2.jpg', ... usw."

- Warum funktioniert der Abgleich folglich nicht?
Weil in dateiliste_1 jeder Dateipfad mit "'/Users/helmut/Desktop/dir_A" beginnt und in dateiliste_2 mit "'/Users/helmut/Desktop/dir_B"

Ich habe mal die Funktion get_pathname(nr) so abgeändert, dass der relative Pfad zurückgeliefert wird:

Code: Alles auswählen

def get_pathname(nr):
    """Hole den Pfad vom Nutzer"""

    while True:
        print("\nGebe den Pfad zum ", str(nr) + ".", "Verzeichnis ein:\n")
        pfad = os.path.relpath(input())

        # Prüfe, ob das eingegebene Verzeichnis existiert:
        if os.path.exists(pfad):
            return pfad
        else:
            print("Das Verzeichnis existiert nicht, versuche es noch "
                  "einmal.")
Das Ergebnis ist das Gleiche, nur dass jetzt eben
['../../../../../Users/helmut/Desktop/dir_A/file_A.jpg ... usw.'
bzw.
['../../../../../Users/helmut/Desktop/dir_B/file_A.jpg ... usw.'
vor jedem Dateinamen steht.

Ich habe keine Ahnung wo ich das Programm noch ändern könnte.Frust!
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Wie schon in meinem ersten Post gezeigt, gibt es eine Variante von relpath mit zwei Argumenten, und die Argumentnamen passen sogar zu Deinem aktuellen Code.
Spam&Eggs
User
Beiträge: 15
Registriert: Donnerstag 15. Februar 2018, 22:09

Das war nun mein letzter Versuch:

Code: Alles auswählen

dir_a = os.path.relpath("/Users/helmut/Desktop", "dir_A")
dir_b = os.path.relpath("/Users/helmut/Desktop", "dir_B")
Als Ergebnis bekomme ich nun ein leeres Set. Auch das ist nicht was ich wollte :x . Ich gebe auf!!!
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

relpath gibt dir einen relativen Pfad als ERGEBNIS zu zwei ABSOLUTEN Pfaden als Argument. Du gibst aber *einen* absoluten, und einen *relativen* rein. Der relative wird in dem Moment aufgefasst im Verhaeltnis zum current working directory. Das sonstwo sein kann.

Du musst schon zwei absolute Pfade da reingeben, damit das sinnvoll ist.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Code: Alles auswählen

>>> os.path.relpath("/Users/helmut/Desktop/dir_A/dir_2/file_2.jpg", "/Users/helmut/Desktop/dir_A")
'dir_2/file_2.jpg'
Spam&Eggs
User
Beiträge: 15
Registriert: Donnerstag 15. Februar 2018, 22:09

>>> os.path.relpath("/Users/helmut/Desktop/dir_A/dir_2/file_2.jpg", "/Users/helmut/Desktop/dir_A")
'dir_2/file_2.jpg'
@Sirius3: Sorry, aber das löst doch nicht mein Problem. Dein Beispiel leuchtet mir schon ein, aber in meinem Szenario sollen die beiden Verzeichnisbäume dir_A und dir_B von oben nach unten durchlaufen werden und die Unterschiede in einem Set gespeichert werden.
Ich kann in os.path.relpath() aber doch nicht vorgeben, wo das Durchlaufen des Verzeichnisbaums endet. Wenn ich nur einem Ast in dem Verzeichnisbaum folgen wollte, ginge das aber was ist dann mit den anderen Ästen.

Vielleicht muss ich das Problem ganz neu denken.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Natuerlich hilft dir das. Wenn du dir_A durchlaufst, und mit relpath die Pfade *relativ* dazu aufsammelst, ist das deine Menge a. Und dannm machst du das mit dir_B genauso, und hast Menge b. Und dann kannst du dir die Unterschiede bequem mit a-b und b-a ausgeben lassen.
Spam&Eggs
User
Beiträge: 15
Registriert: Donnerstag 15. Februar 2018, 22:09

Puh, :P dass hat gedauert aber ich glaube jetzt hab ich es verstanden. :idea:
Der Fehler lag wohl in der get_filepaths Funktion. Ich glaube so ist es jetzt OK:

Code: Alles auswählen

def get_filepaths(verzeichnis):
    """Erzeuge eine Dateiliste mit allen Dateipfaden in dem übergebenen Verzeichnis"""

    dateiliste = []     # Liste, die alle Dateien im übergebenen Verzeichnis enthält.

    # Durchlaufe den Verzeichnisbaum:
    for root, directories, files in os.walk(verzeichnis):
        for filename in files:

            # Füge die beiden Strings für den kompletten Pfad zusammen:
            dateipfad = os.path.join(os.path.relpath(verzeichnis, root), filename)
            dateiliste.append(dateipfad)

    return dateiliste
als Ergebnis erhalte ich jetzt:

Code: Alles auswählen

{'../../file_neu.jpg', './file_A.jpg', '../file_neu.jpg'}
Das Ergebnis stimmt jetzt. Unschön ist noch, dass
1.Die in dir_A fehlenden Dateien unsortiert ausgegeben werden.
2. Die Pfade zu den fehlenden Dateien relativ ausgegeben werden.

zu 1. Das ist bei einem Set wohl nicht zu ändern.
zu 2. Bei relativ wenigen Änderungen ist das kein Problem. Bei großen, stark verzweigten Verzeichnissen mit vielen Änderungen kann das etwas unübersichtlich sein.

Mit beiden kleinen Schönheitsfehlern kann ich aber gut leben.

Nochmals herzlichen Dank an Alle für die Zeit, die ihr für mich aufgewendet habt. Ich habe auf jeden Fall einiges gelernt.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Spam&Eggs: zu 1) Verzeichnisse sind unsortiert, aber man kann ja nachträglich noch sortieren. zu 2) du benutzt relpath falsch, vergleiche nochmal mein Beispiel mit Deinem Code.
Spam&Eggs
User
Beiträge: 15
Registriert: Donnerstag 15. Februar 2018, 22:09

@sirius3: Ich hoffe in u. a. Version ist jetzt alles OK. Die Pfade werden nicht mehr relativ ausgegeben und es ist auch alles schön sortiert:

Code: Alles auswählen

# !/usr/local/bin/python3
# coding: utf-8


"""
Vergleiche den Inhalt von zwei Verzeichnisbäumen.
"""

import os


def main():
    """
    Framework für den Aufruf der Funktion, die die beiden Verzeichnisse vergleicht.
    """

    # Hole die Pfade zu den zu vergleichenden Verzeichnissen vom Nutzer
    dir_a = get_pathname(1)
    dir_b = get_pathname(2)

    # Generiere die Dateilisten:
    dateiliste_1 = get_filepaths(dir_a)
    dateiliste_2 = get_filepaths(dir_b)

    # Vergleiche die Dateilisten und gebe die Unterschiede aus:
    diff_set = set(dateiliste_2) - set(dateiliste_1)

    # Differenzset zum sortieren und zur Ausgabe in Liste umwandeln
    diff_liste = list(diff_set)
    print("\nDateien die im Originalverzeichnis fehlen:\n")
    diff_liste.sort()
    [print(element) for element in diff_liste]


def get_pathname(nr):
    """Hole den Pfad vom Nutzer"""

    while True:
        print("\nGebe den Pfad zum ", str(nr) + ".", "Verzeichnis ein:\n")
        pfad = input()

        # Prüfe, ob das eingegebene Verzeichnis existiert:
        if os.path.exists(pfad):
            return pfad
        else:
            print("Das Verzeichnis existiert nicht, versuche es noch "
                  "einmal.")


def get_filepaths(verzeichnis):
    """Erzeuge eine Dateiliste mit allen Dateipfaden in dem übergebenen Verzeichnis"""

    dateiliste = []     # Liste, die alle Dateien im übergebenen Verzeichnis enthält.

    # Durchlaufe den Verzeichnisbaum:
    for root, directories, files in os.walk(verzeichnis):
        for filename in files:
            # Füge die beiden Strings für den kompletten Pfad zusammen:
            dateipfad = os.path.join(os.path.relpath(root, verzeichnis), filename)
            dateiliste.append(dateipfad)
    return dateiliste


# Wenn der Code eigenständig laufen soll und nicht importiert werden soll, rufe die
# Funktion main() auf:
if __name__ == '__main__':
    main()
Hier ist die Ausgabe für meine Testumgebung:

Code: Alles auswählen

Dateien die im Originalverzeichnis fehlen:

./DSCN8221.JPG
./DSCN8222.JPG
A1/DSCN8219.JPG
A1/DSCN8220.JPG
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Listcomprehensions sind nicht dazu da, for-Schleifen zu ersetzen. Eine Liste die nur aus None-Werten besteht und gleich nach dem Erzeugen weggeschmissen wird, ist Quatsch.

os.path.join und os.path.relpath würde ich noch vertauschen.
Spam&Eggs
User
Beiträge: 15
Registriert: Donnerstag 15. Februar 2018, 22:09

@Sirius3: Wo du recht hast, hast du recht. Ich habe deine Anregungen noch umgesetzt.
Antworten