CSV mit Powershell

Einleitung

Powershell löst zunehmend Batches ab, deshalb ist es sinnvoll, grundlegende Konstruktionen sich auszudenken, die man bei Bedarf dann anwendet.

Das kann man prima bei der Erstellung von Datenbanken machen. Es geht dabei ausdrücklich nicht darum das Commandlet import csv

Nein, der Autor schreibt für Normal-User, die kleine Exceltabellen in CSV-Tabellen umwandeln und dann ein wenig mit ein paar Tricks  die Tabelle bearbeiten wollen:
Excel oder dessen Pendant in  der Open-Office-Welt sind nach wie vor die erste Wahl.

Ich bringe Ihnen entgegen Lösungen, die sie dann in Javascript oder VB-Script übertragen können, weil wir uns ganz normaler Schleifen bedienen.


Inhaltsangabe
Grundsätzliches
Tabelle nach eigenen Wünschen gestalten
Eine Suchfunktion schreiben
Eine GUI erstellen
Fazit


Grundsätzliches

Fangen wir einmal ganz von vorn an: Datenbanken sind Tabellen in Zeilen und Reihen mit einer Überschrift. Zwischen den Reihen steht in jeder Zeile ein sogenannter Separator, also ein Leerzeichen, Komma, Semikolon, Doppelpunkt.

Die heißt z. B. csv.txt

Manfred, Müller, 20.05.1960
Udo, Baumberg, 03.03.2002
Anton, Kracher, 08.09.2003

ist so eine Tabelle mit drei Zeilen und drei Reihen ohne Überschrift. Diese Tabelle wird uns fortan begleiten. Copy und Paste sind anzuraten und dann bitte auch den o. a. Dateinamen verwenden. Sie kommen sonst ins Rudern!!!

CSV ist nichts weiter als eine Formatendung und genauso könnte auch ein ordinäres Textdokument *.txt eine CSV beinhalten. Sie können jetzt gerne auch fantasieren und ein Textdokument verändern, indem sie die Dateiendung in z. b. irgendeinname.mgd umändern (meine geniale Datenbank). Dann steht da immer noch der Text unverschlüsselt drin und mit dem Editor können sie nach wie vor die Datei öffnen und in Batches verwenden.

Das tut man nicht, schon um allein nicht eine vielleicht schon existierende Dateiform zu treffen -:)

Mit dieser kleinen Datentabelle arbeiten wir in Zukunft, die im selben Verzeichnis wie das zugehörige Powershellscript gelegt wird. Das ist wichtig, weil wir mit relativen Bezügen arbeiten und nicht immer durch dämlich lange Dateipfade, die bei Ihnen garantiert anders aussehen wie bei mir, verwenden.

Nennen sie die abgespeicherte Datei csv.txt, dann arbeiten sie mit mir parallel. Dann werden wir einmal unser erstes Script erstellen, um die CSV-Bearbeitung zu verdeutlichen.
Fortgeschrittene steigen dann vermutlich schon aus, weil sie kapiert haben, wie es grundsätzlich geht und der Rest ist Syntax - da kann ich auch nicht alles abarbeiten, dass sprengt den Rahmen.

Also irgendeinname.ps1 erstellen und testen:

  $array ="csv.txt"
$fileContent = get-content $array
for ($i=0; $i -lt $fileContent.length; $i++){
$tbzeile = $fileContent[$i]
$tbzeile= $tbzeile.split(",")
$tbzeile= $tbzeile[1]
$ergebnis =  "$tbzeile"
$ergebnis >>test.txt
}


Dieses kleine Skript zieht jetzt die zweite Reihe und gibt diese im Dokument test.txt aus. Deshalb auch zwei Ausgabezeichen, weil sonst bei jedem Schleifendurchgang das letzte Ergebnis überschrieben werden würde.

