Heutzutage fallen an allen möglichen Stellen irgendwelche Daten im JSON-Format an. Und das ist gut so. Trägt nicht so auf wie XML, ist dafür aber flexibler — und vor allem besser definiert — als irgendwelche selbstausgedachten CSV- oder INI-Formate.
Man ist schlecht beraten, zu versuchen dermassen komplexe Formate mit Shell-Tools wie awk, sed oder grep zu Leibe zu rücken. Reguläre Ausdrücke sind eine tolle Sache, aber in dieser Situation fallen die einem früher oder später auf den Fuß. Bislang habe ich meist eine Sprache mit P bemüht: Python, Perl oder zur Not PHP. Die können JSON importieren, danach arbeitet man auf den gewohnten Datenstrukturen. Also auf Arrays und Objekten, und die kann man schön sauber nach dem durchkämmen was man sucht.
Jetzt hat aber ein neues Tool seinen Weg in meinen Werkzeugkasten gefunden: jq.
Letzte Tage hatte ich die Anforderung, aus einem JSON-Array (das noch mehr Daten enthielt als dieses Beispiel) Daten zu extrahieren. Ich brauchte die Anzahl aller Objekte die eine bestimmte Severity aufwiesen, nicht acknowledged waren und deren Timestamp mehr als 20 Minuten in der Vergangenheit liegt. Den kompletten Datensatz konnte ich von einer URL abrufen, das sah etwa so aus:
1 2 |
[{"acknowledged":0,"severity":1,"timestamp":1418042121",description":"Irgendwas ganz dolle wichtiges"}, {"acknowledged":0,"severity":1,"timestamp":1418041774,"description":"Irgendwas anderes wichtiges"}] |
Hübsch hässlich. Aber hier schlägt die erste große Stunde von jq: mit einem einfachen Pipe an jq "."
ist aus dem Zeichenwust ein ansehnliches Stück Ausgabe geworden, samt gehighlighteter Syntax:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[ { "acknowledged": 0, "severity": 1, "timestamp": 1418042121, "description": "Irgendwas ganz dolle wichtiges" }, { "acknowledged": 0, "severity": 1, "timestamp": 1418041774, "description": "Irgendwas anderes wichtiges" } ] |
Die weitere Evolution meines Einzeilers sah in etwa so aus:
Erstmal aus dem Array einzelne Objekte machen:
1 |
wget -qO - "$URL" | jq ".[]" |
Diese Objekte dann filtern, so dass nur welche mit einer Severity >= 4 erhalten bleiben. Das kann man sich so vorstellen als ob man die Ausgabe des einen Kommandos an ein anderes weitergibt, und genau wie in der Shell schreibt man das als Pipe:
1 |
wget -qO - "$URL" | jq ".[] | select(.severity >= 4)" |
Weitere Filter, den Timestamp von vor 20 Minuten liefert date:
1 |
wget -qO - "$URL" | jq ".[] | select(.severity >= 4) | select(.timestamp <= $(date -d "20 minutes ago" +%s)) | select(.acknowledged == 0)" |
Wir haben jetzt eine Ansammlung von Objekten, daraus machen wir erstmal wieder ein Array:
1 |
wget -qO - "$URL" | jq "[.[] | select(.severity >= 4) | select(.timestamp <= $(date -d "20 minutes ago" +%s)) | select(.acknowledged == 0)]" |
Und dann bestimmen wir die Länge des erhaltenen Arrays:
1 |
wget -qO - "$URL" | jq "[.[] | select(.severity >= 4) | select(.timestamp <= $(date -d "20 minutes ago" +%s)) | select(.acknowledged == 0)] | length" |
Sehr schön. Wir arbeiten jetzt mit einem richtigen Parser, nicht mit irgendwelchen Schätzungen die zufällig zu den gelieferten Daten passen. Wenn man etwa weiß wie es geht dann lässt sich der Filter recht einfach schreiben — und später wieder lesen. Die Ausführung geht auch fix, und wenn ich mir die Doku ansehe ist das Tool noch viel mächtiger als ich das bislang überblicke.
Seeeehr geil! Und genau zum richtigen Zeitpunkt, das wird nachher ausprobiert. :)