Batch Windows-Dateiensuche möglich?

Update:

Ich glaube, ChatGPT hat nun endlich ein Script rausgespuckt, das genau das tut, was ich möchte. So ganz vertraue ich der Automatik aber noch nicht, daher poste ich es mal hier zum kritischen Drüberschauen (@simpsonsfan bzw. gerne auch alle anderen code-kundigen User):

Die Anforderungen an das Script bzw. ChatGPT waren folgende:

Im Anhang habe ich 2 txt-Dateien hochgeladen. Einmal eine namens "output.txt" und einmal eine namens "search.txt". Die output.txt-Datei ist vollständig, die search.txt-Datei enthält beispielhaft aufgrund der Größenbeschränkung nur einen Teil der Inhalte, ist aber in Wirklichkeit viel größer. Die search.txt-Datei fungiert als Indexdatei, die Pfade zu verschiedenen Medientypen auf dem PC enthält.

Folgendes Vorhaben:
Die output.txt-Datei enthält konkrete Pfade zu Medien. Diese konkreten Medien der output-Datei sollen anhand der Speicherorte der search.txt-Datei (Index-Datei) gefunden und in einen neuen Ordner namens "gefunden" kopiert werden. Es soll dabei das Ordnerverzeichnis der Output-Textdatei auch im "gefunden"-Ordner für die gefundenen Kopien beibehalten werden. Das heißt, die Verzeichnisstruktur ab dem Ordner "Media" und dessen Unterordner soll beibehalten werden. Desweiteren soll das Script Dateien, welche vom Dateinamen identisch sind, berücksichtigen, aber nur dann kopieren, wenn sich der Inhalt aller doppelten Dateien unterscheidet.

Ein konkretes Beispiel hierfür, um es nachvollziehen zu können:
In der Output-Textdatei befindet sich z.B. ein Eintrag Media/WhatsApp Images/IMG-20160519-WA0000.jpg

Ist nun diese Datei "IMG-20160519-WA0000.jpg" anhand der Index-Datei mehrfach in verschiedenen Verzeichnissen vorhanden, soll diese Datei kopiert werden und zudem alle weiteren Dateien mit demselben Dateinamen, die sich inhaltlich (Größe, Hashwerte) von ihr unterscheiden. Wenn es keine inhaltlichen Unterschiede gibt, soll nur eine Instanz kopiert werden. Wenn es inhaltliche Unterschiede zwischen gefundenen Dateien mit identischem Dateinamen gibt, dann sollen all jene Dateien kopiert werden, die sich jeweils inhaltlich durch Hashwerte voneinander unterscheiden. Damit sich diese Dateien im "gefunden-Ordner" nicht gegenseitig überschreiben, soll dann an den Dateinamen am Ende für jede weitere Kopie ein (1), (2), etc. angehängt werden.

Hier das Kopier-Script:


Python:
import os
import shutil
import hashlib

# Pfade zu den Dateien
output_file = 'output.txt'
search_file = 'search.txt'
zielordner = 'gefunden'

# Hilfsfunktion: Hash einer Datei berechnen
def calculate_file_hash(filepath, chunk_size=8192):
    hasher = hashlib.md5()
    with open(filepath, 'rb') as f:
        for chunk in iter(lambda: f.read(chunk_size), b""):
            hasher.update(chunk)
    return hasher.hexdigest()

# Output- und Search-Dateien einlesen
with open(output_file, 'r', encoding='utf-8') as f:
    output_paths = [line.strip() for line in f if line.strip()]

with open(search_file, 'r', encoding='utf-8') as f:
    search_paths = [line.strip() for line in f if line.strip()]

# Mapping der Dateien nach Dateinamen
search_dict = {}
for full_path in search_paths:
    filename = os.path.basename(full_path)
    if filename not in search_dict:
        search_dict[filename] = []
    search_dict[filename].append(full_path)

# Zielordner erstellen
os.makedirs(zielordner, exist_ok=True)

# Dateien aus der Output-Liste verarbeiten
for relative_path in output_paths:
    filename = os.path.basename(relative_path)
    subfolder = os.path.dirname(relative_path)

    # Überprüfen, ob Datei in der Search-Datei existiert
    if filename in search_dict:
        found_files = search_dict[filename]
       
        # Hashes aller gefundenen Dateien berechnen
        hashes = {}
        for file_path in found_files:
            file_hash = calculate_file_hash(file_path)
            if file_hash not in hashes:
                hashes[file_hash] = []
            hashes[file_hash].append(file_path)

        # Dateien kopieren, unter Berücksichtigung von Duplikaten
        for i, (file_hash, paths) in enumerate(hashes.items()):
            source_path = paths[0]  # Nur eine Instanz je Hash kopieren

            # Zielverzeichnis und -dateiname vorbereiten
            target_subfolder = os.path.join(zielordner, subfolder)
            os.makedirs(target_subfolder, exist_ok=True)

            if i == 0:
                target_path = os.path.join(target_subfolder, filename)
            else:
                name, ext = os.path.splitext(filename)
                target_path = os.path.join(target_subfolder, f"{name}({i}){ext}")

            # Datei kopieren
            shutil.copy2(source_path, target_path)

print("Dateien wurden erfolgreich verarbeitet und kopiert.")