Im neuen Dokument steht dann Mülle, Baumberg und Kracher untereinander. Ich habe die zweite Reihe separiert. Wer jetzt nur Bahnhof verstanden hat, wir gehen im Schnelldurchlauf die Zeilen des Codes Zeile für Zeile durch, so gesehen ist das eine "Codetabelle" mit einer Zeile -:)

Am Anfang definiere ich eine Variable, die ich Array nenne: Der Name ist egal, aber da eine Tabelle nichts weiteres ist als ein multidimensionales Array, finde ich das zulässig...

In der zweiten Zeile ziehe ich mir den Inhalt der Tabelle: Ich habe so gesehen eine Objekthülle mit Inhalt.

In der dritten Zeile eröffne ich eine Schleife, die von der Zeilenlänge (length) her genauso lang wie die Zeilenlänge der Tabelle ist. Ich arbeite also Zeile für Zeile in Zukunft ab.

Diese Schleife könnten sie nur mit einer winzigen Abänderung bis hierher, nämlich dem notwendigen Parameter -lt  in Javascript verwenden. Die Schleife wird von 0-2 abgearbeitet.
Hätte ich jetzt eine Überschrift, dann würde aus $i=0 eben $i=1 werden, da die Schleife dann von 1 aus weiterzählt und immer um eine 1 $i++ erhöht wird.

$tbzeile = $fileContent[$i]


In Wirklichkeit schaffe ich nur auf der linken Seite eine Variable, in dem ich je nach Schleifendurchlauf eine Zeile nach der anderen abarbeite: Im ersten Durchlauf steht da praktisch

$tbzeile = $fileContent[0]

Dann mache ich den Zeileninhalt lesbar: $tbzeile= $tbzeile.split(",") . Durch den Split-Befehl wird die Zeile als String mit drei Separatoren erfasst: Jedes Komma ist also ein Separator: Da könnte zwischen den Anführungszeichen genauso ein Doppelpunkt, Semikolon oder Leerzeichen stehen - aber wir verwendeten nun einmal Kommata als Separatoren...

$tbzeile= $tbzeile[1]

Jetzt reduzieren wir die Variable auf einen Ausschnitt, nämlich immer auf die Reihe zwei, deshalb steht dort auch eine 1, weil wir die Nachnamen haben wollen.

Am Ende packe ich den selektierten Ausschnitt in eine neue Variable und gebe diesen aus. Zeile für Zeile wird die neue Tabelle aufgebaut.

Was sollten Sie jetzt können?

Sie wissen nun, wie man an eine Reihe herankommt und wie man an eine Zeile herankommt.


Tabelle nach eigenen Wünschen gestalten



Damit können sie bereits grobe Gestaltung einer Tabelle vornehmen. Statt einer gesplitteten Variable können sie Natürlich nun jede Reihe separieren - alles innerhalb des vorgegebenen Codegerüstes:

Beispiel:

$array ="csv.txt"
$fileContent = get-content $array
for ($i=0; $i -lt $fileContent.length; $i++){
$someFilePath = $fileContent[$i]
$someFilePath= $someFilePath.split(",")
$someFilePath= $someFilePath[1]
$bsp = $fileContent[$i]
$bsp= $bsp.split(",")
$bsp= $bsp[2]
$ergebnis =  $bsp +"," + $someFilePath
$ergebnis >>test.txt
}


Ich habe die alten Variablen beibehalten und nur der rote Text ist erklärungswürdig.

Ich lasse also nur noch den  Namen und das Geburtsdatum ausdrucken, indem ich nach derselben Technik den zweiten String zusätzlich sichere  $bsp= $bsp[2].

Dann manipuliere ich die Ergebnisausgabe, damit ich nun eine neue Tabelle mit zwei Reihen und einem Komma als Separator ausdrucke $ergebnis =  $bsp +"," + $someFilePath
Das könnte ich nun auch mit drei, vier, fünf..X- Reihen bei größeren Tabellen machen.

