Most of us know awk as the program that you use when you want only that one column of your output. Just pipe it through awk '{print $3}' and the third column you get.

Fewer people know that awk is a programming language. It is specialized for processing structured input like tables.

As an example we will analyze this table of events at of the first Quidditch match of the first Harry Potter book, Griffindor vs Slytherin.

player team points
Johnson Griffindor 10
Spinnet Griffindor 10
Flint Slytherin 10
Flint Slytherin 10
Flint Slytherin 10
Flint Slytherin 10
Flint Slytherin 10
Flint Slytherin 10
Potter Griffindor 150

You can run all the awk programs in this blogpost with awk -F, 'PROGRAMM' quidditch.csv or by saving the awk-program into a file and running awk -F, -f 'PROGRAMMFILE' quidditch.csv. The file can be found here, it is the table from above with columns separated by ,.`

Get values from column 3

In awk we think of the input as a sequence of records (lines normally) that consists of sequence of fields (words). These fields can be adressed by $1, $2 etc (and $0 is the whole record).

Lets extract the column "points": The third field of each record.

{print $3}

Of course the header is not interesting here and we should remove it.

Remove the header

The syntax of awk is a bit different from the programming languages you might know. In man awk we read:

An awk program is a sequence of patterns and corresponding

actions. When input is read that matches a pattern, the action

associated with that pattern is carried out.

So a program is a series of statements like this:

$$\stackrel{\text{If the input matches this pattern ....}}{\overbrace{\text{pattern}}}\underset{\text{... then run this action on it}}{\underbrace{\left\{ \text{action}\right\} }}$$

In the case of our program above a special case zoccurs: If there is no pattern given every record matches.

To remove the header of the results we can simply add a condition to the program: Don't act on the first line (the record where the record number NR is 1).

NR!=1 {print $3}

Sum of a column ...

Now lets get the sum of all the points scored by the players. Our first thought here is to pipe the result into the program sum. But interestingly enough there is no such program in the world of commandlines as far as I know. It seems like our ancestors with their great beards were fine with writing their own version when they saw need to. And when they did they probably used awk.

NR!=1 {points+=$3}
END{print points}
    Two things should be noted here:
  • the line marked with END is only triggered after all the input has
  • been processed. There is a similar keyword (BEGIN) which allows code to be executed before the rest of the code. (Dough!~)
  • the variable points did not have to be initialized. awk is full
  • of sensible defaults and one of them is that a numerical variable with no value is assumed to have the value 0 if not stated otherwise.

... grouped by another column

To get the result of the Quidditch match we need to sum the points for every team separately. We can use arrays (which would be called ~dict~s in most other languages) for that.

NR!=1 {salaries[$2]+=$3}
END {for (key in salaries) { print key " " salaries[key]}}
Griffindor 170
Slytherin 60

And here we have our final score of the game.

I really know only one other language which can do this sort of processing with so little code: SQL. But SQL only works for databases so it needs a big setup to be useful. So when I find myself with a bunch of text files in a unixoid environment, then awk is the way to go.

Oh, and if you think Griffindor totally dominated this match, here is how the score would have looked like if Harry hadn't caught the snitch (which happened more or less by luck).

NR!=1 && $1 !~ "Potter" {salaries[$2]+=$3}
END {for (key in salaries) { print key " " salaries[key]}}
Griffindor 20
Slytherin 60

Die letzten Tage habe ich auf der PyconGermany in Karlsruhe verbracht. Die dominanten Themen waren Machine learning, Docker und vielleicht noch ein bisschen BigData. Über Python2 redet eigentlich keiner mehr, das ist schön.

Ich habe viel gelernt und einige spannende Leute getroffen (schöne Grüße an Corrie). Irgendwie schafft es die Pythoncommunity, ein breiteres Feld als die üblichen Nerds anzusprechen (zu denen ich mich selbst dazu zählen würde). Insbesondere das Verhaeltnis Maenner:Frauen war erfreulich ausgeglichen, wenn auch noch nicht 1:1.

Dort habe ich meinen ersten Lightningtalk gehalten. Ich war sehr aufgeregt, zumal der Vortrag auf Englisch sein sollte. Aber es lief sehr gut, ich habe das Zeitlimit eingehalten und die Aha-Momente sind übergesprungen. Ich bin sehr zufrieden.

Für die Slides habe ich Remark.js verwendet: Man hat eine html-Datei, den eigentlichen Inhalt schreibt man im Markdownformat. Das wird dann vom Javascript in Folien umgewandelt, wenn man die Datei im Browser öffnet. Das ist ein cleveres Format, weil es mehrere Vorteile vereint:

  • Schreiben in Markdown
  • Styling mit der Macht von HTML und CSS
  • Code Highlighting mit highlight.js
  • Nur ein einzelnes, schlankes Dokument (zumindest solange man keine Bilder einbettet)
  • Läuft auf jedem OS, das einen Browser hat.
  • Es gibt ein minimales Interface für den Praesentierenden, in dem Notitzen, ein Timer und die nächste Folie angezeigt wird. Hilfe gibt es mit h oder ?.

Interface fuer den Praesentierenden.

Ein paar Schwierigkeiten habe ich aber noch:

Es ist knifflig, offline zu präsentieren, weil Remark.js aus dem Netz nachgeladen wird. Gerade auf Konferenzen ist stabiles Netz ja keine Selbstverständlichkeit. Man kann das natürlich lösen, indem man das Repo klont und die lokale Version lädt, aber dann ist es halt nicht mehr so portabel.

Außerdem habe ich ein bisschen die Allmacht von Orgmode vermisst, Code einzubinden und den Output direkt generieren zu lassen. Aber das ist ein Luxusproblem, wer Orgmode will muss eben Orgmode benutzen.

Insgesamt war ich mit Remark.js sehr zufrieden und würde es für Praesentationen wieder verwenden. Andererseits kenne ich mich gut genug um zu wissen, dass ich selten zweimal das gleiche Setup für meine Grafiken und Folien verwende. Es gibt einfach zu viele spannende Tools.

Start videos from the commandline

tldr: Just use mpv VIDEOURL to start watching.

Lets say you want to watch a video, how would you do it from the commandline?

local video file

If the file you want to see is already on your computer, then this can’t be too hard. Just go with your favorite video player (vlc in this example):

vlc ~/Videos/Nyan_Cat.mkv

videos from the internet

So what if the video isn’t already downloaded? Lets say we want to look at this YouTube Video: Nyan Cat.

No problem, we can download it right away with youtube-dl.

youtube-dl -o "~/Videos/Nyan_Cat.mkv" "" && vlc ~/Videos/Nyan_Cat.mkv

The reason why we don’t use curl/wget here is that we don’t have the url of the video file itself (and if we did, we could just continue to feed it into vlc directly). The video is embedded into a website and there is no trivial way to get its url. If we ask youtube-dl nicely (with --get-url) he will tell us, but at this point we have used youtube-dl anyway, so what’s the point?

The video that google uses has a really long url. Something tells me that is isn't meant for direct use.

Youtube-dl is quite amazing. It supports a great number of video sites and file formats. It also supports playlists, logins for private videos and downloading the audio only. Being controlled from the commandline makes it scriptable, but the best thing about it is that it has been under continuous development for many years now, catching up with all the api-changes from all the supported websites.

streaming, not downloading

Downloading works, but what if we want to start to watch straight away, waiting for the download to finish is unnecessary (especially if the video is a big one, like the 10 hour version of the video above).

Youtube-dl can write the file to stdout (--output -) and tell the video-player to play from stdin (vlc - in this case).

youtube-dl --output - '' | vlc -

Unfortunatelly there is no easy way to jump positions in the videos itself.

mpv to the rescue

vlc has a big fanbase, but for quite some time I preferred to use mplayer, because it has the most minimal gui imaginable (a window whith the video in it, nothing more) and is easier to use from the keyboard. Then I found mpv, a pimped version of mplayer - and everything is perfect. Much like feh for images, it does all I want and goes out of the way otherwise. I just love it.

Mpv makes all the problems from above trivial.

mpv ''

It uses youtube-dl, can show subtitles in different languages, jumps between positions and has an easy syntax. It is free software (like all the other programs I talked about in this post) and its development continues steadily.

Why not use a browser, like a normal person?

Using a native video-player comes with some cool bonuses. You can configure and finetune it, use media-keys on your keyboard, it plays nicely with the rest of your setup and your window-manager etc.

The only downside I see is that the resolution is not adjusted on the fly if the connection is not good enough to stream in full resolution. That’s why I still use a browser for twitch-streams.

Wieder da

Ich bin wieder da. Der Blog hat lange geruht und jetzt habe ich Lust, wieder zu schreiben.

Der Blog war auch und vor allem deshalb fort, weil wir unseren geliebten Server Gipfelsturm verloren haben. Da sind auch Sandras und mein Ghostblog drauf gewesen und ich wollte erst wieder schreiben, wenn ich beide Blogs wieder so weit habe, dass sie benutzt werden können (Es wäre unfair gewesen, selbst weiter zu bloggen bevor ihr Blog wieder läuft).

Dazu musste ich aber aus schlechten Backups die meisten Artikel wieder herzaubern (viel habe ich von und dem google-cache gezogen, Viele Bilder hatten wir noch undeinige konnten wir wieder zuordnen) und eine ganz neue Bloginfrastrucktur aufbauen. Das war eine lange Reise, aber jetzt bin ich ganz zufrieden. Vielleicht schreibe ich später mehr zu den Details. Die Hauptsache ist, dass ich jetzt große Kontrolle über den Blog habe (wenn was nicht geht kann ichs wahrscheinlich reparieren) und es trotzdem einfach ist, zu bloggen.

Es läuft wieder was in der Schauderbasis.

PS: Der RSS-Feed funktioniert auch:

Bilder aussortieren

Es gab eine Feier, jemand hat Fotos gemacht und rumgeschickt. Oder vielleicht habe ich selbst hundert mal den selbe Essen fotografiert. Jedenfalls sind da ein Haufen Bilder und ich will nur die guten behalten.

Dieses Aussortieren macht wenig Spaß und soll deshalb schnell gehen. Wenn man es gut macht hat man danach nur noch 5-10% der Bilder. Der Lohn: Man kann den wenigen guten Bildern viel mehr Beachtung schenken.

Erstaunlicherweise bietet der einfachste (und schnellste) mir bekannte Imageviewer dafür das beste Interface bereit: feh


Wir bilden den Sortierprozess auf das Filesystem ab. Wärend dem sortieren haben wir 3 Haufen von Fotos:

  • die noch unsortierten Fotos (~/Pictures/feier)
  • die guten Fotos (~/Pictures/feier/good)
  • die schlechten Fotos (~/Pictures/feier/bad)
mkdir ~/Pictures/feier
cd ~/Pictures/feier
mv ~/Downloads/ .
mkdir good bad

Wir werden die schlechten Bilder erst wegwerfen wenn wir mit dem sortieren ganz fertig sind (so vermeiden wir unglückliche Versehen).


Hier steckt die ganze Magie drin:

feh --auto-zoom --scale-down --action1 "mv %f good/" --action2 "mv %f bad/" .

Übersetzt bedeutet das: “Zeige alle Bilder im aktuellen Verzeichnis (.) nacheinander (als Slideshow) an, so gezoomt das man immer das ganze Foto sehen kann. Wenn ich die Taste 1 drücke, verschiebe das aktuelle Bild ins Verzeichnis good, wenn ich 2 rücke verschiebe es nach bad.”

Nette Annehmlichkeiten sind dabei:

  • Wenn man gerade nicht sicher ist, kann man mit den Pfeiltasten einfach zum nächsten Bild springen und die Entscheidung verschieben.
  • Ein Bild, bei dem man eine Wahl getroffen hat wird nicht mehr angezeigt. Sind keine Bilder mehr übrig, wird das Programm beendet
  • Will man unterbrechen, hilft die Taste q, was man bisher sortiert hat bleibt auch sortiert.
  • feh ist zwar schlank und klein, aber zum zoomen und Bilder richtig herum drehen reicht es noch.
  • Wie die meisten Unix-workflows ist auch dieser sehr flexibel. Ideen, die man damit schnell verwirklichen könnte:
    • einen dritten Ordner (bin_nicht_sicher)
    • mehrere Sortierdurchläufe.
    • Photos taggen
  • Das Interface ist so einfach wie nur irgend möglich: Man sieht nur das Bild und hat genau 2 Tasten für 2 Möglichkeiten.
  • Es geht schnell. Wenn man mal ein bisschen drin ist hat man schnell einige Hundert Bilder sortiert.


Zum Schluss (und wenn man sich sicher ist) wirft man die schlechten Fotos weg und behält die guten:

rm bad/* -f
mv good/* .
rmdir good bad

Bilder von hier und hier.

Hörbücher auf SailfishOS

Unterwegs höre ich sehr gerne Hörbücher - besonders in vollen öffentlichen Verkehrsmitteln, wo man ohnehin nicht viel anderes machen kann als Musik/Podcasts/Hörbüchern zu lauschen oder seinen Feedreeder durchzuarbeiten. Auf SailfishOS gibt es keinen nativen Client, aber die Standardapp “Medien” funktioniert wunderbar. Wie ist also der Workflow?

Umweg über den Rechner

Bestimmt geht das auch direkt auf dem Telefon, aber ehrlich gesagt habe ichs noch nicht ausprobiert. Außerdem geht es dank ssh/scp sehr flott auch von der Kommandozeile.

Hörbuch finden

Ich könnte es gar nicht besser zusammenschreiben als es im Wiki Audiobooks Subreddit steht.

Für diesen Blogpost wählen wir The Wrong Box auf Librivox.

Hörbuch herunterladen

Hörbücher sind meistens mehrere Audiodateien, zum Beispiel eine für jedes Kapitel. Meistens bekommt man diese schön kompakt in einer zip-Datei, die lädt man herunter und entpackt sie. Manchmal sieht man aber auch nur eine Website voll mit Links zu dein einzelenen Dateien (wsl für Leute, die alles direkt im Browser konsumieren wollen). In diesem Fall hilft dieser Befehl.

lynx -dump | grep 64kb.mp3$ | awk '{print $2}' | xargs -n1 wget

In Worten: Schau dir alle Links der genannten Webseite an, nimm die die auf “64kb.mp3” enden und lade sie einzeln herunter. (Es könnte also schlau sein, das in einem leeren Ordner auszuführen.)

Bei librivox ist das aber eigentlich nicht nötig, man kann alles direkt herunterladen (sogar per Torrent, wohooo!).

Es macht übrigends alles (erheblich) einfacher, wenn die Dateien so benannt sind, dass die alphabetische und die logische Ordnung übereinstimmen.

ssh aufs Telefon

Testweise kann man sich schonmal aufs Telefon verbinden und den Ordner anlegen, wo die Dateien später liegen sollen. Ssh muss in den Einstellungen des Telefons unter “Entwicklermodus” aktiviert werden, dort findet man auch Passwort und IP-Adresse.

ssh nemo@
mkdir Music/wrong_box

Hörbuch aufs Telefon

Da wir ja jetzt schon alles vorbereitet haben, reicht ein kurzes Befehl:

scp *.mp3 nemo@

Das wars schon.


Wir können alles in der Medien-App anhören. Nach dem aktuellen Kapitel wird automatisch zum nächsten gesprungen.

Firefox History in der Kommandozeile

Firefox ist so bekannt geworden, weil man alles mögliche an ihm einstellen kann. Warum ist es dann so schwer, ihn von der Kommandozeile zu steuern?

Ich würde wirklich gerne ein paar Skripte schreiben, welche die aktuell geöffneten Seiten von Firefox auswerten (zum Beispiel um die aktuelle Seite in eine Liste einzutragen, einen Eintrag in meinen Passwortmanager zu machen oder ähnliches).

Leider ist das scheinbar sehr schwierig, denn dieser riesige Kloß von einem Browser kann scheinbar nicht nach außen kommunizieren (Ich arbeite gerade mit Mozilla Firefox 46). Folgenden Notbehelf habe ich gefunden:

Die neuste Seite ist aktuell

Ich nehme vereinfachend an, dass ich mich nur für die Seite interessiere, die als letztes aufgerufen wurde. Dann muss man nur in der Firefox History nachschauen, welche Seite die letzte war und sie ausgeben.

Warum ist das leichter? Firefox speichert seine History, Bookmarks und noch ein paar andere Dinge in einer sqlite Datenbank: $HOME/.mozilla/iwfal82r.default/places.sqlite. (Der Teil vor “default” sieht jedes mal ein bisschen anders aus, aber das findet man schon. Im Folgenden benutze ich ein Regexsternchen, so dass die Befehle bei jedem funktionieren sollten.) Da ich ja neulich schon etwas Erfahrung mit Datenbanken gesammelt habe, wage ich mich da mal rein.

So sieht eine Datenbank von innen aus

In der Datenbank

sqlite3 $HOME/.mozilla/firefox/*.default/places.sqlite

SQLite version 3.13.0 2016-05-18 10:57:30
Enter ".help" for usage hints.

Wir sind jetzt in der Datenbank (raus kommen wir mit Ctrl-D). Erstmal schauen wir was es hier für tables gibt:

sqlite> .tables
moz_anno_attributes  moz_favicons         moz_items_annos    
moz_annos            moz_historyvisits    moz_keywords       
moz_bookmarks        moz_hosts            moz_places         
moz_bookmarks_roots  moz_inputhistory  

Die Tabelle die wir brauchen ist die moz_places. Hier ist unsere gesamte Browserhistory drin (habe ich hier rausgefunden). Wir schauen uns die Spalten der Tabelle an:

sqlite> pragma table_info(moz_places);

Für uns sind url und last_visit_date wichtig (denn wir wollen ja die URL, die zuletzt aufgerufen wurde). Wir tasten uns mal langsam ran:

Alle URLS, die ich je aufgerufen habe

Alphabetisch sortiert.

sqlite> select url from moz_places;
Alle URLS, die ich je aufgerufen habe

Nach Datum sortiert.

sqlite> select url from moz_places order by last_visit_date;
Wann die letzte URL aufgerufen wurde
sqlite> select max(last_visit_date) from moz_places;

Hmm, eigentlich sollte hier ein Datum stehen. Laut table_info ist dieses als Integer kodiert. Das Internet hilft weiter.

sqlite> select max(last_visit_date) as raw_visit_date,datetime(last_visit_date/1000000,'unixepoch') from moz_places;
1464029535578940|2016-05-23 18:52:15

Sieht doch gleich viel besser aus, die ISO 8601 lebe hoch. Aber irgendwie ist das um zwei Stunden falsch? Ach ja, Zeitzonen.

sqlite> select max(last_visit_date) as raw_visit_date,datetime(last_visit_date/1000000,'unixepoch','localtime') from moz_places;
1464029535578940|2016-05-23 20:52:15

Geht doch.

Eigentlich brauchen wir diese schick formatierten Strings aber gar nicht, wir wollen ja nur nach Zeit sortieren. Deswegen orientieren wir uns am ersten Versuch.

Die URL, die ich als letztes besucht habe

Genau genommen fragen wir eher nach allen urls, die zu dem Zeitpunkt aufgerufen wurden, an dem wir zuletzt eine url aufgerufen haben.

sqlite> select url from moz_places where last_visit_date=(select max(last_visit_date) from moz_places);

Hurra, es funktioniert!

Für Skripte

Damit wir uns nicht ständig in sqlite einloggen müssen, kann man das auch von außen tun.

$ sqlite3 $HOME/.mozilla/firefox/*.default/places.sqlite "select url from moz_places where last_visit_date=(select max(last_visit_date) from moz_places)"

So, da ist sie. Die letzte besuchte Url. Das funktioniert, auch sofort nach dem Aufrufen der Url oder wenn der Firefox aus ist.

Bilder von hier und hier

An der Weboberfläche kratzen

Ich mag Programmieren, aber mit Netzwerk, Browser, Javascript und Webseiten kenne ich mich nicht gut aus. Wenn ich dann einen Fuß ins kalte Wasser halte, ist es schon spannend (für mich). Hier mein jüngstes Erlebnis:

Bilder in Ghost

Meine Freundin hat, wie ich, einen Ghostblog. Anders als ich schreibt sie dort regelmäßig und oft, mit vielen Bildern über Abenteuer in der Küche. Neulich konnte sie plötzlich keine Bilder mehr hochladen.

Something went wrong


Fehler eingrenzen:

  • Die Fehlermeldung ist wenig hilfreich.
  • So wenig hilfreich, das man nicht mal sinvoll danach googlen kann.
  • Till und ich haben Blogs auf dem gleichen Server, können aber Bilder hochladen.
  • Auf Sandras Blog kann man keine Bilder hochladen, egal ob von ihrem oder meinem Account.
  • Firefox, Safari oder Chrome - kein Unterschied
  • Verschiedene Dateitypen oder kleinere Bilder ändern nichts an der Fehlermeldung.
  • Die offizielle Ghost Dokumentation ist lausig. Immerhin gibt es einen sehr kurzen Abschnitt über Image upload issues. Nichts Hilfreiches. Das angesprochene Cloudflair benutzen wir nicht.

Ideen, woran es liegen könnte:

  • Sandras Blog ist von allen mir bekannten Ghostblogs der mit dem meisten Content, insbesondere der mit den meisten Bildern. Vielleicht gibt es da irgendwo ein Quota, dass erreicht ist?
  • Sandras Blog ist der mit dem Umlaut in der url (“Gewürzrevolver”). So bitter es ist, das hat bis jetzt einige Probleme gemacht. Liegt es vielleicht daran?

Mehr kann ich jetzt alleine nicht rausfinden, ab hier brauche ich Ansprechpartner.

Mal fragen

Ich befrage Till (der Herr über den Server) und den Ghost Slack Channel help, wo ein Kevin mir freundlich zur Seite steht.

“Gibt es auf unserem Server irgendeine Begrenzung des Speicherplatzes?”

Nein, Till weiß von keinem Quota und ein mysteriöses künstliches Quota bei Ghost scheint auch nicht zu existieren.

“Wo soll ich nach Fehlern suchen?”

Schau mal in den Web Inspektor.

Wie immer, wenn ich mit dem Support spreche google ich nebenbei, was die Antworten bedeuten. Den Webinspektor ist das Tor in die Javascript-Hölle. Here be dragons. Aber here be also a log of what happens in your browser when you interact with a website.

Webinspektor öffnen

Ich öffne also den Webinspector und versuche, ein Bild auf den Blog hochzuladen. Tatsächlich finde ich nach ein bisschen herumsuchen eine Fehlermeldung.

So sieht ein Fehler auf einer Webseite aus

Die Fehlermeldung

Die Fehler kommt in Form eines JSON Objektes, die entscheidende Zeile ist:

EACCES, open '/var/www/ghost/revolver/content/images/2016/04/Organigram-svg.png'

Sehr hilfreich sieht das erstmal nicht aus. Aber wenn man mal schaut, was EACCES überhaupt heißt, bekommt man heraus dass es sich wohl um einen permission error in dem angegebenen Ordner handelt. (Diese Schlussfolgerung zieht eigentlich der hilfreiche Kevin aus dem Ghost Chat, ich plappere das nur fleißig nach).

Also nochmal dem Till geschrieben, mit der Bitte die Rechte in dem Ordner zu checken und - tatsächlich, das war es. Der Upload funktioniert jetzt wieder und Sandra kann ihren neuen Blogpost mit Bildern ausstatten (Spargel, Huäääääääh!).

Abschliessende Gedanken:

  • Fehlermeldungen sollten immer aus zwei (!) Teilen bestehen: Eine technische (für den Entwickler) und eine menschenlesbare für den User.
  • “Something went wrong.” ist keine gute Fehlermeldung. Da ist keine nützliche Information drin. Dass es nicht geklappt hat sieht der User auch so.
  • Der Fehlersuche war nicht qualvoll und nervig, denn alle waren geduldig und haben sich professionell verhalten. So macht das Spaß, danke an Till und Kevin.
  • Wie so oft bei Computerproblemen gilt: Wenn du noch nicht wirklich selbst versucht hast, das Problem zu lößen, dann ist es nicht angebracht anderer Leute wertvolle Zeit dafür in Anspruch zu nehmen.
  • Keiner weiß, wie das mit den Rechten passiert ist (mit den Rechten weiß man das nie so recht). Till war es nicht und auch sonst keiner. Auf dem Server spukt es.

(Titelbild von hier, Pixabay ist eine tolle Quelle für freie Bilder)

ping Datenbank

Pingplot war als Vorbereitung für ein etwas größeres Projekt gedacht, für das ich eine Datenbank einsetzen möchte. Also sollte ich erst mal lernen, wie man mit Datenbanken arbeitet.

Tatsächlich habe ich am Ende etwas mehr gelernt als gedacht und ein bisschen älteres Wissen aufgefrischt.


Daten, die laufend generiert werden sollen gesammelt und visualisiert werden. Zum Üben nehme ich den Ping zu einer Website. Der eignet sich weil es ein beständiger Strom von nicht zufälligen, aber auch nicht voraussagbaren Datenpunkten ist.

Es wird 4 zentrale Programme in unserem Setup geben:

  1. Die Steuerzentrale
  2. Die Datenquelle
  3. Die Datenbank
  4. Die Datenaufbereitung

Mir ist wichtig, das am Ende alles auf Knopfdruck funktioniert. Der Befehl, einen Plot zu erstellen ist nur ein Befehl sein und es ist keine weiteres Eingreifen meinerseits notwendig, erst recht kein hin- und herkopieren von Daten. Dieses Prinzip hat sich bei meiner Bachelorarbeit extrem gut bewährt - irgendwann drückt man nur noch auf den Knopf und hat etwas später 50 Graphen und Diagramme, die man analysieren kann.

Die Steuerzentrale - Gnu Make

Als ich zum ersten mal ein Makefile benutzt habe war ich wirklich begeistert von der Idee: Wenn man in einem Directory ohnehin immer wieder das gleiche macht, kann man das auch automatisieren. Und tatsächlich habe ich beim Programmieren immer ein Terminal laufen, in das ich permanent die selben Sachen eingebe:

  • compilieren
  • testcase mit den einen Parametern
  • testcase mit den anderen Parametern
  • Zwischenergebnisse aufräumen, um sie neu zu erstellen
  • git
  • temporäre Dateien aufräumen– Ups, da war was Wichtiges dabei :(

Stattdessen schreibt man ein Makefile, indem das alles vorformuliert ist und gibt dann nur noch Befehle wie:

make compile


make plot

Dank tab-completion geht das schneller und man vertippt sich nicht ausversehen. Ich finde es ja allgemein ganz gut, wenn ein Programm möglichst viel über sich selbst weiß und sich selbst quasi selbst organisiert (Solange klar ist, das ich noch der Chef bin - die Apple-Lösung mit diesen Mediathekformat, in das man nicht reinschauen kann).

Die Syntax von Makefiles ist erstmal sehr einfach. Man schreibt das target auf, einen Doppelpunkt dahinter - und danach eingerückt die Befehle, die ausgeführt werden sollen wenn das target aufgerufen wird.

	gnuplot graph.plot
	feh --reload 1 ping.png &

Mehr muss man erstmal nicht wissen.

Die Datenquelle - ein kurzes Shell-Skript

Um den Ping zu einer Website herauszufinden, reicht ein einfaches

$ ping
PING ( 56(84) bytes of data.
64 bytes from ( icmp_seq=1 ttl=61 time=29.9 ms
64 bytes from ( icmp_seq=2 ttl=61 time=37.2 ms
64 bytes from ( icmp_seq=3 ttl=61 time=28.2 ms

Uns interessieren die Werte zwischen time= und ms. Um die herauszubekommen hat für mich folgendes funktioniert:

  • Nur noch ein Wert stat unendlich viele:
$ ping -c 1
PING ( 56(84) bytes of data.
64 bytes from ( icmp_seq=1 ttl=61 time=29.8 ms

--- ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 29.865/29.865/29.865/0.000 ms
  • Nur noch die zweite Zeile, wo die wichtige Information drin steht:
$ ping -c 1 | sed -n 2p
64 bytes from ( icmp_seq=1 ttl=61 time=28.9 ms
  • Davon die achte Spalte
$ ping -c 1 | sed -n 2p | awk '{print $8}'
  • Und dann alles ab dem sechsten Buchstaben:
$ ping -c 1 | sed -n 2p | awk '{print $8}' | cut -c 6-


Mehr Werte

Damit wir nachher eine ordentliche Datenbasis haben, wollen wir viele Werte hintereinander generieren. Eigentlich macht das ping ja schon selbst, aber wir bauen uns hier eine eigene Schleife, so das wir Daten sofort einlesen können.

Optionale Argumente (wusste ich vorher auch nicht) gehen so:

# number of datapoints to generate: take first argument or 10 as default
# sleeptime: take second argument or 1 as default

Am Ende (mit ein bisschen Zeug aus dem nächsten Abschnitt) sieht das ganze so aus.

dbdo="mysql -u root -s $database_name -e"

# number of datapoints to generate: take first argument or 10 as
# default
# sleeptime; take second argument or 1 as default

for i in `seq $n`
    pingtime=$(ping -c 1 $url | sed -n 2p | awk '{print $8}' | cut -c 6-)
    $dbdo "insert into $table_name (Zeitpunkt, URL, Ping) values (NOW(), '$url', $pingtime);"
    sleep $t

Gar nicht mal so hässlich, von bash bin ich schlimmeres gewohnt.

Die Datenbank - MariaDB

Es gibt verschiedene Datenbanken für verschiedene Zwecke. Ich habe mich für MariaDB entschieden, hauptsächlich wegen dem Artikel im Arch-Wiki zum aufsetzen und dem Tutorial auf der Website von MariaDB, das mit genau so viel Information gegeben hat wie ich als blutiger Anfänger brauchte.

Beim Lernen hat mir wirklich sehr geholfen, das ich mit einem Makefile arbeite. So konnte ich einfach Zeilen wie diese eintragen:

database_name = pingDB
table_name = pingtimes
general_do = mysql -u root -e
dbdo = mysql -u root $(database_name) -e

	$(general_do) "create database if not exists $(database_name)"

	$(dbdo) "create table if not exists $(table_name) (Zeitpunkt TIMESTAMP, URL VARCHAR(30), Ping FLOAT UNSIGNED)";

	$(dbdo) "select * from $(table_name)"

Was man sich aufgeschrieben hat, kann man schon mal nicht wieder vergessen.

Tatsächlich ist die SQL-Syntax gar nicht so schlimm, solange man relativ einfache Anfragen stellt. Das war bei mir zum Glück der Fall und das bisschen was ich brauchte konnte ich dann auch relativ flott auswendig.

Am Ende hatte ich eine Datenbank mit einer Tabelle, die (mit dem Skript von oben) so aussah:

$ make show_table
mysql -u root pingDB -e "select * from pingtimes"
| Zeitpunkt           | URL                  | Ping |
| 2016-01-25 16:56:40 | | 29.8 |
| 2016-01-25 16:56:41 | | 30.1 |
| 2016-01-25 16:56:42 | | 29.0 |
| 2016-01-25 16:56:43 | | 32.2 |
| 2016-01-25 16:56:44 | | 28.8 |
| 2016-01-25 16:56:45 | | 29.6 |
| 2016-01-25 16:56:47 | | 30.1 |
| 2016-01-25 16:56:48 | | 29.8 |
| 2016-01-25 16:56:49 | | 28.6 |
| 2016-01-25 16:56:50 | | 29.2 |

Die Datenaufbereitung - Gnuplot

Eigentlich kam für mich kein anderes Tool in Frage, Gnuplot passt einfach zu gut. Ich habe schonmal was zu verschiedenen Plottern aufgeschrieben und hier war die Entscheidung klar.

Das schwierigste war die Frage, wie man die Daten aus der Datenbank in Gnuplot hinein bekommt. Gut das Gnuplot alles kann:

# Output from mysql is normaly formated as ascii-boxes,
# with the flag -B it is just tab-separated.
set datafile separator "\t"

plot '< mysql -u root -B pingDB -e "SELECT Zeitpunkt, Ping FROM pingtimes;"' using 1:2

Im Prinzip wird hier die SQL-Abfrage direkt von Gnuplot ausgeführt. Kein Problem.

Es ist gar nicht so klar, wie das mit der Zeit eingelesen werden soll. SQL liefert das Datum und die Uhrzeit schön nach ISO 8601: 2016-01-25 16:56:40

Gnuplot kommt von klugen Leuten, die wissen dass es auf der Welt sehr viele sehr schlimme Formate gibt, in der Leute die Zeit angeben. Deswegen gibt man einfach an, in welchem Format das Datum eingelesen und ausgegeben werden soll:

# time format used for reading input
set xdata time
set timefmt "%Y-%m-%d %H:%M:%S"

# time format used for printing on axis
set format x "%H:%M:%S"

Einfacher geht es nicht. Eine Aufschlüsselung der Variablen (falls nötig) gibt es im hervorragenden Handbuch. Es scheint aber das gleiche Format zu sein wie bei dem Programm date, also reicht wsl auch die entsprechende manpage.

Jetzt ist nur noch die Frage, wie der Graph aussehen soll. Ich habe mich entschieden, die Werte interpolieren zu lassen, damit der Graph schön glatt ist. Das Stichwort hier heißt smooth, man sollte es in Gedanken aber immer smooooooooth aussprechen.

Und das wars

Eigentlich ziemlich einfach, hier funktionieren ein paar mächtige Werkzeuge sehr gut zusammen. Es hat Spaß gemacht und ich habe jede Menge über wichtige Standardwerkzeuge gelernt.

Der Code liegt hier zur freien Verfügung (mit freier Lizenz natürlich).

Gedanken zum Projekt

  • Für so ein kleines Projekt würde man normalerweise keine riesige Datenbank anschmeißen. Matthias meinte, dass die meisten Leute die nicht wissen welche Datenbank sie benutzen sollen mit sqlight wahrscheinlich am besten bedient sind.
  • Bash ist eine furchtbare Programmiersprache. Aber leider sehr nützlich.
  • Ich weiß nicht wer sich ausgedacht hat, dass Variablen in Make und bash fast, aber nur fast gleich aussehen und funktionieren. Was soll das?
  • Ich war erst ein bisschen genervt, dass man in SQL immer brüllen muss: “CREATE TABLE IF NOT EXISTS pingtimes”. Dann habe ich gemerkt, dass das gar nicht notwendig ist - die Sprache ist case insensitive: “create table if not exists pingtimes”. Manche Sachen (zum Beispiel die Datentypen) habe ich trotzdem in Caps gelassen, das sah irgendwie richtiger aus.
  • Wenn ich cooler wäre würde ich einen Cronjob einrichten, der das Skript regelmäßig anstößt (~alle 5 Minuten?). Das könnten interessante Graphen sein.
  • Mit wenig Aufwand könnte man das Skript umbauen, so dass andere Werte aufgezeichnet und verarbeitet werden. Zum Beispiel:
    • die Batterie (Wie viel Prozent habe ich im Schnitt noch übrig?)
    • RAM und CPU
    • Wie viele Wlans verfügbar sind (verschlüsselt vs. unverschlüsselt?)
  • Nachdem ich viel im Internet nach Gnuplotschnipseln gestöbert habe entdeckte ich gegen Ende des Projektes, dass Gnuplot ein hervorragendes und ausführliches Handbuch mit vielen Beispielen und Bildern hat. Nächstes mal weiß ich das vorher.