none
txt Dateien nach Teil des Dateinamens filtern RRS feed

  • Frage

  • Hallo,

    ich habe einen Ordner mit sehr vielen txt Dateien. Format: LIST_Zeitstempel_ID also zum Beispiel: LIST_191108_A0234. Zeitstempel beruht auf dem CreationTime

    Von jeder ID kann es mehrere Listen geben, die alle ein unterschiedlichen Zeitstempel haben. Ich möchte nun gerne für jede ID die aktuellste (nach Zeitstempel bzw Creationtime) Datei in eine neue Datei (LIST_ALL.txt) schreiben.

    Umsetzungsidee wäre folgende:

    Mit Get-ChildItem alle .txt Files holen und formatieren(1), so dass ich im nächsten Schritt mit Replace()(2), die ID ermitteln kann. Dann mit Get-Unique alle doppelten löschen(3). Dann wieder Get-ChildItem(4*verschiedene ID-Gruppen) mit Filter ID und diese nach Creationtime sortieren(5) und in die neue Liste schreiben.

    Meine Frage wäre: Wie kann man das effizienter machen?

    Momentan würde ich mit dieser Strategie ca. 5*verschiedene ID-Gruppen alle .txt Files durchlaufen. Kann man nicht gewisse Schritte kombinieren z.B. das erste Get-Childitem und Repalce in einem Schleifendurchlauf?

    Wäre super wenn jemand Verbesserungsvorschläge hat.

    mfg werdas34

    Freitag, 8. November 2019 22:18

Antworten

  • Mit Get-ChildItem alle .txt Files holen und formatieren(1), so dass ich im nächsten Schritt mit Replace()(2), die ID ermitteln kann. Dann mit Get-Unique alle doppelten löschen(3). Dann wieder Get-ChildItem(4*verschiedene ID-Gruppen) mit Filter ID und diese nach Creationtime sortieren(5) und in die neue Liste schreiben.

    Get-ChildItem brauchst Du nur einmal. ;-) Du könntest dann mittels calculated properties die IDs und die Zeitstempel als separate Properties erzeugen, nach denen Du dann sortieren, gruppieren und "uniquen" kannst. Wenn die Dateinamen alle das gleiche Format haben, kannst Du die .subString() Methode benutzen. z.B.:
    'LIST_191108_A0234'.Substring(5,6)


    Live long and prosper!

    (79,108,97,102|%{[char]$_})-join''

    Samstag, 9. November 2019 00:42
  • für ein Datumsformat musst du mal hier sehen:
    https://stackoverflow.com/questions/51224/regular-expression-to-match-valid-dates/8768241#8768241

    Den Ausdruck must du nur umformatieren;-).

    Ansonsten dann eben "LIST_<DatumAusdruck>_TaktInfo_.{5}\.txt"

    Ohne Datumsprüfung währe der Ausdruck

    "LIST_\d{6}_TaktInfo_.{5}\.txt"

    Montag, 11. November 2019 14:25
  • Gibt es von Get-Date eine Methode oder Möglichkeit zu prüfen, ob die 6 Zahlen sinnvoll sind? Sowas wäre sinnfrei "191312", da es keinen 13. Monat gibt.

    $FileDate = <...> # woher auch immer das Dateidatum kommt :-)
    [ref]$Date = Get-Date
    [bool]$ValidDate = [DateTime]::TryParseExact( $FileDate, 'yyMMdd', $null, 'None', $date )

    Greetings/Grüße, Martin - https://mvp.microsoft.com/en-us/PublicProfile/5000017 Mal ein gutes Buch über GPOs lesen? - http://www.amazon.de/Windows-Server-2012--8-Gruppenrichtlinien/dp/3866456956 Good or bad GPOs? My blog - http://evilgpo.blogspot.com And if IT bothers me? Coke bottle design refreshment - http://sdrv.ms/14t35cq

    Montag, 11. November 2019 15:08