Ich kann also Reihen beliebig umstellen oder wegfallen lassen, alles mit einem Basiskonstrukt, der eben funktioniert und bekomme am Ende eine "CSV" heraus, die ich mit Excel auch wieder einlesen kann. Ich würde immer ein Komma als Separator benutzen.

Auch kann ich das ganze leicht nummerieren:

$array ="csv.txt"
$nr=0
$fileContent = get-content $array
for ($i=0; $i -lt $fileContent.length; $i++){
$tbzeile = $fileContent[$i]
$tbzeile= $tbzeile.split(",")
$tbzeile= $tbzeile[1]
$bsp = $fileContent[$i]
$bsp= $bsp.split(",")
$bsp= $bsp[2]
$nr= $nr+1
$stnr= "$nr"
$ergebnis =$stnr + "," + $bsp +"," + $tbzeile
$ergebnis >>test.txt
}

 
Dazu lasse ich parallel eine Variable von $nr=0 hochzählen, erhöhe sie in der Schleife um 1$nr= $nr+1  
und wandle sie durch Anführungszeichen  in einen String $stnr= "$nr"
Ich gebe zu: Das ist schwer zu schlucken, geht leider nicht unkomplizierter, weil sonst Powershell die Zahlen mit Variablen addieren will und das geht nicht..., bei solchen Schwierigkeiten in der Syntax hilft dann der Autor doch noch.

So, dann haben wir auf der Metaebene erstmal alles wichtige eruiert: Nummerieren und Umstellen von Reihen, dass ist die halbe Miete.

Eine Suchfunktion schreiben


Jetzt suchen wir einmal einen Nachnamen und lassen uns dann den zugehörigen Datensatz herausgeben: Dazu ist eine Datenbank im allgemeinen da:

