none
Suchen und Ersetzen in einer CSV Datei mittels PowerShell RRS feed

  • Frage

  • Hallo zusammen,

    ich habe folgendes vor:

    1. Ich habe eine CSV Datei (1) in der in der 8ten Spalte Nummern stehen.

    2. Ich gehe mittels einer Foreach Schleife jede Zeile durch und lese den Wert ein.

    3. Ich prüfe ob die Nummer wirklich nummerisch ist.

    4. Ich habe noch eine weitere CSV (2) mit fast 1000 alphanumerischen Werten und den dazugehörigen nummerischen Werten (als Übersetzung quasi)

    5. Ich möchte wenn der Wert/die Nummer aus der CSV (1) alphanummerisch ist, sie in der CSV (1) durch den entsprechenden nummerischen Wert aus CSV (2) ersetzten

    -> Meine erste Idee war es in der Foreach Schleife eine weitere Schleife einzubauen, also das nach der Prüfung ob es sich um einen Alphanummerischenwert handelt, der entsprechende Wert mit allen 1000 alphanummerischen Werten abgleicht. Dieses klappt auch, dauert nur ewig da ich Zeile für Zeile durchgehe. CSV (1) kann bis zu 100 Zeilen haben und dementsprechend geht das Script 100 Mal alle 1000 Werte aus der CSV (2) durch.

    Habt ihr eine Idee wie ich es alternativ aufbauen könnte?

    Vielen Dank und viele Grüße!


    Mittwoch, 23. August 2017 06:08

Antworten

  • Hallo Timo,

    ich habe das mal etwas überarbeitet und die meisten Bemerkungen in den Code geschrieben. Ich hoffe, es ist verständlich, ansonsten bitte nochmal nachfragen.

    Der zweite else-Block ist alternativ zum ersten. Bitte nur einen von beiden im Code belassen.

    Außerdem musst du die ganzen Feldnamen eintragen, da ich nicht den Kopf deiner beiden Dateien kenne.

    $PathToWorkDat = "E:\Script\Test\pkha_V0PKHL06_330.dat"
    $PathPaareCSV = "E:\Script\Test\Kostenstellen_alphanum2num.csv"
    
    #ich entnehme deinem Code, dass die eine Datei mit # getrennt ist und die andere mit ;
    $DatDatei = Import-Csv $PathToWorkDat -Delimiter "#"
    $KeyDatei = Import-Csv $PathPaareCSV -Delimiter ";"
    
    #alternativ: mit HashTable
    $KeyHT = @{}
    $KeyDatei | foreach{$KeyHT[$_.SchlüsselFeld] = $_.Wertfeld}
    
    foreach ($Zeile in $DatDatei) {
    
        #Bitte den Feldnamen vom betreffenden Feld statt Feld5 eintragen
        If($Zeile.Feld5 -match '^\d+$'){
            Write-Host "Weiter"
        }
        else{
            Write-Host "Alphanummerisch"    
    
            #ich unterstelle, dass nur ein Eintrag in KeyDatei gefunden werden kann (eindeutige Zuordnung)
            $Treffer = $KeyDatei | where{$_.FeldName -eq $Zeile.Feld5} 
    
            if($Treffer -eq $null){Write-Host "Kein Treffer"}
            else{Write-Host "Treffer"}
    
            #ersetzen
            $Zeile.Feld5 = $Treffer.FeldNameZumErsetzen
        }
    
        #alternativ zum oberen Else-Block: mit Hashtable
        else
        {
            $Treffer = $KeyHT[$_.Feld5]
        }
    }

    • Als Antwort markiert timowoehst Freitag, 25. August 2017 08:20
    Mittwoch, 23. August 2017 07:04
  • Nur mal noch so ne Idee .... wenn Deine Dat-Datei einen Delimiter hat ('#' wenn ich mich nicht verguckt habe), kannst Du doch trotzdem einfach Import-CSV benutzen und die Header beim Import mit angeben, oder?

    Grüße - Best regards

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

    Mittwoch, 23. August 2017 09:30
  • Das Out-File muss ganz ans Ende des Skriptes. Momentan exportierst du bei jedem Schleifendurchlauf. Außerdem hast du in deiner Exportzeile $DateiDat stehen, obwohl es immer $DatDatei heißt.

    Die Zuweisung ($Zeile.f = ...) sollte lieber mit in den else-Block, der momentan "Treffer" schreibt. Aber das ist nur Schönheit.

    Eigentlich sehe ich nicht, warum es nicht funktionieren sollte.

    Mittwoch, 23. August 2017 11:18

