Excel? Nein - AWK und Gnuplot!

vorhergehende Artikel in: Linux
17.12.2020

Nachdem ich neulich ein Video zum Thema AWK gefunden hatte habe ich gedacht - da geht noch mehr

@youtube.com

Es ging dabei darum, dass man LibreOffice Calc oder ähnliche Programme nicht benötigt wenn man ein Linux-Terminal hat. Ausgehend von Garys Beispielen habe ich mir eine schnelle Liste von Anforderungen aufgestellt, die ich hier mal mit Syntaxbeispielen zu Lösung darstellen möchte.

Beispiel-Setup

Ich bin von einer ähnlichen Datenquelle wie Gary ausgegangen - ein einfaches Dateisystem-Listing mittels ls -l sorgte für Input. Ich musste die erste Zeile noch abschneiden damit ich sauber mit tabellarischen Daten arbeiten konnte - dafür sorgte tail -n +2.

Als erstes wird das Feld mit der Dateigröße in jeder Zeile der Ausgabe ausgegeben:

ls -l /usr/bin |tail -n +2| awk '{print $5}'

Die Anzahl der Zeilen kann man mittels wc ermitteln:

ls -l /usr/bin |tail -n +2| wc -l

Das funktioniert aber auch mit AWK:

ls -l /usr/bin |tail -n +2| awk 'END{print NR}'

Man kann die extrahierten Daten jeder Zeile durch eine vorangestellte Zeilennummer ergänzen:

ls -l /usr/bin |tail -n +2| awk '{print NR":"$5}'

Statistik

Nun ein wenig Statistik: Die Berechnung des Durchschnitts (Mittelwerts) der Daten in Spalte 5 erfolgt so:

ls -l /usr/bin |tail -n +2| awk 'BEGIN{sum=0.0}{sum+=$5}END{printf("Average: %f\n",sum/NR)}'

Variablen werden von AWK immer auf 0 initialisiert - daher kann man im vorigen Statement die BEGIN Klausel auch weglassen. Es ist auch möglich, Variablen auf der Kommandozeile zu vereinbaren und dem Skript zu übergeben:

ls -l /usr/bin |tail -n +2| awk -v N=5 '{sum+=$N}END{printf("Average: %f\n",sum/NR)}'

Wir berechnen zusätzlich die Standardabweichung:

ls -l /usr/bin |tail -n +2| awk -v N=5 \
'{sum+=$N;sumsq+=$N^2}END{printf("Average: %f Std deviation: %f\n",sum/NR,sqrt((sumsq-sum^2/NR)/NR))}'

Dieses Mal wird der Median bestimmt:

ls -l /usr/bin |tail -n +2| awk -v N=5 '{print $N}'|sort -n| awk \
'{a[i++]=$1}END{x=int((i+1)/2);if (x < (i+1)/2) y=(a[x-1]+a[x])/2; else y=a[x-1]; printf("Median: %f\n",y)}'

Möchte man erfahren, ab welchem Wert ein bestimmter Prozentsatz der Eingaben jeweils unterhalb dieses Wertes liegen (Percentile), kann man das ebenfalls mittels AWK herausfinden:

ls -l /usr/bin |tail -n +2| awk -v N=5 \
'{print $N}'|sort -n| awk '{s[NR-1]=$N} END{for(i=0.1;i<=1.0;i+=0.1){printf("%d %f\n",i*100,s[int(NR*i-0.5)])}}'

Man kann mittels AWK auch Histogramme erzeugen:

ls -l /usr/bin |tail -n +2| awk -v N=5 '{print $N}'|sort -n| awk -v DELTA=150000 \
'BEGIN{delta = (DELTA == "" ? 10000 : DELTA)} \
{bucketNr = int(($0+delta) / delta);cnt[bucketNr]++;numBuckets = (numBuckets > bucketNr ? numBuckets : bucketNr)} \
END{for (bucketNr=1; bucketNr<=numBuckets; bucketNr++) {end = beg + delta;printf("%0.1f %0.1f %d\n"), beg, end, cnt[bucketNr];beg = end;}}'>hist.dat

Gnuplotting

In diesem Fall sollten wir uns auf den interessanten Teil des Histogrammes beschränken - das zeigt auch gleich, dass man mehrere Variablen auf der Kommandozeile vereinbaren kann:

