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!!!
Weiterführende Abhandlungen zum Thema CSV auf deser Webseite
CSV in Batches
Datenbanken in Javascript
Erstellung von VCards aus CSV