Auswertung von CSV- und Log-Dateien auf der Command Line mit awk
Die Programmiersprache awk ist klein und unscheinbar, unter Data Science at the Command Line-Verfechtern allerdings ein häufiges Tool zur schnellen Analyse von CSV-Datein und vergleichbar strukturierten Daten (z. B. Logfiles) mit über Trennzeichen differenzierten Spalten. Auch in Shell-Skripten kommt awk meistens dann zum Einsatz, wenn es um den Zugriff, aber auch um die Manipulation von solchen Dateien geht.
Data Science at the Command Line: Facing the Future with Time-Tested Tools
awk wird als Skriptsprache mit nahezu jeder Linux-Distribution ausgeliefert und ist recht einfach eingehalten, kann jedoch auch schnell kryptisch werden. awk wird meistens ad-hoc auf der Kommandozeile ausgeführt, es können jedoch auch Skripte in awk-Dateien erstellt werden. Häufiger Grund für den Einsatz von awk ist die Anwendung von regulären Ausdrücken (Textmustersuche) auf Logdateien.
Nachfolgend ein kleines Tutorial für den Schnelleinstieg in diese interessante Analysetool auf Kommandozeile. Die CSV-Datei einfach hier downloaden: (einen Überblick über den Inhalt bietet auch eine Einführung in Python, die ebenfalls auf dieser CSV-Datei basiert)
wget https://www.data-science-blog.com/download/standorte.csv
CSV-Datei gedownloaded? Dann kann es losgehen im Terminal jeder beliebiger Linux-Distribution:
awk -F'|' '{print}' standorte.csv
Anweisungen, so auch die obige, beginnen stets mit “awk”. Da diese CSV-Datei nicht mit dem Standardchar (Komma), sondern einem vertikalen Strich (Pipe) getrennt ist, muss dies via “-F’|'” angegeben werden. Wäre das Trennzeichen ein Semikolon, wäre der Parameter “-F’;'” korrekt. Der Befehl gibt jede Zeile des CSV in der Kommandozeile aus, so dass wie nachfolgend den gesamten Dateiinhalt sehen:
ID|Standort|Funktion|Mitarbeiter|Umsatz|Kosten 1|Muenchen|Verwaltung + Vertrieb|45|3500000|2300000 2|Stuttgart|Nur Vertrieb|23|2800000|800000 3|Hannover|Verwaltung + Vertrieb|45|1800000|1000000 4|Leipzig|Nur Vertrieb|12|1000000|320000 5|Dresden|Produktio + Vertrieb|65|450000|700000 6|Frankfurt am Main|Nur Vertrieb|12|240000|20000 7|Duesseldorf|Nur Vertrieb|43|45000|53000 8|Kassel|Nur Vertrieb|23|250000|90000 9|Hamburg|Verwaltung + Vertrieb|89|2800000|690000 10|Koeln|Nur Vertrieb|21|110000|12000 11|Potsdam|Nur Vertrieb|12|20000|67000 12|Nuernberg|Nur Vertrieb|15|60000|30000 13|Ingolstadt|Nur Vertrieb|8|80000|10000 14|Wolfsburg|Nur Vertrieb|8|90000|23000 15|Braunschweig|Nur Vertrieb|32|900000|750000 16|Augsburg|Verwaltung + Vertrieb|45|700000|370000 17|Chemnitz|Nur Vertrieb|4|95000|78000 18|Bochum|Nur Vertrieb|9|32000|67000 19|Dortmund|Produktio + Vertrieb|56|2100000|450000 20|Essen|Nur Vertrieb|10|190000|140000
Viele CSV- und Logdateien haben keinen Header, diese hier hat jedoch die erste Zeile als Header, die daher bei der Analyse nicht als Werte-Zeile fehlinterpretiert werden darf, daher wird nachfolgend von nun an die Anweisung “NR>1” mitgegeben:
awk -F'|' 'NR>1 {print}' standorte.csv
Spalten werden in awk über das Dollarzeichen angesprochen, folgende Anweisung zeigt uns alle Zeilen der zweiten Spalte:
awk -F'|' 'NR>1 {print $2}' standorte.csv
Diese Skriptsprache beherrscht assoziative Arrays. Es können demnach auch nicht-numerische Schlüssel für den Zugriff auf Datenfelder verwendet werden. Dies machen wir uns für das Anzeigen aller Standorte mit Angabe der jeweiligen Mitarbeiterzahl an dem Standort zu nutze. Die Variable a speichert alle Mitarbeiterzahlen in Spalte 4 über den Schlüssel des Standortnamens in Spalte 2, dann endet der Anweisungsblock und es folgt eine For-Schleife, die alle Schlüsselwerte ausgibt und den dazugehörigen Speicherwert (Mitarbeiterzahl) ausgibt.
awk -F'|' 'NR>1{a[$2] = $4;} END {for (i in a) print "Standort: " i " - Mitarbeiterzahl: " a[i];}' standorte.csv
Standort: Essen - Mitarbeiterzahl: 10 Standort: Bochum - Mitarbeiterzahl: 9 Standort: Hannover - Mitarbeiterzahl: 45 Standort: Frankfurt am Main - Mitarbeiterzahl: 12 Standort: Dresden - Mitarbeiterzahl: 65 Standort: Wolfsburg - Mitarbeiterzahl: 8 Standort: Dortmund - Mitarbeiterzahl: 56 Standort: Braunschweig - Mitarbeiterzahl: 32 Standort: Chemnitz - Mitarbeiterzahl: 4 Standort: Augsburg - Mitarbeiterzahl: 45 Standort: Leipzig - Mitarbeiterzahl: 12 Standort: Duesseldorf - Mitarbeiterzahl: 43 Standort: Nuernberg - Mitarbeiterzahl: 15 Standort: Hamburg - Mitarbeiterzahl: 89 Standort: Muenchen - Mitarbeiterzahl: 45 Standort: Potsdam - Mitarbeiterzahl: 12 Standort: Kassel - Mitarbeiterzahl: 23 Standort: Koeln - Mitarbeiterzahl: 21 Standort: Ingolstadt - Mitarbeiterzahl: 8 Standort: Stuttgart - Mitarbeiterzahl: 23
Auch If-Anweisungen sind einfach machbar. Folgendes Beispiel unterscheidet die Zeilennummern (Spalte1) nach geraden und ungeraden Zahlen und gibt den dazugehörigen Standortnamen (Spalte 2) aus.
awk -F'|' 'NR>1 {if ($1 % 2 == 0) print "Gerade: " $1"->"$2; else print "Ungerade: " $1"->"$2} ' standorte.csv
Ungerade: 1->Muenchen Gerade: 2->Stuttgart Ungerade: 3->Hannover Gerade: 4->Leipzig Ungerade: 5->Dresden Gerade: 6->Frankfurt am Main Ungerade: 7->Duesseldorf Gerade: 8->Kassel Ungerade: 9->Hamburg Gerade: 10->Koeln Ungerade: 11->Potsdam Gerade: 12->Nuernberg Ungerade: 13->Ingolstadt Gerade: 14->Wolfsburg Ungerade: 15->Braunschweig Gerade: 16->Augsburg Ungerade: 17->Chemnitz Gerade: 18->Bochum Ungerade: 19->Dortmund Gerade: 20->Essen
Folgendes Beispiel klassifiziert alle Standorte mit weniger als 10 Mitarbeitern, allerdings nicht über “if…else…”, sondern über die Kurzabfrage nach dem Schema a>b?”True”:”False”.
awk -F'|' 'NR>1 {a[$2]=$4>=10?$2"->"$4:$2" hat weniger als 10 Mitarbeiter"; print a[$2]}' standorte.csv
Muenchen->45 Stuttgart->23 Hannover->45 Leipzig->12 Dresden->65 Frankfurt am Main->12 Duesseldorf->43 Kassel->23 Hamburg->89 Koeln->21 Potsdam->12 Nuernberg->15 Ingolstadt hat weniger als 10 Mitarbeiter Wolfsburg hat weniger als 10 Mitarbeiter Braunschweig->32 Augsburg->45 Chemnitz hat weniger als 10 Mitarbeiter Bochum hat weniger als 10 Mitarbeiter Dortmund->56 Essen hat weniger als 10 Mitarbeiter
Folgendes Code-Beispiel zeigt die Zählung der Vorkommnisse (Entsprechung: GROUP BY Spalte3, Count(*)).
awk -F'|' 'NR>1 {a[$3]++;} END {for (i in a) print i, a[i];}' standorte.csv
Produktio + Vertrieb 2 Verwaltung + Vertrieb 4 Nur Vertrieb 14
Etwas umformuliert, können wir auch die Werte pro Gruppe aufsummieren, nachfolgend beispielhaft der Gewinn (Einnahmen aus Spalte 5 – Kosten aus Spalte 6) und die Mitarbeiterzahl über die jeweilige Gruppe.
awk -F'|' 'NR>1{a[$3]+=$5-$6; b[$3]+= $4}END{for (i in a) print i "; Gewinn: " a[i] "; Mitarbeter: " b[i];}' standorte.csv
Produktio + Vertrieb; Gewinn: 1400000; Mitarbeter: 121 Verwaltung + Vertrieb; Gewinn: 4440000; Mitarbeter: 224 Nur Vertrieb; Gewinn: 3452000; Mitarbeter: 232
Das Zusammenführen von Zeichenketten erfolgt simpel durch Aneinandereihung:
c = a b # Verknüpfung ohne Seperator c = a";"b # Verknüpfung mit Semikolon als Seperator
awk -F'|' 'NR>1 {if (a[$3] < $6) a[$3] = $2"->"$6;} END {for (i in a) {print i "->" a[i];}}' standorte.csv
Produktio + Vertrieb->Dresden->700000 Verwaltung + Vertrieb->Muenchen->2300000 Nur Vertrieb->Stuttgart->800000
Ein letztes Beispiel möchte keine einzelnen Zeilen des Datensatzes auflisten und auch keine Gruppierung unterscheiden, sondern die Zusammenfassung über die Angabe der gesamten Mitarbeiteranzahl und der Gewinn-Summe über alle Standorte angeben.
awk -F'|' 'NR>1{x+=$4;y+=$5-$6} END {print "Summe Mitarbeiter: " x " Summe Gewinn: " y}' standorte.csv
Summe Mitarbeiter: 577 Summe Gewinn: 9292000
Fazit
Als Programmiersprache ist awk sicherlich nur ein nice-to-have, aber wenn man das Prinzip dieser Sprache erstmal verstanden hat, kann sie ein interessantes Tool darstellen, um schon auf Kommandozeilenebene sich schnell einen Überblick über Datenbestände zu beschaffen und auch um Datenqualitätstests durchzuführen.