Doppelte Dateien finden - aber nur pro Ordner

Petronius

Cadet 3rd Year
Registriert
Nov. 2012
Beiträge
33
Hallo Leute,

könnt Ihr mir eventuell bei folgendem Problem helfen:

Ich möchte gerne auf einer Partition alle Ordner auf doppelte (-> inhaltsgleiche) Dateien prüfen und die "neueren doppelten Dateien" löschen lassen - aber nur innerhalb des jeweiligen Ordners und nicht ordnerübergreifend.

Wenn man das Problem nur auf einen einzelnen Ordner anwendet, dann kann man das mit vielen Tools sehr einfach lösen, hier z.B. mit CloneSpy:
CS-Screenshot 01.jpg
Allerdings müsste man diesen Vorgang dann pro Ordner neu durchführen, was bei einigen hundert Ordnern sehr zeitaufwendig wäre. Kann man dies automatisieren, so dass Ordner für Ordner abgearbeitet wird - eventuell z.B. mit einer Batch-Datei und einem kommandozeilenfähigen Such-Tool?

Die Suche im Forum und im Web habe ich hierzu reichlich bemüht, aber leider nichts Brauchbares gefunden. Vielleicht habt Ihr ja einen Tipp oder einen passenden Link. Vielen Dank im Voraus.

Petronius
 
Zeroflow schrieb:
Das schreit fast nach Python

Hallo Zeroflow,

vielen Dank für Deinen Hinweis. "Python" - puh, das muss ich jetzt erst mal verdauen. Von Python habe ich nämlich absolut keine Ahnung und auch meine sonstigen Programmierkenntnisse sind äußerst bescheiden. Aber ich versuche mal, mich mit Deinen Links zu beschäftigen und melde mich dann wieder - kann aber ein wenig dauern.

Über weitere Lösungshinweise würde ich mich freuen. Bis dann.
 
Hier mal ein Beispiel wie man das mit python machen kann.
Das Script verwendet leider ein paar fortgeschrittene Python Konstrukte - also wenn du Fragen hast ist es absolut berechtigt ;)

Code:
import os
import hashlib

def digest(file):
    '''digest takes 1 file as argument and returns its md5 sum
    Source code taken from StackOverflow
    
    '''
    md5 = hashlib.md5()
    with open(file,'rb') as f: 
        for chunk in iter(lambda: f.read(8192), b''): 
             md5.update(chunk)
    return md5.digest()

def findDoubles(path):
    '''Find double files per directory and return a list of all
    found files.
    
    '''
    doubles = []
    for path, _, filenames in os.walk(path):
        md5sums = {}
        for file in filenames:
            filepath = os.path.join(path,file)
            filetime = os.path.getmtime(filepath)
            md5sum = digest(filepath)
            
            #check if one file of that folder already had the same md5 sum (duplicate)
            if md5sum in md5sums:
                otherfile, othermtime = md5sums[md5sum]
                
                #Choose the newer file and append it to the "doubles" array
                if othermtime > filetime:
                    doubles.append(otherfile)
                else:
                    doubles.append(filepath)
            
            md5sums[md5sum] = (filepath, filetime)
    return doubles
    
toDelete = findDoubles("C:\doubles")

#print all found doubles. Use os.remove() to delete the files listed as doubles.
for file in toDelete:
    print file

Verwendet habe ich folgende Test-Daten:
https://www.dropbox.com/s/utm02buc2ra8av2/doubles.zip
Dort ist auch das python script dabei.
 
Zeroflow schrieb:
Hier mal ein Beispiel wie man das mit python machen kann.
Hallo Zeroflow,