Das Index-Script zum indizieren aller vorhandenen Dateien und zum Erstellen der search.txt (Index-Datei) ist Folgendes:

Python:
import os

# Quellordner definieren
source_dirs = [
    r"D:\Backup Handys + WhatsApp Medien"
]

# Zieldatei für die Ergebnisse
output_file = "search.txt"

# Suchbegriffe
keywords = ["WA0", "s.whatsapp.net", "g.us"]

# Funktion zum Durchsuchen der Ordner
def search_files(source_dirs, keywords, output_file):
    results = []
   
    for source_dir in source_dirs:
        if not os.path.exists(source_dir):
            print(f"Ordner '{source_dir}' existiert nicht.")
            continue

        # Durchlaufe alle Dateien in diesem Ordner und Unterordnern
        for root, _, files in os.walk(source_dir):
            for file in files:
                if any(keyword in file for keyword in keywords):
                    full_path = os.path.join(root, file)
                    results.append(full_path)
                    print(f"Gefunden: {full_path}")
   
    # Ergebnisse in die Datei schreiben
    with open(output_file, "w", encoding="utf-8") as f:
        for line in results:
            f.write(line + "\n")
   
    print(f"Fertig. Ergebnisse sind in '{output_file}' gespeichert.")

# Hauptfunktion ausführen
if __name__ == "__main__":
    search_files(source_dirs, keywords, output_file)
 
Aus dem Kopf raus könnte ich jetzt nicht sagen, ob Zeile 54 funktioniert (ob das enumerate wirklich ein Tupel für das dict ausspuckt), aber kannst du ja testen.

Und deine output.txt sollte dann wohl Relativpfade enthalten. (Also relativ zu deiner gewünschten Struktur, ohne Laufwerksbuchstaben.)

Generell kannst du dir bei (vor oder nach) Zeile 68 noch eine Textausgabe geben lassen, die dir mitteilt, welche Dateien kopiert wurden. Also etwa print("Copy " + source_path + " to " + target_path).

Außerdem könnte es sein, das in dem Formatstring in Zeile 65 der Punkt fehlt. Bin mir da grade nicht sicher. Ich hätte vermutlich
Python:
name_with_counter = name + "(" + str(i) + ")." + ext
target_path = os.path.join(target_subfolder, name_with_counter)
geschrieben. Aber das ist rein persönliche Vorliebe. Eigentlich sind Formatstring evtl. sogar besser lesbar.

Ich vermute übrigens, dass das eine Weile laufen könnte.
Schöner wäre evtl. auch, die Hashes schon bei der Indizierung der Backupdateien zu berechnen (und zu speichern.)

Aber ich würde sagen, einfach mal testen.
 
simpsonsfan schrieb:
Aus dem Kopf raus könnte ich jetzt nicht sagen, ob Zeile 54 funktioniert (ob das enumerate wirklich ein Tupel für das dict ausspuckt), aber kannst du ja testen.

Ich kann dir auch nicht sagen, ob es funktioniert, da ich nicht mal weiß, was es machen soll oder was nicht...

simpsonsfan schrieb:
Und deine output.txt sollte dann wohl Relativpfade enthalten. (Also relativ zu deiner gewünschten Struktur, ohne Laufwerksbuchstaben.)

Ja, die hat ja schon von Anfang an immer relative Pfade enthalten. Ab "Media" abwärts.

simpsonsfan schrieb:
Generell kannst du dir bei (vor oder nach) Zeile 68 noch eine Textausgabe geben lassen, die dir mitteilt, welche Dateien kopiert wurden. Also etwa print("Copy " + source_path + " to " + target_path).

Ja, das hat mich etwas irritiert, dass cmd während dem Kopieren so rein gar nix angezeigt hat...

simpsonsfan schrieb:
Außerdem könnte es sein, das in dem Formatstring in Zeile 65 der Punkt fehlt. Bin mir da grade nicht sicher. Ich hätte vermutlich
Python:
name_with_counter = name + "(" + str(i) + ")." + ext
target_path = os.path.join(target_subfolder, name_with_counter)
geschrieben. Aber das ist rein persönliche Vorliebe. Eigentlich sind Formatstring evtl. sogar besser lesbar.

Auch hier, wie oben, kann ich nicht sagen, ob es funktioniert oder nicht, da ich nicht weiß, was es eigentlich machen soll und was nicht.

simpsonsfan schrieb:
Ich vermute übrigens, dass das eine Weile laufen könnte.

Es ging. Soo lang war es dann doch gar nicht.

simpsonsfan schrieb:
Schöner wäre evtl. auch, die Hashes schon bei der Indizierung der Backupdateien zu berechnen (und zu speichern.)

Wie müsste man dafür dann das Index-Script abändern bzw. das Kopierscript ebenfalls, damit es nicht nochmal die hash-Werte berechnet?

simpsonsfan schrieb:
Aber ich würde sagen, einfach mal testen.

Also durchgelaufen ist es und auf den ersten Blick scheint es wohl auch genau das gemacht zu haben, wie ich es wollte. Ich weiß aber noch nicht, ob es zu 100% akkurat gearbeitet hat. Bisher ist mir manuell/händisch beim Durchschauen aber noch nichts Negatives aufgefallen...
 

Ähnliche Themen

Zurück
Oben