[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
$title = 'Suche'
$msg   = 'Gib den Suchnamen ein:'
$text = [Microsoft.VisualBasic.Interaction]::InputBox($msg, $title)
$textausgabe=$text

  $array ="csv.txt"
$fileContent = get-content $array
for ($i=0; $i -lt $fileContent.length; $i++){
$out = $fileContent[$i]
$tbzeile = $fileContent[$i]
$tbzeile= $tbzeile.split(",")
$tbzeile= $tbzeile[1]
if("$tbzeile" -Match "$textausgabe"){
$ergebnis =  $out
$ergebnis >>test.txt
}
}

So, nachdem Powershell sich bei vielen Vergleichsoperatoren erst mal doof anstellte, habe ich mit -match den entsprechenden Parameter gefunden...

Die erste Hälfte des Skriptes ist eine Inputbox nach Stack Overflow, eine der führenden englischsprachigen Foren für Fachfragen bei allen niedrigen Computersprachen.

Auf die kommt es hier nicht so an, ist halt eine hübschere Form der Eingabe:  Im Titel steht halt Suche und als Text Gib den Suchnamen ein - das ist eben immer freier Text.

Sehr wichtig ist, dass in der Variablen Text eben unserer Suchname steht  $text.

Ansonsten bleibt fast alles wie es war: Wir bauen eine Hülle für die CSV, ziehen uns den Inhalt und beginnen Zeile für Zeile zu suchen:  Dabei ziehe ich in eine zweite Variable den ganzen Inhalt  der Zeile: $out = $fileContent[$i]

Danach splitte ich wieder die Zeile und ziehe die Reihe mit den Nachnamen.

Dann wird eine if Konstruktion verschachtelt, in der ich den Input "$textausgabe" mit dem Wort aus der Zeile vergleiche "$tbzeile" und verwende dabei den Parameter -Match, der dann so ziemlich alle ähnlichen Buchstabenkombinationen zieht: Sie brauchen im Prinzip nur die ersten Buchstaben einzugeben, das Ergebnis  wird gefunden.

Bei dem Parameter -like müssen Sie hingegen ein wenig aufpassen! Wenn sie unsere test.csv. kopiert haben, dann sind nach jedem Komma natürlich Leerzeichen drin, welches erstmal unbedeutend ist: Bei -like muss aber die Buchstabenkombination der Input-Abfrage gleich dem Text bzw. Leerzeichen am Anfang und Ende sein.

Deshalb würde ich schon alleine immer Match benutzen, weil sie bei einer CSV sonst das Risiko eingehen, dass ihr wert schlichtweg nicht gefunden wird: Schuld daran ist nicht der doofe Computer, sondern der Mensch: Alles zwischen den Separatoren ist eben Bestandteil des Strings.

Denken Sie bitte auch daran: Wenn sie eine Löschfunktion implementieren, dann würde ich das nur über eine gültige Nummer der Durchnummerierung machen oder mit dem -like Parameter: Mit dem Match-Parameter löschen sie in größeren Datenbanken sonst vermutlich mehrere Datensätze...


Eine GUI erstellen


Am Ende betten wir den Code in eine GUI ein: Ich finde die Vorlage von Wordpress genial, die wie geschaffen für eine CSV-Bearbeitung ist:


[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 

$Form = New-Object System.Windows.Forms.Form   
$Form.Size = New-Object System.Drawing.Size(600,400) 

############################################## Start functions

function suchfunktion {
   $array ="csv.txt"
$fileContent = get-content $array
$wks=$InputBox.text;
for ($i=0; $i -lt $fileContent.length; $i++){
$outpt = $fileContent[$i]
$tbzeile = $fileContent[$i]
$tbzeile= $tbzeile.split(",")
$tbzeile= $tbzeile[1]
if("$tbzeile" -Match "$wks"){
$ergebnis+=  "$outpt" + "`r`n"
}
$outputBox.text=$ergebnis
}

                     } #end suchfunktion

############################################## end functions

############################################## Start text fields
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.Text = "Suchfunktion mit Nachnamen"
$form.Controls.Add($label)


$InputBox = New-Object System.Windows.Forms.TextBox
$InputBox.Location = New-Object System.Drawing.Size(20,50)
$InputBox.Size = New-Object System.Drawing.Size(150,20)
$Form.Controls.Add($InputBox)

$outputBox = New-Object System.Windows.Forms.TextBox
$outputBox.Location = New-Object System.Drawing.Size(10,150)
$outputBox.Size = New-Object System.Drawing.Size(565,200)
$outputBox.MultiLine = $True
$outputBox.ScrollBars = "Vertical"
$Form.Controls.Add($outputBox)

############################################## end text fields

############################################## Start buttons

$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(400,30)
$Button.Size = New-Object System.Drawing.Size(110,80)
$Button.Text = "Suche"
$Button.Add_Click({suchfunktion})
$Form.Controls.Add($Button)

############################################## end buttons

$Form.Add_Shown({$Form.Activate()})
[void] $Form.ShowDialog()

Das dauerte dann doch 15 Minuten, weil ich wie gesagt Powershell-Anfänger bin und den ursprünglichen Text anpassen musste:

Die Bearbeitung in einer GUI läuft nach einem etwas anderen Prinzip: Ich schaffe erstmal wieder eine leere Hülle, in der die GUI zusammengesetzt wird.
Dann Schaffe ich eine Funktion, die durch einen Button auslöse.

Dabei wird der Suchtext aus dem Inputfeld verwendet und in meiner CSV verglichen.

Nach dem Ausführen der Funktion schreibe ich das Ergebnis meiner Suchabfrage in eine Variable und die wiederum zeige ich in einem Textausgabefeld in der GUI an.

Die Basisform ist genial und zusätzlich erweiterte ich diese um ein Label, also einer kleinen Textbeschriftung, damit man weiß, worum es geht.

Da sind jetzt in dieser erweiterten Demo sämtliche Bestandteile, die sie brauchen, um eine vernünftige Eingabemaske aus mehreren Inputfeldern zu kreieren: Den Text können sie mit Copy und Paste verändern wie die Inputfelder, die dann andere Namen haben müssen wie $InputBox1 , damit zwei Namen von Variablen nicht in unterschiedlichen Textboxen vorkommen: Der Text wird dann schnell unübersichtlich. Die Outputbox hingegen bleibt immer gleich -:)

Wie man eine Eingabemaske erstellt, ist also eine Frage, wieviel Daten Sie benötigen. Für jede neue Funktion müssen Sie mindestens einen neuen Button definieren.

Außerdem würde ich eine Funktion zum Öffnen einer Datei definieren.

Die Bedürfnisse sind einfach zu verschieden, deshalb spare ich mir weitere Buttons und Inputfelder.

Am Ende gehen wir nochmal auf die angepasste Funktion zurück, damit Sie keine Probleme bekommen.

function suchfunktion {
   $array ="csv.txt"
$fileContent = get-content $array
$wks=$InputBox.text;
for ($i=0; $i -lt $fileContent.length; $i++){
$outpt = $fileContent[$i]
$tbzeile = $fileContent[$i]
$tbzeile= $tbzeile.split(",")
$tbzeile= $tbzeile[1]
if("$tbzeile" -Match "$wks"){
$ergebnis+=  "$outpt" + "`r`n"
}
$outputBox.text=$ergebnis
}

                     } #end suchfunktion

 
Vieles unserer Grundfunktion ist gleich, aber das Ganze wird erstmal eingebettet von function suchfunktion {    bis } #end suchfunktion wobei das Rautezeichen und end suchfunktion nur ein Kommentar ist - mehr nicht. $wks=$InputBox.text; ist eine neue Zeile, die erforderlich wird, weil wir den Text der Inputbox als Variable brauchen.

Die wollen wir vergleichen mit der eben schon verwendeten if-Funktion if("$tbzeile" -Match "$wks"){.
Ergebnisse werden nun nicht etwa ausgedruckt, sondern Zeilenweise in der Variable Ergebnis gespeichert $ergebnis+=  "$outpt" + "`r`n" Das muss ich so machen, weil ich die Zeilen ja untereinander sauber ausgegeben haben will und nicht in einer Reihe. $ergebnis+  heißt nur, dass das Ergebnis in der Variablen bei einem Treffer in der If-Funktion ergänzt wird mit einem Satzumbruchzeichen:  "$outpt" + "`r`n"  

Am Ende schreibe ich das Ergebnis in die outputbox $outputBox.text=$ergebnis.

Warum stört mich das nicht sonderlich? Weil ich auch über Javascript und Datenbanken mehrere Artikel schrieb und Powershell ist in dieser Hinsicht wie Javascript mit leicht unterschiedlicher Syntax, aber sein wir mal ehrlich: $ergebnis+=  ist fast um ein Zeichen Javascript pur: Ich hätte an vielen Punkten Javascript-Funktionen leicht umschreiben können.

Fazit

Sie können mit Javascript, Powershell, HTA mit einer Gui eine Datenbank schreiben, aber das ist ein wenig sinnentleert, denn wozu gibt es Excel und Access ?
Sie werden das Rad nicht neu erfinden und haben in der regel einen sehr viel kleineren Funktionsumfang.

Es kann jedoch für bestimmte Projekte eine individuelle Lösung erstellt werden und dann ist es gut zu wissen, dass eine Datenbank nichts weiter als eine Tabelle mit Reihen und Zeile ist, die durch einen Separator getrennt ist.

Viele Hürden gibt es nicht in Powershell: ü und ä sind kein Problem und ihrer Fantasie sind keine Grenzen gesetzt - mal abgesehen vom Datenschutz bei Datenbanken: Die gehören in der heutigen Zeit verschlüsselt, vielleicht sogar mit meiner Textverschlüsselung -:)

Der Autor wünscht Ihnen viel Erfolg! Mit diesen Vorlagen  kommen Sie schnell ans Ziel und das ist dann schon ein wenig anspruchsvoller als Hello World!!!