zunächst mal vielen Dank für Deine Mühe, dafür schon mal ein ganz dickes Lob von mir. Ich habe versucht, mich ein klein wenig mit den Grundlagen von Python zu beschäftigen und bin dann wie folgt vorgegangen:

  • Von hier habe ich eine portable Version von Python 3 heruntergeladen und eingerichtet,
  • Deine Beispieldateien habe ich nach C:\doubles entpackt,
  • dann den "PyScripter-Portable" gestartet,
  • Dein Skript geladen und
  • mit STRG-F9 ausführen lassen.
  • Anschließend erscheint dann folgende Fehlermeldung:
    Py-Test 01.jpg

Hättest Du mir einen Tipp, was ich falsch mache?
 
Hi!

Der Bug ist mir gut bekannt.
Bei Python 2.7 (das ich verwende) funktioniert "print string"
Bei Python 3.3 hingegen braucht print Klammern, also muss es "print(string)" sein.
 
Zeroflow schrieb:
Bei Python 2.7 (das ich verwende) funktioniert "print string".
Hallo Zeroflow,

am Wochenende konnte ich Dein Skript endlich testen. Unter Portable Python 2.7 funktioniert es einwandfrei und macht genau das, was es soll.
Code:
Zeile 41.    toDelete = findDoubles("C:\doubles")
In Zeile 41 gebe ich statt "C:\doubles" einfach den von mir gewünschten Startordner ein und starte das Skript mit "STRG-F9".

Sogar bei verschachtelten Verzeichnissen arbeitet es "Ordner für Ordner" jeweils intern ab und nicht ordnerübergreifend - super!

Die Ergebnisliste kopiere ich mir dann aus dem Ergebnisfenster des PyScripters in einen Texteditor und erstelle dort eine kleine Batchdatei, die die gefundenen Dateien dann der Reihe nach löscht.
_____

Bevor ich das Skript auf "wichtige" Daten loslasse, sind mir noch ein paar Feinheiten aufgefallen - vielleicht lassen sich diese ja noch beheben oder meine Bedenken zerstreuen:


Startordner beginnt "x" -> Escape-Fehler
================
Bei meinen Tests ist mir aufgefallen, dass ich eine Fehlermeldung bekomme, wenn z.B. der Startordner mit "x" beginnt (vgl. Grafik).
Escape-Fehler.jpg
Sollte ich vorsorglich alle Ordner, die mit "x" beginnen, vorher suchen und umbenennen oder muss man das im Skript einfach anders eingeben? Gibt es noch andere Buchstabenkombinationen, die man beachten sollte?

MD5-Hashkollisionen vermeiden
===================
Wenn ich es richtig verstanden habe, dann könnte "hashlib" nicht nur MD5, sondern z.B. auch SHA-1 oder andere Hashwerte zur Ermittlung der doppelten Dateien verwenden.

Zu meiner persönlichen Beruhigung würde ich gerne in einem zweiten Skript die doppelten Dateien zusätzlich z.B. per SHA-1 ermitteln lassen und danach die beiden Ergebnislisten im Texteditor einfach kurz miteinander vergleichen. Wenn sie identisch sind, müsste die Gefahr einer Hashkollision doch ziemlich unwahrscheinlich sein? Oder ist mein Denkansatz hier falsch?

Wie müsste man das zweite Skript abändern, damit es statt MD5 einen anderen Hashwert nutzt? Welcher Hashwert wäre ergänzend zu MD5 sinnvoll?

Startordner abfragen + Ergebnisse in einer Texdatei ausgeben
============================================
Könnte man das Skript noch so abändern, dass es nach dem "Startordner" fragt und die Ergebnisliste gleich in einer Textdatei (z.B. direkt nach "C:\Temp") ausgibt? Könnte ein Laie so ein angepasstes Skript mit einfachen Mitteln kompilieren, so dass man eine Standalone-EXE-Datei hätte? - Diese letzten Punkte sind aber nicht wichtig, sondern würden rein der "Bequemlichkeit" dienen.
_____

Wie schon ausgeführt, kann ich mit Deinem Skript aber bereits in der jetzigen Form mein Ausgangsproblem grundsätzlich lösen - daher nochmals *Danke Danke Danke*:)
 