ls -l /usr/bin |tail -n +2| awk -v N=5 '{print $N}'|sort -n| awk -v DELTA=1000 -v MAX=15001  \
'BEGIN{delta = (DELTA == "" ? 10000 : DELTA)} \
{if($0<15001){bucketNr = int(($0+delta) / delta);cnt[bucketNr]++;numBuckets = (numBuckets > bucketNr ? numBuckets : bucketNr)}} \
END{for (bucketNr=1; bucketNr<=numBuckets; bucketNr++) {end = beg + delta;printf "%0.1f %0.1f %d\n", beg, end, cnt[bucketNr];beg = end;}}'>hist.dat

Man kann dieses Histogramm direkt mit Gnuplot im Terminal darstellen - die Größe des Plots passt sich automatisch der Größe des Terminals an...

ls -l /usr/bin |tail -n +2| awk -v N=5 '{print $N}'|sort -n| awk -v DELTA=1000 -v MAX=15001  \
'BEGIN{delta = (DELTA == "" ? 10000 : DELTA)} \
{if($0<MAX){bucketNr = int(($0+delta) / delta);cnt[bucketNr]++;numBuckets = (numBuckets > bucketNr ? numBuckets : bucketNr)}} \
END{for (bucketNr=1; bucketNr<=numBuckets; bucketNr++) {end = beg + delta;printf "%0.1f %0.1f %d\n", beg, end, cnt[bucketNr];beg = end;}}' \
|gnuplot -p -e "set terminal dumb size $(tput cols), $(tput lines) enhanced; set autoscale;set style data histogram;set style fill solid;plot '-' using 3:xtic(1)"

Bedingungen

Es ist möglich, nur bestimmte Zellen in der Berechnung zu benutzen - Man kann zum Beispiel anhand regulärer Ausdrücke bestimmte Zeilen ausschließen:

ls -l /usr/bin |tail -n +2| awk -v N=5 \
'{if($9 ~ /^m/){sum+=$N;sumsq+=$N^2;count++}} \
END{printf("Average: %f Std deviation: %f Count: %d\n",sum/count,sqrt((sumsq-sum^2/count)/count),count)}'

Es ist dabei auch möglich, die Groß- und Kleinschreibung zu ignorieren:

ls -l /usr/bin |tail -n +2| awk -v N=5 \
'{IGNORECASE = 1;if($9 ~ /^m/){sum+=$N;sumsq+=$N^2;count++}} \
END{printf("Average: %f Std deviation: %f Count: %d\n",sum/count,sqrt((sumsq-sum^2/count)/count),count)}'

Eine alternative Schreibweise dafür:

ls -l /usr/bin |tail -n +2| awk -v N=5 \
'IGNORECASE = 1 && $9 ~ /^m/{sum+=$N;sumsq+=$N^2;count++} \
END{printf("Average: %f Std deviation: %f Count: %d\n",sum/count,sqrt((sumsq-sum^2/count)/count),count)}'

Es ist natürlich auch möglich, die in der Berechnung zu berücksichtigenden Zeilen einfach über ihre Zeilennummern zu bestimmen:

ls -l /usr/bin |tail -n +2| awk -v N=5 \
'{if(NR >9 && NR<21){sum+=$N;sumsq+=$N^2;count++}} \
END{printf("Average: %f Std deviation: %f Count: %d\n",sum/count,sqrt((sumsq-sum^2/count)/count),count)}'

Eine alternative Schreibweise dafür:

ls -l /usr/bin |tail -n +2| awk -v N=5 \
'(NR >9 && NR<21){sum+=$N;sumsq+=$N^2;count++} \
END{printf("Average: %f Std deviation: %f Count: %d\n",sum/count,sqrt((sumsq-sum^2/count)/count),count)}'

Es ist sogar möglich, Werte über den Vergleich von Daten (Zeitpunkten) zu filtern:

ls -lt --time-style=long-iso /usr/bin|awk -v date=2020-06-09 'date<$6{print $8}'

Funktionen

Es gibt auch Möglichkeiten, Funktionen zu definieren - während int(n) einfach nur die Nachkommastellen abschneidet, kann man in AWK eine korrekte Rundungsfunktion wie folgt definieren:

func round(n)
{
  return int(n+0.5)
}

Eine Funktion für die nächstgrößere ganze Zahl ist ebenfalls sehr einfach zu schaffen:

function ceil(n)
{
  return n%1 ? int(n)+1 : n
}

Cheat-Sheet