Alle Antworten

  • Hallo,

    es wäre gut, wenn du uns dein Skript schicken würdest. 1000 mal 100 Werte durchzugehen, sollte für einen Computer heutiger Zeit lapidar sein und nicht allzu lange dauern. Wenn es langsam ist, wird es Redundanzen in der Programmierung geben.

    Als Tipp lässt sich auch so sagen: Schreibe deine Übersetzung in eine HashTable. Der Zugriff darauf ist wesentlich schneller als das vollständige Durchsuchen eines Arrays.

    Bei sehr großen Datenmengen (nach deinen Angaben ist das bei dir noch nicht) bekommt PowerShell ein Speicherproblem, da es aufgrund der Objektstruktur ziemlich viel Speicher benötigt. Dann kann es sinnvoll sein, mit StreamReadern nur einzelne Zeilen in den Speicher zu laden oder ganz andere Ansätze zu verfolgen.

    Viele Grüße

    Christoph


    • Bearbeitet hpotsirhc Mittwoch, 23. August 2017 06:21
    Mittwoch, 23. August 2017 06:19
  • Hallo Christoph,

    hier ist das Script, ich habe das ersetzeten noch nicht eingebaut sondern erstmal nur eine Ausgabe, ob ich sich der Alphanummerische Wert in der "Übersetzungs CSV" befindet oder nicht:

    $PathToWorkDat = "E:\Script\Test\pkha_V0PKHL06_330.dat"
    $PathPaareCSV = "E:\Script\Test\Kostenstellen_alphanum2num.csv"
    $DatDatei = Get-Content $PathToWorkDat
    $KeyDatei = Get-Content $PathPaareCSV
    
    
    foreach ($Zeile in $DatDatei) {
    
           $Zeile = [String]$Zeile
           $Inhaltsplit = $Zeile.Split("#")
    
           If($Inhaltsplit[5] -match '^\d+$'){
           Write-Host "Weiter"
           }
           else{
           Write-Host "Alphanummerisch"
           $Inhalt = $Inhaltsplit[5] 
                foreach ($Row in $KeyDatei){
                    $Row = [String]$Row
                    $Inhaltstrenner = $Row.Split(";")
    
                    If($Inhalt -eq $Inhaltstrenner[0]){
                        Write-Host "Match"
                    }
                    Else{
                        Write-Host "Kein Match"
                    }
                } 
             }
        }

    VG Timo

    Mittwoch, 23. August 2017 06:36
  • Hallo Timo,

    ich habe das mal etwas überarbeitet und die meisten Bemerkungen in den Code geschrieben. Ich hoffe, es ist verständlich, ansonsten bitte nochmal nachfragen.

    Der zweite else-Block ist alternativ zum ersten. Bitte nur einen von beiden im Code belassen.

    Außerdem musst du die ganzen Feldnamen eintragen, da ich nicht den Kopf deiner beiden Dateien kenne.

    $PathToWorkDat = "E:\Script\Test\pkha_V0PKHL06_330.dat"
    $PathPaareCSV = "E:\Script\Test\Kostenstellen_alphanum2num.csv"
    
    #ich entnehme deinem Code, dass die eine Datei mit # getrennt ist und die andere mit ;
    $DatDatei = Import-Csv $PathToWorkDat -Delimiter "#"
    $KeyDatei = Import-Csv $PathPaareCSV -Delimiter ";"
    
    #alternativ: mit HashTable
    $KeyHT = @{}
    $KeyDatei | foreach{$KeyHT[$_.SchlüsselFeld] = $_.Wertfeld}
    
    foreach ($Zeile in $DatDatei) {
    
        #Bitte den Feldnamen vom betreffenden Feld statt Feld5 eintragen
        If($Zeile.Feld5 -match '^\d+$'){
            Write-Host "Weiter"
        }
        else{
            Write-Host "Alphanummerisch"    
    
            #ich unterstelle, dass nur ein Eintrag in KeyDatei gefunden werden kann (eindeutige Zuordnung)
            $Treffer = $KeyDatei | where{$_.FeldName -eq $Zeile.Feld5} 
    
            if($Treffer -eq $null){Write-Host "Kein Treffer"}
            else{Write-Host "Treffer"}
    
            #ersetzen
            $Zeile.Feld5 = $Treffer.FeldNameZumErsetzen
        }
    
        #alternativ zum oberen Else-Block: mit Hashtable
        else
        {
            $Treffer = $KeyHT[$_.Feld5]
        }
    }

    • Als Antwort markiert timowoehst Freitag, 25. August 2017 08:20
    Mittwoch, 23. August 2017 07:04
  • Statt einer sequentiellen Suche bietet sich hier eine Hash-Table an.
    Die 2. Datei wird in eine Hashtabelle geladen (Key = Alte Nummer, Value = neue Nummer).
    Bei der 1. Tabelle suchst du dann in der Hashtabelle den Wert.
    Vorteil: Der Zugriff auf eine Hash-Tabelle erfolgt i.d.R. mit nur einem einzigen Zugriff.

    Mittwoch, 23. August 2017 07:33
  • Statt einer sequentiellen Suche bietet sich hier eine Hash-Table an.
    Die 2. Datei wird in eine Hashtabelle geladen (Key = Alte Nummer, Value = neue Nummer).
    Bei der 1. Tabelle suchst du dann in der Hashtabelle den Wert.
    Vorteil: Der Zugriff auf eine Hash-Tabelle erfolgt i.d.R. mit nur einem einzigen Zugriff.

    Das hab ich schon in meinem ersten Post vorgeschlagen und in meinem Code-Beispiel als Alternative implementiert (soweit das ohne Kenntnis der Feldnamen möglich ist)...
    Mittwoch, 23. August 2017 07:37
  • Ich bitte um Entschuldigung. Das hatte ich doch glatt überlesen;-).
    Mittwoch, 23. August 2017 07:40
  • Vielen Dank für deine Hilfe!

    Leider handelt es sich bei der einen Datei um eine Dat Datei, diese hat keine Spaltennamen, die habe ich nur in der CSV mit der Übersetzung. Deswegen habe ich das mit dem get Content und dem Split bei # gemacht.

    [Sorry habe ich in der Fragestellung falsch geschrieben].

    Gibt es eine möglichkeit deine Lösung trotzdem anzuwenden?

    Mittwoch, 23. August 2017 08:08
  • Da du deine DAT-Datei ja vernünftig in ein Key/Value-Pair aufgelöst hast steht der Hash-Lösung doch nichts im Wege.
    Du musst halt nur deine DAT-Datei in einer Schleife zuerst in die Hashtabelle laden und in einer 2. Schleife dann die Ersetzung durchführen.
    Mittwoch, 23. August 2017 08:22
  • Ich glaube ich habe es noch nicht zu 100 Prozent verstanden, sorry.

    $KeyDatei | foreach{$KeyHT[$_.SchlüsselFeld] = $_.Wertfeld}

    Da ich $_.Wertefeld nicht machen kann da meine Dat Datei keine Überschriften hat, setzte ich nach dem "=" jetzt noch eine Schleife in der ich mittels Get Content und Split die entsprechende Spalte der Dat Datei einlese?

    Also so ungefähr?:

    $DatDatei = Get-Content $PathToWorkDat 
    
    $KeyDatei = Import-Csv $PathPaareCSV -Delimiter ";"
    
    
    $KeyHT = @{}
    $KeyDatei | foreach{$KeyHT[$_.Unverschluesselt] = foreach($Zeile in $DatDatei){$Zeile.Split("#")[5]}}
    

    Mittwoch, 23. August 2017 09:21
  • Nur mal noch so ne Idee .... wenn Deine Dat-Datei einen Delimiter hat ('#' wenn ich mich nicht verguckt habe), kannst Du doch trotzdem einfach Import-CSV benutzen und die Header beim Import mit angeben, oder?

    Grüße - Best regards

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

    Mittwoch, 23. August 2017 09:30
  • Ja du hast recht, funktioniert, danke!

    Nun meine letzte Frage: Wie kann ich nun das ersetzte Feld (Nummer) wieder in der Dat Datei speichern?

    Mittwoch, 23. August 2017 09:51
  • Nochmal zu der vorherigen Frage: An dieser Stelle auf keinen Fall eine Schleife, wenn dann
    $KeyDatei | foreach{$a = $_ -split "#";$KeyHT[$a[0]] = $a[1]}
    Du kannst mit Out-File oder Set-Content Dateien mit Text befüllen. Also wenn du es, wie in meinem Code oben, ersetzt hast, einfach
    $DatDatei | Out-File "Zielpfad"
    Ich empfehle aber dringend, nicht die Datei zu überschreiben. Schreib die Daten lieber in eine andere Datei (einfach einen anderen Dateinamen in einem existierenden Ordner). Damit bei einem Fehler nicht das Original weg ist.
    Mittwoch, 23. August 2017 10:11
  • Danke!
    Mittwoch, 23. August 2017 10:36
  • Wenn deine Fragen damit beantwortet sind, markiere bitte noch die Antworten.

    Blog: http://bytecookie.wordpress.com

    Kostenloser Powershell Code Manager v5: Link
    (u.a. Codesnippets verwalten + komplexe Scripte graphisch darstellen)

    Hilf mit und markiere hilfreiche Beiträge mit dem "Abstimmen"-Button (links) und Beiträge die eine Frage von dir beantwortet haben, als "Antwort" (unten).
    Warum das Ganze? Hier gibts die Antwort.

    Mittwoch, 23. August 2017 10:44
    Moderator
  • Ich habe jetzt eure Tipps und Anmerkungen mit eingebaut, allerdings werden die alphanummerischen Nummern nicht erstezt, es liegt also noch ein Fehler bei dem ersetzten vor.

    Anhand des Outputs sehe ich aber das das Script die richtigen Nummern findet und auch durch die richtigen ersetzten würde.

    $PathToWorkDat = "E:\Script\Test\pkha_V0PKHL06_330.dat" $PathPaareCSV = "E:\Script\Test\Kostenstellen_alphanum2num.csv" $DatDatei = Import-CSV $PathToWorkDat -Delimiter "#" -Header "a", "b","c","d","e","f","g","h","i","j","k","l" $KeyDatei = Import-Csv $PathPaareCSV -Delimiter ";" -Header "Verschlüsselt", "Entschlüsselt" foreach ($Zeile in $DatDatei) { echo $Zeile.f If($Zeile.f -match '^\d+$'){ Write-Host "Weiter" } else{ Write-Host "Alphanummerisch"

    $Treffer = $KeyDatei | where{$_.Verschlüsselt -eq $Zeile.f} if($Treffer -eq $null){Write-Host "Kein Treffer"} else{Write-Host "Treffer"} #Hier fehlt noch was oder? $Zeile.f = $Treffer.Entschlüsselt $DateiDat|Out-File "E:\Script\Test\pkha.dat" } }


    Mittwoch, 23. August 2017 10:46
  • Das Out-File muss ganz ans Ende des Skriptes. Momentan exportierst du bei jedem Schleifendurchlauf. Außerdem hast du in deiner Exportzeile $DateiDat stehen, obwohl es immer $DatDatei heißt.

    Die Zuweisung ($Zeile.f = ...) sollte lieber mit in den else-Block, der momentan "Treffer" schreibt. Aber das ist nur Schönheit.

    Eigentlich sehe ich nicht, warum es nicht funktionieren sollte.

    Mittwoch, 23. August 2017 11:18
  • Du hast recht, so klappt es, nur die Formatierung ist jetzt falsch.

    Die Ausgabe sieht so aus:

    a: 0123

    b: 001

    und so weiter, also die Überschrifen die ich gesetzt habe stehen jetzt untereinader und die Werte nach einem Doppelpunkt dahinter.

    Ursprünglich stand ein Datensatz in einer Zeile mit # getrennt, gibt es auch hierfür einen Trick?

    Mittwoch, 23. August 2017 11:47
  • Das ist kein Trick, PowerShell führt die Befehle aus, die man einstellt. Aber stimmt, ich habe bei meinem Verweis auf Out-File nicht richtig nachgedacht. 

    Es gibt noch Export-Csv und ConvertTo-Csv. Ersteres exportiert in einer Datei, zweiteres in eine Variable. Wenn du den Header nicht willst, kannst du ihn mit dem zweiten Befehl unterdrücken:

    $DatDatei | Export-CSV "Path" -Delimiter "#" -NoTypeInformation
    
    #oder ohne Header
    $DatDatei | ConvertTo-CSV -delimiter "#" | select -skip 1 | out-File "Path"

    Mittwoch, 23. August 2017 12:57
  • Wenn ich das so mache, dann werden die erstzten Nummern nicht übernommen, bei Out-File schon.

    (Sorry das ich noch weiter nerven muss)

    Donnerstag, 24. August 2017 11:51
  • An der Stelle steht in der Datei jetzt folgendes:

    System.Object[]

    Donnerstag, 24. August 2017 12:29
  • Okay, System.Object[] ist ein Array (darauf weist [] hin). Ich hatte ganz oben mal im Code diese Anmerkung

    #ich unterstelle, dass nur ein Eintrag in KeyDatei gefunden werden kann (eindeutige Zuordnung)

    gemacht. Wenn der Treffer ein Array ist, heißt das, dass mehrere Einträge gefunden werden. Schau mal in deiner Key-Datei, ob die betreffenden Einträge mehrmals drinstehen. 

    Sollte das so sein, musst du dir noch überlegen, welchen Eintrag du dann möchtest. Also: Den ersten, den letzten, irgendeinen,...

    PS: Und nein, du nervst nicht. Es fangen alle mal an :D



    • Bearbeitet hpotsirhc Donnerstag, 24. August 2017 12:46
    Donnerstag, 24. August 2017 12:45
  • Das Problem hat sich erledigt, danke. War einfach nur ein Fehler meinerseits.

    Letzte Sache: alle einträge sind in Anführungszeichen in der erzeugten CSV:

    "012342" "#" "4325" "#"

    kann man die bei der ConvertierenTo-CSV Methode entfernen?

    Donnerstag, 24. August 2017 13:01
  • Natürlich, solange deine Datei sonst keine gewollten Anführungszeichen enthalten soll:
    ($DatDatei | ConvertTo-CSV -delimiter "#") -replace '"' | select -skip 1 | out-File "Path"

    Freitag, 25. August 2017 07:59
  • Ich danke dir vielmals für deine Zeit und Hilfe!!
    Freitag, 25. August 2017 08:20
  • Bitte, kein Problem. :-)
    Freitag, 25. August 2017 09:18