Dateien suchen, finden und wegschreiben

  • Liebe AutoIt Gemeinde,
    ich bin durch Zufall auf euer Forum gestossen, da ich für ein Problem
    schnelle Hilfe und Ergebnisse brauche, welche, so denke ich, am schnellsten
    mit AutoIt zu lösen sein könnten.

    Folgendes Problem:
    In einem Versandsystem wurden und werden Rechnungen in einer Verzeichnis/Unterverzeichnisstruktur
    als PDF mit der Stuktur <Nummer>_ZR_<Timestamp>.pdf abgespeichert.
    Aufgrund einer Nachfrage von extern sollen nun aber ca. 9000 Rechnungen in einem Rutsch zur Verfügung gestellt werden.

    Ein einzelner oder mehrere Mitarbeiter wären hier Tage beschäftigt, das zu bewerkstelligen, deshalb
    habe ich an AutoIt gedacht. Die 'Anforderungsliste' sprich die bereitzustellenden Rechnungsnummern
    liegen als Textdatei vor.

    Gibt es eine Möglichkeit oder kann mir jemand helfen/eine Lösung geben, welche folgendes bewerkstelligt:

    1. Lese den ersten Eintrag aus der Anforderungsliste.
    2. Suche mit diesem Eintrag (teilqualifiziert, also <Nummer>*.pdf) in Verzeichnis X alle Unterverzeichnisse nach diesem Eintrag durch.
    3. Wenn gefunden, dann kopiere die gefundene Datei in das Verzeichnis Y.
    4. Wenn nicht gefunden, dann erstelle einen Log-Eintrag (z.B. Rechnung für <Nummer> nicht gefunden).
    5. Lese nächsten Eintrag und beginne wieder bei 2. bis Anforderungslistenende.

    Ich bin absoluter Newbie bei AutoIt und ansonsten auch keine grosse Leuchte in Sachen Programmierung,
    aber vielleicht gibt es ja schon vorgefertigte oder ähnliche Lösungen und ich kann mit Copy/Paste arbeiten.

    Wäre Klasse, wenn es funktionieren würde

    Grüsse an alle hier im Forum !

  • Ich sehe das Hauptproblem für einen Beginner darin alle Verzeichnisse + Unterverzeichnisse nach den Dateien zu durchsuchen und die entsprechenden Informationen aus dem Dateinamen zu filtern.
    Daher hab ich mal versucht ein vollständiges Beispiel zu schreiben da es für den Einstieg wohl eher zu schwierig sein könnte.
    Wenn die Listendatei so aufgebaut ist das in jeder Zeile nur die Nummer steht dann solte es so funktionieren (nur die ersten 4 Zeilen anpassen:

    PDF-Dateifinder
    [autoit]

    Global Const $s_LISTFILEPATH = "D:\Ordner\Liste.txt" ; Pfad zur Anforderungsliste
    Global Const $s_LOGFILEPATH = "D:\Ordner\Log.txt" ; Pfad zur Log-Datei
    Global Const $s_FROMPATH = "D:\Ordner\Von" ; Suchpfad für PDF-Dateien
    Global Const $s_TOPATH = "D:\Ordner\Nach" ; Zielpfad fürs Kopieren der gefundenen Dateien
    Global Const $s_REGEXFILTER = "^(\d+)_ZR_.+?\.pdf$" ; Regulärer Ausdruck zur Filterung der Dateinamen

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ; diverse Variablen werden hier nur deklariert:
    Global $s_Pfad, $s_Line

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ; Dateiliste erstellen "sortiert" nach der <Nummer>:
    ; über einen "regulären Ausdruck" werden nur Dateien gefiltert welche dem
    ; Namensschema entsprechen und gleichzeitig wird die Nummer aus dem Dateinamen
    ; extrahiert da nach dieser ja gesucht werden soll:
    Global $o_DateiListe = ListFilesByPart($s_FROMPATH, $s_REGEXFILTER)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ; LogDatei zum Schreiben öffnen:
    Global $h_LogFile = FileOpen($s_LOGFILEPATH, 2)

    [/autoit] [autoit][/autoit] [autoit]

    ; Datei Anforderungsliste zum Lesen öffnen:
    Global $h_File = FileOpen($s_LISTFILEPATH, 0)
    If $h_File = -1 Then Exit MsgBox(48, "Fehler", "Konnte Anforderungslistendatei nicht finden/öffnen")

    [/autoit] [autoit][/autoit] [autoit]

    ; Datei Anforderungsliste Zeile für Zeile durchgehen:
    ; Hier ist Format der Datei: Pro Zeile nur eine Nummer:
    Do
    $s_Line = FileReadLine($h_File)
    If @error Then ExitLoop

    [/autoit] [autoit][/autoit] [autoit]

    ; Check ob Datei vorhanden ist:
    $s_Pfad = $o_DateiListe($s_Line)

    [/autoit] [autoit][/autoit] [autoit]

    If $s_Pfad = "" Then
    ; Da Datei nicht existiert Log-Eintrag schreiben
    FileWriteLine($h_LogFile, $s_Line & " nicht gefunden")
    Else
    ; Gefundene Datei kopieren:
    FileCopy($s_Pfad, $s_TOPATH)
    EndIf
    Until 0
    FileClose($h_File)
    FileClose($h_LogFile)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ; Listet Dateien in einem Verzeichnis/Unterverzeichnis in ein Dictionary so
    ; dass Key=Value gilt: Teil des Dateinamens=Pfad
    ; $sSD : Startverzeichnis
    ; $sPat : RegEx-Pattern zur Filterung von Dateien und Extraktion eines Teils des Dateinamens
    Func ListFilesByPart($sSD, Const $sPat = '')
    Local $o_Dic = ObjCreate('Scripting.Dictionary')
    Local $sSubD = "", $a_RegEx
    For $i In StringSplit($sSD, '|', 2)
    If StringRight($i, 1) = '\' Then $i = StringTrimRight($i, 1)
    If Not FileExists($i) Then Return SetError(2, 0, "")
    $sSubD &= '|' & $i
    Next
    Local $FFFF, $FFNF, $sDir, $iC
    Local $aD, $hDLL = DllOpen('kernel32.dll')
    Do
    $iC = StringInStr($sSubD, '|', 2, -1)
    If @error Or $iC = 0 Then ExitLoop
    $iC = StringLen($sSubD) - $iC
    $sDir = StringRight($sSubD, $iC)
    $sSubD = StringTrimRight($sSubD, $iC + 1)
    $FFFF = FileFindFirstFile($sDir & '\*')
    If $FFFF <> -1 Then
    Do
    $FFNF = FileFindNextFile($FFFF)
    If @error Then ExitLoop
    If @extended Then
    $aD = DllCall($hDLL, 'dword', 'GetFileAttributesW', 'wstr', $sDir & '\' & $FFNF)
    If @error Or BitAND($aD[0], 0x400) Then ContinueLoop
    $sSubD &= '|' & $sDir & '\' & $FFNF
    Else
    $a_RegEx = StringRegExp($FFNF, $sPat, 3)
    If @error Or (Not IsArray($a_RegEx)) Then ContinueLoop
    $o_Dic($a_RegEx[0]) = $sDir & '\' & $FFNF
    EndIf
    Until 0
    FileClose($FFFF)
    EndIf
    Until 0
    DllClose($hDLL)
    Return $o_Dic
    EndFunc ;==>ListFilesByPart

    [/autoit]

    Einmal editiert, zuletzt von AspirinJunkie (25. Oktober 2012 um 13:36)

  • Hallo AspirinJunkie,

    wooow. Das ging aber schnell. Ich hab's auch gleich ausprobiert, also Pfade angepasst, zur exe konvertiert und laufen lassen, aber nix passiert (leider) Weder Fehlermeldungen noch irgendwelche Log-Einträge werden geschrieben.

    Wie bekomme ich raus, was falsch bzw. nicht läuft ?

    Gruss

  • Zur exe brauchst du es nicht konvertieren.
    Wenn du Scite4AutoIt (Link links in der Downloadleiste) verwendest kannst du einfach dort das Skript mit F5 starten ohne es vorher zu kompilieren.
    Das hat vor allem dann Vorteile wenn du zwischendurch Ausgaben per ConsoleWrite() machen willst.

    Dies wollen wir gleich einmal versuchen in dem wir zusätzlich noch ein paar Ausgaben einbauen um zu schauen welche Teile korrekt und welche nicht laufen (dafür die au3 aus Scite4AutoIt heraus starten):

    Spoiler anzeigen
    [autoit]

    Global Const $s_LISTFILEPATH = "D:\Programmierung\Autoit\Test\Liste.txt" ; Pfad zur Anforderungsliste
    Global Const $s_LOGFILEPATH = "D:\Programmierung\Autoit\Test\Log.txt" ; Pfad zur Log-Datei
    Global Const $s_FROMPATH = "D:\Programmierung\Autoit" ; Suchpfad für PDF-Dateien
    Global Const $s_TOPATH = "D:\Programmierung\Autoit\Test\To" ; Zielpfad fürs Kopieren der gefundenen Dateien
    Global Const $s_REGEXFILTER = "^(\d+)_ZR_.+?\.pdf$" ; Regulärer Ausdruck zur Filterung der Dateinamen

    [/autoit] [autoit][/autoit] [autoit]

    ; diverse Variablen werden hier nur deklariert:
    Global $s_Pfad, $s_Line

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite("Suchpfad existiert: " & FileExists($s_FROMPATH) & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    ; Dateiliste erstellen "sortiert" nach der <Nummer>:
    ; über einen "regulären Ausdruck" werden nur Dateien gefiltert welche dem
    ; Namensschema entsprechen und gleichzeitig wird die Nummer aus dem Dateinamen
    ; extrahiert da nach dieser ja gesucht werden soll:
    Global $o_DateiListe = ListFilesByPart($s_FROMPATH, $s_REGEXFILTER)
    ConsoleWrite("Anzahl gefundener Dateien: " & $o_DateiListe.Count & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    ; LogDatei zum Schreiben öffnen:
    Global $h_LogFile = FileOpen($s_LOGFILEPATH, 2)

    [/autoit] [autoit][/autoit] [autoit]

    ; Datei Anforderungsliste zum Lesen öffnen:
    Global $h_File = FileOpen($s_LISTFILEPATH, 0)
    If $h_File = -1 Then Exit MsgBox(48, "Fehler", "Konnte Anforderungslistendatei nicht finden/öffnen")

    [/autoit] [autoit][/autoit] [autoit]

    ; Datei Anforderungsliste Zeile für Zeile durchgehen:
    ; Hier ist Format der Datei: Pro Zeile nur eine Nummer:
    Do
    $s_Line = FileReadLine($h_File)
    If @error Then ExitLoop

    [/autoit] [autoit][/autoit] [autoit]

    ConsoleWrite("Aktuelle Zeile: " & $s_Line & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    ; Check ob Datei vorhanden ist:
    $s_Pfad = $o_DateiListe($s_Line)

    [/autoit] [autoit][/autoit] [autoit]

    If $s_Pfad = "" Then
    ; Da Datei nicht existiert Log-Eintrag schreiben
    FileWriteLine($h_LogFile, $s_Line & " nicht gefunden")
    Else
    ; Gefundene Datei kopieren:
    ConsoleWrite("Kopieren Datei " & $s_Pfad & @CRLF)
    FileCopy($s_Pfad, $s_TOPATH)
    EndIf
    Until 0
    FileClose($h_File)
    FileClose($h_LogFile)

    [/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]

    ; Listet Dateien in einem Verzeichnis/Unterverzeichnis in ein Dictionary so
    ; dass Key=Value gilt: Teil des Dateinamens=Pfad
    ; $sSD : Startverzeichnis
    ; $sPat : RegEx-Pattern zur Filterung von Dateien
    ; $s_PartPattern : RegEx-Pattern zur Extraktion des Teils des Dateinamen
    Func ListFilesByPart($sSD, Const $sPat = '')
    Local $o_Dic = ObjCreate('Scripting.Dictionary')
    Local $sSubD = "", $a_RegEx
    For $i In StringSplit($sSD, '|', 2)
    If StringRight($i, 1) = '\' Then $i = StringTrimRight($i, 1)
    If Not FileExists($i) Then Return SetError(2, 0, "")
    $sSubD &= '|' & $i
    Next
    Local $FFFF, $FFNF, $sDir, $iC
    Local $aD, $hDLL = DllOpen('kernel32.dll')
    Do
    $iC = StringInStr($sSubD, '|', 2, -1)
    If @error Or $iC = 0 Then ExitLoop
    $iC = StringLen($sSubD) - $iC
    $sDir = StringRight($sSubD, $iC)
    $sSubD = StringTrimRight($sSubD, $iC + 1)
    $FFFF = FileFindFirstFile($sDir & '\*')
    If $FFFF <> -1 Then
    Do
    $FFNF = FileFindNextFile($FFFF)
    If @error Then ExitLoop
    If @extended Then
    $aD = DllCall($hDLL, 'dword', 'GetFileAttributesW', 'wstr', $sDir & '\' & $FFNF)
    If @error Or BitAND($aD[0], 0x400) Then ContinueLoop
    $sSubD &= '|' & $sDir & '\' & $FFNF
    Else
    $a_RegEx = StringRegExp($FFNF, $sPat, 3)
    If @error Or (Not IsArray($a_RegEx)) Then ContinueLoop
    $o_Dic($a_RegEx[0]) = $sDir & '\' & $FFNF
    EndIf
    Until 0
    FileClose($FFFF)
    EndIf
    Until 0
    DllClose($hDLL)
    Return $o_Dic
    EndFunc ;==>ListFilesByPart

    [/autoit]


    In der Ausgabekonsole von Scite4AutoIt sollten nun unsere Debugging-Einträge stehen.
    Diese mal bitte posten.

    Spontan vermute ich dass die Listendatei nicht im Format Nummer - nächste Zeile - Nummer - nächste Zeile... steht oder die Dateinamen ein anderes Namensschema als im regulären Ausdruck definiert wurde haben.

  • Hallo AspirinJunkie,
    Du bist mein Held !
    Es waren zwei Fehler/Probleme welche das Skript daran gehindert hatten zu laufen.
    1. Der Dateiname war falsch (mein Fehler).
    Ich habe daraufhin den Regexfilter nach "^(\d+)_.+?\.pdf$" angepasst.

    2. Die Eingabetextdatei war UTF-8 kodiert, und damit hat oder hatte AutoIt wohl ein Problem.
    Als ich sie als ANSI gespeichert hatte, lief das ganze ohne Probleme durch, und das in
    einem Affenzahn !.

    Vielen, vielen Dank dafür. You made my day :)
    Ich werde dies zum Anlass nehmen um mich intensiver mit AutoIt zu beschäftigen.

  • Die Eingabetextdatei war UTF-8 kodiert, und damit hat oder hatte AutoIt wohl ein Problem.


    AutoIt liest und schreibt tatsächlich standardmäßig in ANSI.
    Das kann man aber problemlos durch eine zusätzliche Option bei >>FileOpen()<< einstellen.
    Ich kann mir allerdings nicht vorstellen dass es hier ein Problem darstellen sollte da in der Textdatei ja nur Ziffern stehen und die sollten in ANSI und UTF-8 meiner Meinung nach gleich codiert sein. So intensiv stehe ich hierbei aber nicht im Thema.

    Ich werde dies zum Anlass nehmen um mich intensiver mit AutoIt zu beschäftigen.

    Lohnt sich auf alle Fälle.
    Auch wenn man erstmal Zeit braucht um sich reinzufinden (wie bei allen neuen Dingen) kann man schlussendlich damit ne Menge Zeit und Nerven sparen.
    Diese vorgekaute Lösung war natürlich aus didaktischer Sicht nicht allzu lehrreich aber für den Einstieg wäre es denke ich zu heftig gewesen.
    Paar kleinere Sachen mal selbst programmieren und dann sollte man schnell voran kommen denke ich.
    Also viel Spaß dir dann mit AutoIt :)