Wer möchte, kann sich das hier angehängte Cheat-Sheet, das ich aus einem Template erstellt habe auch ausdrucken...

Lizenz

TeX-Sourcecode

Cheat-Sheet

Aktualisierung vom 17. Dezember 2020

Eine weitere Idee ist das Einfärben einzelner Abschnitte (Zellen) in einer Datei - wieder am Beispiel eines Dateisystemlistings mittels ANSI-Escape-Codes. Hier wird die Spalte mit der Dateigröße in Bytes farblich hervorgehoben, wenn der jeweilige Wert größer als eine festgelegte Schwelle ist. Das Beispiel funktioniert nur in Terminals, die Farben darstellen und ANSI-Escape-Sequenzen interpretieren können - beispielsweise xterm.

ls -l /usr/bin |tail -n +2| \
awk '{if($5>999999)print NR"\t"$1"\t"$2"\t"$3"\t"$4"\t\033[1;31m"$5"\033[0m"; \
else print NR"\t"$1"\t"$2"\t"$3"\t"$4"\t"$5;}'

Das Cheat-Sheet wurde natürlich entsprechend erweitert...

Artikel, die hierher verlinken

GPS-(NMEA-)Tracks mit Gnuplot zu Animationen verarbeiten

02.11.2021

Durch einen Post auf Mastodon wurde ich auf eine Idee gebracht...

AWK Linksammlung

29.10.2021

Ich habe bereits darüber berichtet, dass man in vielen Fällen AWK den Vorzug vor Excel und ähnlichem geben kann (und sollte!) - hier nun einige weitere Inspirationen dahingehend

Gnuplot und Statistik

26.05.2021

Ich bin neulich bei Mastodon über eine Anfrage gestolpert, wie man online schnell eine graphische Darstellung statistischer Daten mit Mittelwert usw. anfertigen könnte. Ich gab dazu einige Suchparameter ergänzt um den Schlüsselbegriff "Gnuplot" ein und fand sofort einen Artikel, der erklärte, wie man das angefragte Szenario mittels Gnuplot umsetzen könnte.

Renderer für generierte Dungeons mit Ansi-Steuercodes

15.01.2021

Nachdem ich neulich mein AWK-Kochbuch um ein neues Rezept erweitert habe mit dem es möglich ist, einzelne Felder einer Datei regelbasiert mittels Ansi-Escape-Sequenzen unterschiedlich einzufärben, hat mich der Ansi-Steuercode-Virus wieder voll erwischt...

Alle Artikel rss Wochenübersicht Monatsübersicht Github Repositories Gitlab Repositories Mastodon Über mich home xmpp


Vor 5 Jahren hier im Blog

  • Fährnisse des Buildprozesses unter Windows

    17.07.2019

    Nachdem ich begonnen hatte, mich mit der Beschleunigung der Berechnung des Mandelbrot-Fraktals unter Zuhilfenahme der Shadereinheiten in Graphikkarten zu beschäftigen und erste Erfolge feiern konnte, wollte ich das mal auf einer richtigen Graphikkarte ausprobieren...

    Weiterlesen...

Neueste Artikel

  • Datenvalidierung UTF8 mit BiDi-Steuerzeichen (TrojanSource 2.0)

    Ich bin heute nochmal inspiriert worden, weiter über die Trojan Source Vulnerability nachzudenken. Meiner Meinung nach bestehen hier noch Probleme - speziell bei Nutzereingaben oder Daten, die über externe Schnittstellen ampfangen werden.

    Weiterlesen...
  • OpenStreetMap Navi als Docker-Container

    Ich habe die auf OpenStreetMap basierende OpenSource Navigationslösung Graphhopper in einen Docker-Container gepackt und als neuestes Mitglied in meinem Docker-Zoo willkommen geheißen.

    Weiterlesen...
  • SQL-Aggregatfunktionen in SQLite als BeanShell-Scripts

    Ich habe neulich über eine Möglichkeit berichtet, SQLite mittels der sQLshell und Beanshell-Skripten um SQL-Funktionen zu erweitern. In diesem Artikel versprach ich auch, über eine solche Möglichkeit für Aggregatfunktionen zu berichten.

    Weiterlesen...

Manche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.

Wer daran teilhaben und eventuell sogar davon profitieren möchte, muß damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.

Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...

PS: Meine öffentlichen GitHub-Repositories findet man hier - meine öffentlichen GitLab-Repositories finden sich dagegen hier.