Alle Antworten

  • Mit Get-ChildItem alle .txt Files holen und formatieren(1), so dass ich im nächsten Schritt mit Replace()(2), die ID ermitteln kann. Dann mit Get-Unique alle doppelten löschen(3). Dann wieder Get-ChildItem(4*verschiedene ID-Gruppen) mit Filter ID und diese nach Creationtime sortieren(5) und in die neue Liste schreiben.

    Get-ChildItem brauchst Du nur einmal. ;-) Du könntest dann mittels calculated properties die IDs und die Zeitstempel als separate Properties erzeugen, nach denen Du dann sortieren, gruppieren und "uniquen" kannst. Wenn die Dateinamen alle das gleiche Format haben, kannst Du die .subString() Methode benutzen. z.B.:
    'LIST_191108_A0234'.Substring(5,6)


    Live long and prosper!

    (79,108,97,102|%{[char]$_})-join''

    Samstag, 9. November 2019 00:42
  • Wenn die Dateinamen alle das gleiche Format haben, kannst Du die .subString() Methode benutzen. z.B.:

    'LIST_191108_A0234'.Substring(5,6)

    ...und wenn die Längen variieren und das Trennzeichen nicht, kannst Du mit -split die einzelnen Teile bekommen:

    'LIST_191108_A0234'.Split('_')


    Evgenij Smirnov

    http://evgenij.smirnov.de

    Samstag, 9. November 2019 08:44
  • Und nach dem Ersetzen eines ggf. mittles Split ermittelten Teilstings mittels Join-String wieder zusammen bauen.
    Samstag, 9. November 2019 09:28
  • Get-ChildItem brauchst Du nur einmal. ;-) Du könntest dann mittels calculated properties die IDs und die Zeitstempel als separate Properties erzeugen, nach denen Du dann sortieren, gruppieren und "uniquen" kannst. Wenn die Dateinamen alle das gleiche Format haben, kannst Du die .subString() Methode benutzen. z.B.:
    'LIST_191108_A0234'.Substring(5,6)

    Ist aber keine Effizienzsteigerung, oder? Ich mein mit Get-ChildItem bekomme ich ein Array. Ob ich nun ein weiters Array (mit Get-ChildItem) nach meinen Wünschen erstelle, oder ob ich vom alten Array mittels -match meine Sachen gruppiere. Der Aufwand ist der selbe.

    Oder verstehe ich dich falsch?

    Samstag, 9. November 2019 13:10
  • Da Du absolut nichts von Deinem Code gezeigt hast, sondern nur eine vage Beschreibung lieferst, kann man das nicht sagen. So lange Du ein zweiter Mal ein Get-ChildItem brauchst, glaube ich schon, dass mein Ansatz effizienter wäre. Ist denn Effizienz ein Problem bei Deinem Code? Läuft Dein Code nicht schnell genug? Verbraucht Dein Code zu RAM? Wenn das alles nicht der Fall ist und Dein Code funktioniert, dann ist doch alles gut. Wenn Du neugierig bist, ob man diese Aufgabe eleganter lösen kann, als Du es getan hast, wirst Du wohl Deinen Code hier posten müssen und wir gucken mal .... ;-)

    Live long and prosper!

    (79,108,97,102|%{[char]$_})-join''

    Samstag, 9. November 2019 14:36
  • Es gibt keinen vorzeigbaren Code. Nur die Strategie. Ich würde gern jetzt schon möglichst effizient das umsetzen. Auf den Server sind zurseit ca. 1000 txt Files. Gedacht sind später mindestens 30.000 FIles. Und da macht jeder extra Durchlauf schon einen Unterschied.

    Was man auch sagen muss. Das Skript wird einmal in der Nacht ausgeführt, da der Server nur zu einem Zeitpunkt neue txt Files bekommt.

    Ich werde ihn Montag dann man hochladen. :D

    Samstag, 9. November 2019 15:26
  • OK, dann bleibt's bei dem Hinweis aus meiner ersten Antwort. Dateisystem-Operationen sind üblicherweise aufwändig und träge und wir versuchen sie auf ein Minimum zu reduzieren. Wenn Du also ein zweites Get-ChildItem brauchst, hast Du vermutlich schon 'ne Bremse eingebaut. ;-) 

    Wenn es um sehr viele Dateien geht, kann man sogar überlegen, robocopy für seine Zwecke einzuspannen. Das ist viel schneller als pures Powershell und man kann es z.B. auch dafür benutzen, nur die Dateinamen einzusammeln.  ;-) ;-)


    Live long and prosper!

    (79,108,97,102|%{[char]$_})-join''

    Samstag, 9. November 2019 15:55
  • Jetzt verstehe ich deine Argumentation. Mir gings nur um die eigentlichen Durchläufe. Habe jetzt den Zugriff auf das Dateisystem komplett vergessen. Jetzt machts natürlich viel SInn. :D

    Und mit Robocopy hast du mich neugierig gemacht. Kenne dieses Tool und arbeite grade, um für mich persönlich ein nettes Backup Tool zu schreiben. Wie bekomme ich nur die Dateinamen? Habe jetzt schon paar mal die Optionen durch und nichts dementsprechend gefunden. Und kann man dann mit Robocopy auch die Creationtime abfragen?

    Samstag, 9. November 2019 16:14
  • Und mit Robocopy hast du mich neugierig gemacht. Kenne dieses Tool und arbeite grade, um für mich persönlich ein nettes Backup Tool zu schreiben. Wie bekomme ich nur die Dateinamen? Habe jetzt schon paar mal die Optionen durch und nichts dementsprechend gefunden. Und kann man dann mit Robocopy auch die Creationtime abfragen?

    Die entscheidende Option ist /L. Damit bekommst Du die Liste der Dateien und sie werden nicht kopiert. Dann blendest Du noch den JobHeader und die JobSummary aus /NJH /NJS. Die DirectoryList braucht man meistens auch nicht - /NDL, die Sizes und Classes sind auch eher uninteressant - /NS /NC und ein Progress ist beim Auflisten auch überflüssig - /NP. Den TimeStamp bekommst Du mit /TS. Ich schließe dann noch die Junctions aus - /XJ und das sollte so passen. Diese Liste ist natürlich reiner Text - die musst Du dann natürlich wieder mit Powershell parsen und so in verwertbare Objecte mit Properties umwandeln.

    Live long and prosper!

    (79,108,97,102|%{[char]$_})-join''

    Samstag, 9. November 2019 16:56
  • Vielen Dank. Habe jetzt bisschen rumgespielt. Die erste Frage ist, ob es im Endeffekt durch das parsen immer noch effizienter ist.

    Hast du einen Tipp was ich als Zielpfad angeben sollte? Wenn Quelle und Ziel gleich sind funktioniert gar nichts. Und wenn ich das Ziel anders bestimme dann kommen vom Ziel noch die Dateien. Man müsste einen leeren Ordner als Ziel wählen, was auch wieder unschön ist.

    Und wie splitte ich nach newline? $string.split("`n") funktioniert nicht. .split("") möchte ich nicht verwenden, da ich dann ganz viele leere Arrayeinträge habe. Unten sieht man die Ausgabe in PS.

    	  		 2019/11/07 19:54:47	C:\Users\*\Desktop\Test\LIST_190911_Taktinfo_A1234.txt
    	  		 2019/11/07 19:54:47	C:\Users\*\Desktop\Test\LIST_190912_Taktinfo_A1234.txt
    	  		 2019/11/07 19:54:47	C:\Users\*\Desktop\Test\LIST_190912_Taktinfo_A5678.txt
    	  		 2019/11/07 19:54:47	C:\Users\*\Desktop\Test\LIST_190912_Taktinfo_B1266.txt


    Edit: Problem gelöst. Kein Wunder, wenn das array[0] einfach nur eine Leerzeile ausgibt. :D

    • Bearbeitet werdas34 Samstag, 9. November 2019 20:29 Korrektur
    Samstag, 9. November 2019 18:54
  • Und wenn ich das Ziel anders bestimme dann kommen vom Ziel noch die Dateien. Man müsste einen leeren Ordner als Ziel wählen, was auch wieder unschön ist.

    Ich erstelle mir üblicherweise einen neuen leeren Ordner, den ich nach getaner Arbeit wieder abräume. Aber genaugenommen muss das auch nicht sein, wenn man sich mal die Hilfe für robocopy ansieht, findet man noch die Option /XX - damit werden dann noch die eXtra Dateien ausgelassen

    Und wie splitte ich nach newline?

    Was und warum willst Du denn nach newline splitten?


    Live long and prosper!

    (79,108,97,102|%{[char]$_})-join''

    Samstag, 9. November 2019 20:58

  • Und wie splitte ich nach newline?

    Was und warum willst Du denn nach newline splitten?

    Keine Ahnung wie ich den grauen Balken oben weg bekomme, ohne das es mir die komplette Formatierung zerschießt.  

    Man kann ja nicht ahnen das $array[0] einfach einen leeren String zurückliefert. Dachte deshalb ich müsste ihn splitten.

    Mit Robocopy bekomme ich mittels /TS den TimeStamp. Nur dieser TimeStamp bezieht sich auf das Änderungsdatum. Ist es möglich mit Robocopy das Erstelldatum abzufragen? In der Doku fand ich nichts dazu. Vielleicht kennst du einen Weg?

    Montag, 11. November 2019 07:23
  • Vielleicht kennst du einen Weg?

    Nö. Da ist mir nix Undokumentiertes bekannt. Im Zweifel hat dieser Trick dann natürlich seine Grenzen. Wenn Du zwingend ein Attribut brauchst, was Dir robocopy nicht liefert, kannst Du's halt nicht benutzen.

    Live long and prosper!

    (79,108,97,102|%{[char]$_})-join''

    Montag, 11. November 2019 11:13
  • Es gab da ein Missverständnis zwischen Auftraggeber und mir. Er hatte Testdaten zur Verfügung gestellt mit alten und neuen Dateiformaten ohne zu erwähnen, das die alten nicht mehr benötigt werden. Ich dachte deshalb ich soll die alten in die neuen Formate umwandeln. Muss ich nicht. Das Datum steht im neuem Format bereits im Filenamen drin. Muss man nur herausoperieren aus dem Namen.

    Im folgenden kommt der Code wie versprochen: Kurze Erläuterung zu unteren Teil. Alle Datums die in der Zukunft liegen sollen nicht in der Liste.txt sein. Und von den aktuellen soll nur die jeweils jüngste der Id in der Liste auftauchen.

    $LIST = "LIST_TaktInfo_DateiListe.txt"
    $FILTER_BAT = "*.bat"
    $FILTER_CMD = "*.cmd"
    $FILTER_PS1 = "*.ps1"
    $FILTER_LOG = "*.log"
    $FILTER_PPTX = "*.pptx"
    $FILTER_Aktuell = "*Aktuell*.txt"
    
    function Test-Filename{
        param([string] $file)
        $splittedArr = $file.Split("_")
        try{
            if(-Not ($splittedArr[0] -eq "LIST")){
                return $false
                if(-Not ($splittedArr[1] -match "^\d+$")){
                    return $false
                    if(-Not ($splittedArr[2] -eq "TaktInfo")){
                        return $false
                        if(-Not(($splittedArr[3].Length -eq 9) -and ($splittedArr[3].EndsWith(".txt")))){
                            return $false
                        }
                    }
                }
            }
        }catch{
            return $false
        }
        return $true
    }
    
    function Create-Object{
        param([string] $file)
    
        $trimmed = $file.Trim()
        $name = $trimmed.Split("\")[-1]
        $id = $trimmed.Split("_")[-1]
        $date = $name.Split("_")[1]
        $obj = New-Object -TypeName PSObject
        $obj | Add-Member -Name "Date" -MemberType NoteProperty -Value $date
        $obj | Add-Member -Name "Name" -MemberType NoteProperty -Value $name
        $obj | Add-Member -Name "Id" -MemberType NoteProperty -Value $id
        return $obj
    }
    
    $dontSpamLog = New-Item -Path $PSSCRIPTROOT -ItemType "directory" -Name "Test"
    $allFiles = robocopy $PSSCRIPTROOT $("$PSSCRIPTROOT\Test") /L /NJH /NJS /NDL /NS /NC /NP /XJ /XX /XF $FILTER_BAT $FILTER_CMD $FILTER_PS1 $FILTER_LOG $FILTER_PPTX $FILTER_Aktuell $LIST
    Remove-Item -Path $("$PSSCRIPTROOT\Test")
    
    
    
    $allFilesInfo = @()
    for($i=1; $i -lt $allFiles.Length; $i++){
        $name = $allFiles[$i].Split("\")[-1]
        if(Test-Filename -file $name){ 
            $allFilesInfo += Create-Object -file $name
        }
    }
    
    $currentDate = Get-Date -Format "yyMMdd"
    $sortedDateAll = $allFilesInfo | Sort-Object -Property Date -Descending
    $sortedDate = $sortedDateAll | Where-Object {$_.Date -le $currentDate}
    
    $uniqueIdCollector = @()
    $uniqueId = @()
    foreach($date in $sortedDate){
        if(-Not ($uniqueIdCollector.Contains($date.Id))){
            $uniqueIdCollector += $date.Id
            $uniqueId += $date
        }
    }
    
    $uniqueId | Select-Object -ExpandProperty Name > "$($PSSCRIPTROOT)\$LIST"
    
    
    

    Zwei Fragen:

    1. Kann man das Testen auf das richtige Dateiformat besser machen? Ein gültiges Format beginnt mit LIST dann Datum in 6 Zahlen, dann TaktInfo und dann eine beliebige Zeichenfolge die 5 Zeichen lang ist. Z.B.: "LIST_191107_TaktInfo_4079A.txt".
    2. Gibt es von Get-Date eine Methode oder Möglichkeit zu prüfen, ob die 6 Zahlen sinnvoll sind? Sowas wäre sinnfrei "191312", da es keinen 13. Monat gibt.
    Montag, 11. November 2019 13:40
  • für ein Datumsformat musst du mal hier sehen:
    https://stackoverflow.com/questions/51224/regular-expression-to-match-valid-dates/8768241#8768241

    Den Ausdruck must du nur umformatieren;-).

    Ansonsten dann eben "LIST_<DatumAusdruck>_TaktInfo_.{5}\.txt"

    Ohne Datumsprüfung währe der Ausdruck

    "LIST_\d{6}_TaktInfo_.{5}\.txt"

    Montag, 11. November 2019 14:25
  • Gibt es von Get-Date eine Methode oder Möglichkeit zu prüfen, ob die 6 Zahlen sinnvoll sind? Sowas wäre sinnfrei "191312", da es keinen 13. Monat gibt.

    $FileDate = <...> # woher auch immer das Dateidatum kommt :-)
    [ref]$Date = Get-Date
    [bool]$ValidDate = [DateTime]::TryParseExact( $FileDate, 'yyMMdd', $null, 'None', $date )

    Greetings/Grüße, Martin - https://mvp.microsoft.com/en-us/PublicProfile/5000017 Mal ein gutes Buch über GPOs lesen? - http://www.amazon.de/Windows-Server-2012--8-Gruppenrichtlinien/dp/3866456956 Good or bad GPOs? My blog - http://evilgpo.blogspot.com And if IT bothers me? Coke bottle design refreshment - http://sdrv.ms/14t35cq

    Montag, 11. November 2019 15:08