Zuletzt bearbeitet: (Rechtschreibfehler)
Hi!

Zu deinen Fehlern:

Beginn mit x:
Das ist mein Fehler, ich hab das Script oben um 2 in der Früh betrunken geschrieben ;)
Es gehört stattdessen "C:\\Ordner"

http://docs.python.org/release/2.5.2/ref/strings.html

Bei einem String kann man mit \ bestimmte Zeichen "escapen". z.B wird damit aus einem \n ein Zeilenumbruch (newline), \t ein tab und eben mit \x könntest du direkt einen hexadezimalen Code für ein zeichen wie z.B \x03 angeben.
In deinem Fall müsstest du "C:\\xOrdner" schreiben - damit hast du im String ein Backslash als Zeichen stehen und nicht als escape operator.

SHA1:
Laut hashlib ( http://docs.python.org/2/library/hashlib.html#module-hashlib ) musst du nur eben statt hashlib.md5() stattdessen hashlib.sha1() verwenden.

Startordner abfragen:
Mit raw_input nen string einlesen
http://docs.python.org/2/library/functions.html#raw_input

Datei schreiben:
ganz simpel.
File öffnen, alle elemente mit .write() schreiben, file schließen
.write kümmert sich allerdings nicht um zeilenumbrüche, also solltest du zu jedem element noch ein "\r\n" anhängen
http://docs.python.org/2/library/functions.html#open + http://docs.python.org/2/library/stdtypes.html#bltin-file-objects

Exe Datei:
http://www.py2exe.org/index.cgi/Tutorial
 
Zuletzt bearbeitet: (Links hinzugefügt)
Hallo Zeroflow,

am Wochenende war bei mir mal wieder "Laienprogrammierung mit Python" angesagt. Ich habe versucht, die Eingabe des Startpfads, die Ausgabe in eine Datei und SHA-1 in Dein Skript einzubauen.

Die gute Nachricht zuerst:
Beide Skripte (MD5 und SHA-1) laufen durch, ohne Syntax-Errors, etc. zu produzieren, und liefern sogar jeweils dieselben Ergebnisse.

Die schlechte Nachricht:
Leider kommt am Ende nicht das gewünschte Suchergebnis heraus.

Ich habe zum Testen einen Musterpfad (vgl. xDoppeltest) erstellt, in dem 20 doppelte Dateien gefunden werden müssten (vgl. Ergebnisliste_Doubles korrekt.txt).

Im Python-Interpreter werden auch 20 Treffer aufgelistet (vgl. Ergebnisliste_Doubles sha1 - laut Python Interpreter.jpg). Beim näheren Betrachten fällt aber auf, dass manche Dateien mehrmals aufgelistet werden und andere Dateien fehlen. In der neu erzeugten Ergebnisdatei finden sich sogar nur 12 Treffer (vgl. Ergebnisliste_Doubles sha1.txt).

Ich fürchte, dass ich den Such-Algorithmus durcheinander gebracht habe. Vielleicht kannst Du oder einer der anderen Mitglieder gelegentlich einen Blick darauf werfen. Vielen Dank im Voraus.

Anhang anzeigen Doubles - Ergebnisse unvollstaendig.zip
 
Zuletzt bearbeitet: (Rechtschreibfehler)
Hi!

Nach langer Zeit gehts weiter.

Ich habs mal so umgebaut, statt MD5 benutzt es SHA1+MD5, damit ists nahezu ausgeschlossen dass eine Datei falsch erkannt wird ;)
Einer deiner Fehler war, dass du in die ergebnisliste immer "filepath" geschrieben hast, egal welche der beiden verglichenen Dateien älter war.

Hier meine neue Version die dir eig. das liefern sollte was du willst.
https://dl.dropbox.com/u/9857661/doubles.py

Wenn du es mit "python doubles.py OrdnerPfad" aufrufst, dann ersparst du dir jedesmal den pfad einzutippen.
 
Zuletzt bearbeitet:
Zurück
Oben