Première rédaction de cet article le 9 août 2017
Aujourd'hui, énormément de données sont distribuées dans le format JSON. La simplicité de ce format, normalisé dans le RFC 8259, et bien sûr son utilisation dans JavaScript expliquent ce succès. Pour le transport de données (celui du texte est une autre histoire), JSON a largement remplacé XML. Il existe donc des bibliothèques pour traiter le JSON dans tous les langages de programmation. Mais si on veut une solution plus simple, permettant de traiter du JSON depuis la ligne de commande ? C'est là qu'intervient jq, qui permet à la fois de faire des opérations simples en ligne de commande sur des données JSON, et de programmer des opérations complexes, si nécessaires.
jq est parfois présenté comme « l'équivalent de sed pour JSON ». Ce n'est pas faux, puisqu'il permet en effet des opérations simples depuis la ligne de commande. Mais c'est assez court, comme description : jq est bien plus que cela, il inclut même un langage de programmation complet.
Commençons par quelques exemples simples. On va utiliser le JSON produit en utilisant l'outil madonctl pour récupérer des messages (les pouètes) envoyées sur Mastodon. Si je fais :
% madonctl --output json timeline :afnic [{"id":3221183,"uri":"tag:mastodon.social,2017-07-20:objectId=13220936:objectType=Status","url":"https://mastodon.soc ial/users/afnic/updates/3830115","account":{"id":37388,"username":"afnic","acct":"afnic@mastodon.social","display_nam e":"Afnic","note":"\u003cp\u003eLe registre des noms de domaine en .fr\u003cbr\u003eRdv sur \u003ca href=\"http://www .afnic.fr/\" rel=\"nofollow noopener\" target=\"_blank\"\u003e ...
J'ai récupéré les pouètes qui parlent de l'AFNIC, sous forme d'une seule ligne de JSON, peu lisible. Alors qu'avec jq :
% madonctl --output json timeline :afnic | jq . [ { "id": 3221183, "uri": "tag:mastodon.social,2017-07-20:objectId=13220936:objectType=Status", "url": "https://mastodon.social/users/afnic/updates/3830115", "account": { "id": 37388, "username": "afnic", "acct": "afnic@mastodon.social", "display_name": "Afnic", "note": "<p>Le registre des noms de domaine en .fr<br>Rdv sur <a href=\"http://www.afnic.fr/\" rel=\"nofollow n oopener\" target=\"_blank\"><span class=\"invisible\">http://www.</span><span class=\"\">afnic.fr/</span><span class= \"invisible\"></span></a></p>", "url": "https://mastodon.social/@afnic",
J'ai au contraire du beau JSON bien lisible. C'est la première utilisation de jq pour beaucoup d'utilisateurs, dans un monde où les services REST fabriquent en général du JSON très compact (peut-être pour gagner quelques octets), jq peut servir de pretty-printer.
Le point après la commande
jq
est un filtre. jq
fonctionne par enchainement de filtres et, par défaut, produit des
données JSON joliment formatées. (Si, par contre, on ne veut pas
de pretty-printing, on lance jq avec l'option
--compact-output
.)
Prenons un exemple où on veut sélectionner une seule information dans un fichier JSON. Mettons qu'on utilise RDAP (RFC 9082) pour trouver de l'information sur une adresse IP. RDAP produit du JSON :
% curl -s http://rdap.db.ripe.net/ip/2001:1470:8000:53::44 { "handle" : "2001:1470:8000::/48", "startAddress" : "2001:1470:8000::/128", "endAddress" : "2001:1470:8000:ffff:ffff:ffff:ffff:ffff/128", "ipVersion" : "v6", "name" : "ARNES-IPv6-NET", "type" : "ASSIGNED", "country" : "SI", ...
Si on ne veut que le pays du titulaire d'un préfixe d'adresses
IP, c'est simple avec jq, avec le filtre
.country
qui veut dire « le champ
country
de l'objet JSON
(.
étant le texte JSON de plus haut niveau,
ici un objet) » :
% curl -s http://rdap.db.ripe.net/ip/131.111.150.25 | jq .country "GB" % curl -s http://rdap.db.ripe.net/ip/192.134.1.1 | jq .country "FR"
On n'aurait pas pu utiliser les outils ligne de commande classiques comme grep ou sed car le format JSON n'est pas orienté ligne (et a trop de subtilités pour eux).
Et si on veut interroger son nœud Bitcoin pour connaitre à quel bloc il en est :
% bitcoin-cli getinfo | jq .blocks 478280
Cet exemple particulier n'est pas super-intéressant, on a déjà
bitcoin-cli getblockcount
mais cela montre
bien qu'on peut utiliser jq très simplement, pour des tâches
d'administration système.
Au passage, pour trouver le filtre à utiliser, il faut connaitre la structure du fichier JSON. On peut lire la documentation (dans l'exemple RDAP ci-dessus, c'est le RFC 9083) mais c'est parfois un peu compliqué alors que JSON, étant un format texte, fournit une solution plus simple : afficher le début du fichier JSON et repérer les choses qui nous intéressent. (Un format binaire comme CBOR, RFC 8949, ne permet pas cela.) Cette méthode peut sembler du bricolage, mais l'expérience prouve que les services REST n'ont pas toujours de documentation ou, lorsqu'ils en ont, elle est souvent fausse. Donc, savoir explorer du JSON est utile.
Notez que jq produit du JSON, donc les chaînes de caractères
sont entre guillemets. Si on veut juste le
texte, on utilise l'option --raw-output
:
% curl -s http://rdap.db.ripe.net/ip/2001:1470:8000:53::44 | jq --raw-output .country SI
Et si on veut un membre d'un objet qui est lui-même un membre de l'objet de plus haut niveau ? On crée un filtre avec les deux clés :
% echo '{"foo": {"zataz": null, "bar": 42}}' | jq .foo.bar 42
Un exemple réel d'un tel « double déréférencement » serait avec l'API de GitHub, ici pour afficher tous les messages de commit :
% curl -s https://api.github.com/repos/bortzmeyer/check-soa/commits | \ jq --raw-output '.[].commit.message' New rules for the Go linker (see <http://stackoverflow.com/questions/32468283/how-to-contain-space-in-value-string-of-link-flag-when-using-go-build>) Stupid regression to bug #8. Fixed. Timeout problem with TCP Query the NN RRset with EDNS. Closes #9 Stupidest bug possible: I forgot to break the loop when we got a reply TCP support ...
Et si le texte JSON était formé d'un tableau et pas d'un objet (rappel : un objet JSON est un dictionnaire) ? jq permet d'obtenir le nième élément d'un tableau (ici, le quatrième, le premier ayant l'indice 0) :
% echo '[1, 2, 3, 5, 8]' | jq '.[3]' 5
(Il a fallu mettre le filtre entre apostrophes car les crochets sont des caractère spéciaux pour le shell Unix.)
Revenons aux exemples réels. Si on veut le cours du Bitcoin en euros, CoinDesk nous fournit une API REST pour cela. On a juste à faire un triple accès :
% curl -s https://api.coindesk.com/v1/bpi/currentprice.json | jq .bpi.EUR.rate "2,315.7568"
Notez que le cours est exprimé sous forme d'une chaîne de caractères, pas d'un nombre, et qu'il n'est pas à la syntaxe correcte d'un nombre JSON. C'est un des inconvénients de JSON : ce format est un tel succès que tout le monde en fait, et souvent de manière incorrecte. (Sinon, regardez une solution sans jq mais avec sed.)
Maintenant, on veut la liste des comptes
Mastodon qui ont pouèté au sujet de
l'ANSSI. Le résultat de la requête
madonctl --output json timeline :anssi
est un
tableau. On va devoir
itérer sur ce tableau, ce qui se fait avec
le filtre []
(déjà utilisé plus haut dans l'exemple GitHub), et faire un double
déréférencement, le membre account
puis le
membre acct
:
% madonctl --output json timeline :anssi | jq '.[].account.acct' "nschont@mastodon.etalab.gouv.fr" "ChristopheCazin@mastodon.etalab.gouv.fr" "barnault@mastodon.etalab.gouv.fr" ...
Parfait, on a bien notre résultat. Mais le collègue qui avait demandé ce travail nous dit qu'il faudrait éliminer les doublons, et trier les comptes par ordre alphabétique. Pas de problème, jq dispose d'un grand nombre de fonctions pré-définies, chaînées avec la barre verticale (ce qui ne déroutera pas les utilisateurs Unix) :
% madonctl --output json timeline :anssi | jq '[.[].account.acct] | unique | .[]' "ChristopheCazin@mastodon.etalab.gouv.fr" "G4RU" "Sangokuss@framapiaf.org" ...
Oulah, ça devient compliqué ! unique
se comprend sans difficulté mais c'est
quoi, tous ces crochets en plus ? Comme
unique
travaille sur un tableau, il a fallu en
fabriquer un : les crochets extérieurs dans l'expression
[.[].account.acct]
disent à jq de faire un
tableau avec tous les .account.acct
extraits. (On peut aussi fabriquer un objet JSON et je rappelle que
objet = dictionnaire.) Une fois ce tableau
fait, unique
peut bien travailler mais le résultat sera alors un tableau :
% madonctl --output json timeline :anssi | jq '[.[].account.acct] | unique' [ "ChristopheCazin@mastodon.etalab.gouv.fr", "G4RU", "Sangokuss@framapiaf.org", ...
Si on veut « aplatir » ce tableau, et avoir juste une suite de
chaînes de caractères, il faut refaire une itération à la fin (le
.[]
).
(Les Unixiens expérimentés savent qu'il existe
uniq et sort comme commandes Unix et
qu'on aurait aussi pu faire jq '.[].account.acct' | sort
| uniq
. Chacun ses goûts. Notez aussi qu'il n'y a pas
besoin d'un tri explicite en jq : unique
trie
le tableau avant d'éliminer les doublons.)
J'ai dit un peu plus haut que jq pouvait construire des textes
JSON structurés comme les tableaux. Ça marche aussi avec les
objets, en indiquant la clé et la valeur de chaque membre. Par
exemple, si je veux un tableau dont les éléments sont des objets
où la clé href
désigne l'URL d'un pouète
Mastodon (ici, les pouètes ayant utilisé le
mot-croisillon « slovénie ») :
% madonctl --output json timeline :slovénie | jq "[.[] | { href: .url }]" [ { "href": "https://mastodon.gougere.fr/users/bortzmeyer/updates/40282" }, { "href": "https://mamot.fr/@Nighten_Dushi/3131270" }, { "href": "https://mastodon.cx/users/DirectActus/updates/29810" } ]
Les accolades entourant la deuxième partie
du programme jq indiquent de construire un objet, dont on indique comme clé
href
et comme valeur le membre
url
de l'objet original.
Rappelez-vous, jq ne sert pas qu'à des filtrages ultra-simples
d'un champ de l'objet JSON. On peut écrire de vrais programmes et
il peut donc être préférable de les mettre dans un fichier. (Il
existe évidemment un mode Emacs pour éditer
plus agréablement ces fichiers source,
jq-mode.)
Si le fichier accounts.jq
contient :
# From a Mastodon JSON file, extract the accounts [.[].account.acct] | unique | .[]
Alors, on pourra afficher tous les comptes (on se ressert de
--raw-output
pour ne pas avoir d'inutiles
guillemets) :
% madonctl --output json timeline :anssi | jq --raw-output --from-file accounts.jq ChristopheCazin@mastodon.etalab.gouv.fr G4RU Sangokuss@framapiaf.org ...
Mais on peut vouloir formater des résultats sous une forme de
texte, par exemple pour inclusion dans un message ou un
rapport. Reprenons notre nœud Bitcoin et
affichons la liste des pairs (Bitcoin est un réseau
pair-à-pair), en les classant selon le
RTT décroissant. On met ça dans le fichier bitcoin.jq
:
"You have " + (length | tostring) + " peers. From the closest (network-wise) to the furthest away", ([.[] | {"address": .addr, "rtt": .pingtime}] | sort_by(.rtt) | .[] | ("Peer " + .address + ": " + (.rtt|tostring) + " ms"))
Et on peut faire :
% bitcoin-cli getpeerinfo | jq --raw-output --from-file bitcoin.jq You have 62 peers. From the closest (network-wise) to the furthest away Peer 10.105.127.82:38806: 0.00274 ms Peer 192.0.2.130:8333: 0.003272 ms Peer [2001:db8:210:5046::2]:33567: 0.014099 ms ...
On a utilisé des constructions de tableaux et d'objets, la
possibilité de trier sur un critère quelconque (ici la valeur de
rtt
, en paramètre à sort_by
) et des fonctions utiles pour l'affichage
de messages : le signe plus indique la
concaténation de chaînes, et la fonction
tostring
transforme un entier en chaîne de
caractères (jq ne fait pas de conversion de type implicite).
jq a également des fonctions pré-définies pour faire de
l'arithmétique. Utilisons-les pour analyser les résultats de
mesures faites par des sondes
RIPE Atlas. Une fois une mesure lancée (par exemple la
mesure de RTT
#9211624,
qui a été créée par la commande atlas-reach -r 100 -t 1
2001:500:2e::1
, cf. mon article « Using RIPE Atlas to Debug Network Connectivity Problems »),
les résultats peuvent être récupérés sous forme d'un fichier JSON
(à l'URL
https://atlas.ripe.net/api/v2/measurements/9211624/results/
). Cherchons
d'abord le RTT maximal :
% jq '[.[].result[0].rtt] | max' < 9211624.json 505.52918
On a pris le premier élément du tableau car, pour cette mesure,
chaque sonde ne faisait qu'un test. Ensuite, on utilise les
techniques jq déjà vues, plus la fonction
max
(qui prend comme argument un tableau, il
a donc fallu construire un tableau). La sonde la plus lointaine de l'amer est donc à plus de 500 millisecondes. Et le
RTT minimum ? Essayons la fonction min
% jq '[.[].result[0].rtt] | min' < 9211624.json null
Ah, zut, certaines sondes n'ont pas réussi et on n'a donc pas de
RTT, ce que jq traduit en null
. Il faut donc
éliminer du tableau ces échecs. jq permet de tester une valeur,
avec select
. Par exemple,
(select(. != null)
va tester que la valeur
existe. Et une autre fonction jq, map
, bien
connue des programmeurs qui aiment le style fonctionnel, permet
d'appliquer une fonction à tous les éléments d'un tableau. Donc,
réessayons :
% jq '[.[].result[0].rtt] | map(select(. != null)) | min' < 9211624.json 1.227755
C'est parfait, une milli-seconde pour la sonde la plus
proche. Notez que, comme map
prend un tableau
en entrée et rend un tableau, on aurait aussi pu l'utiliser au
début, à la place des crochets :
% jq 'map(.result[0].rtt) | map(select(. != null)) | min' < 9211624.json 1.227755
Et la moyenne des RTT ? On va utiliser
les fonctions
add
(additionner tous les éléments du
tableau) et length
(longueur du tableau) :
% jq '[.[].result[0].rtt] | add / length' < 9211624.json 76.49538228
(Il y a une faille dans le programme, les valeurs nulles auraient dû être exclues. Je vous laisse modifier le code.) La moyenne n'est pas toujours très utile quand on mesure des RTT. C'est une métrique peu robuste, que quelques outliers suffisent à perturber. Il est en général bien préférable d'utiliser la médiane. Une façon simple de la calculer est de trier le tableau et de prendre l'élément du milieu (ou le plus proche du milieu) :
% jq '[.[].result[0].rtt] | sort | .[length/2]' < 9211624.json 43.853845
On voit que la médiane est bien plus petite que la moyenne, quelques
énormes RTT ont en effet tiré la moyenne vers le haut. J'ai un peu
triché dans le filtre jq ci-dessus car il ne marche que pour des
tableaux de taille paire. Si elle est impaire,
length/2
ne donnera pas un nombre entier et on
récupérera null
. Corrigeons, ce sera l'occasion
d'utiliser pour la première fois la structure de contrôle
if
. Notez qu'une expression jq doit toujours
renvoyer quelque chose (les utilisateurs de langages
fonctionnels comme Haskell ne
seront pas surpris par cette règle), donc pas de if
sans une
branche else
:
% jq '[.[].result[0].rtt] | sort | if length % 2 == 0 then .[length/2] else .[(length-1)/2] end' < 9211624.json 43.853845
Et maintenant, si on veut la totale, les quatre métriques avec du texte, on mettra ceci dans le fichier source jq. On a utilisé un nouvel opérateur, la virgule, qui permet de lancer plusieurs filtres sur les mêmes données :
map(.result[0].rtt) | "Median: " + (sort | if length % 2 == 0 then .[length/2] else .[(length-1)/2] end | tostring), "Average: " + (map(select(. != null)) | add/length | tostring), "Min: " + (map(select(. != null)) | min | tostring), "Max: " + (max | tostring)
Et cela nous donne :
% jq --raw-output --from-file atlas-rtt.jq < 9211624.json Median: 43.853845 Average: 77.26806290909092 Min: 1.227755 Max: 505.52918
Peut-on passer des arguments à un programme jq ?
Évidemment. Voyons un exemple avec la base des « espaces blancs »
décrite dans le RFC 7545. On va chercher la
puissance d'émission maximale autorisée pour une fréquence
donnée, avec ce script jq, qui contient la variable
frequency
, et qui cherche une plage de
fréquences (entre startHz
et
stopHz
) comprenant cette fréquence :
.result.spectrumSchedules[0].spectra[0].frequencyRanges[] | select (.startHz <= ($frequency|tonumber) and .stopHz >= ($frequency|tonumber)) | .maxPowerDBm
On l'appelle en passant la fréquence où on souhaite émettre :
% jq --from-file paws.jq --arg frequency 5.2E8 paws-chicago.json 15.99999928972511
(Ne l'utilisez pas telle quelle : par principe, les espaces blancs ne sont pas les mêmes partout. Celui-ci était pour Chicago en juillet 2017.)
Il est souvent utile, quand on joue avec des données, de les
grouper par une certaine caractéristique, par exemple pour compter
le nombre d'occurrences d'un certain phénomène, ou bien pour
classer. C'est le classique GROUP BY
de
SQL, qui a son équivalent en jq. Revenons à
la liste des comptes Mastodon qui ont pouèté sur
l'ANSSI et affichons combien chacun a émis
de pouètes. On va grouper les pouètes par compte, fabriquer
un objet JSON dont les clés sont le compte, le trier, puis afficher le résultat. Avec ce code jq :
# Count the number of occurrences [.[].account] | group_by(.acct) | # Create an array of objects {account, number} [.[] | {"account": .[0].acct, "number": length}] | # Now sort sort_by(.number) | reverse | # Now, display .[] | .account + ": " + (.number | tostring)
On obtiendra :
% madonctl --output json timeline :anssi | jq -r -f anssi.jq gapz@mstdn.fr: 4 alatitude77@mastodon.social: 4 nschont@mastodon.etalab.gouv.fr: 2 zorglubu@framapiaf.org: 1 sb_51_@mastodon.xyz: 1
Voilà, on a bien avancé et je n'ai toujours pas donné
d'exemple avec le DNS. Un autre cas
d'utilisation de select
va y pourvoir. Le
DNS Looking Glass peut produire
des résultats en JSON, par exemple ici le
SOA du TLD
.xxx
: curl
-s -H 'Accept: application/json'
https://dns.bortzmeyer.org/xxx/SOA
. Si je veux juste le
numéro de série de cet enregistrement SOA ?
% curl -s -H 'Accept: application/json' https://dns.bortzmeyer.org/xxx/SOA | \ jq '.AnswerSection[0].Serial' 2011210193
Mais il y a un piège. En prenant le premier élément de la
Answer Section, j'ai supposé qu'il s'agissait
bien du SOA demandé. Mais l'ordre des éléments dans les sections
DNS n'est pas défini. Par exemple, si une signature
DNSSEC est renvoyée, elle sera peut-être le
premier élément. Il faut donc être plus subtil, et utiliser
select
pour ne garder que la réponse de type SOA :
% curl -s -H 'Accept: application/json' https://dns.bortzmeyer.org/xxx/SOA | \ jq '.AnswerSection | map(select(.Type=="SOA")) | .[0].Serial' 2011210193
Notre code jq est désormais plus robuste. Ainsi, sur un nom qui a plusieurs adresses IP, on peut ne tester que celles de type IPv6, ignorant les autres, ainsi que les autres enregistrements que le serveur DNS a pu renvoyer :
% curl -s -H 'Accept: application/json' https://dns.bortzmeyer.org/gmail.com/ADDR | \ jq '.AnswerSection | map(select(.Type=="AAAA")) | .[] | .Address' "2607:f8b0:4006:810::2005"
Bon, on approche de la fin, vous devez être fatigué·e·s, mais encore deux exemples, pour illustrer des trucs et concepts utiles. D'abord, le cas où une clé dans un objet JSON n'a pas la syntaxe d'un identificateur jq. C'est le cas du JSON produit par le compilateur Solidity. Si je compile ainsi :
% solc --combined-json=abi registry.sol > abi-registry.json
Le JSON produit a deux défauts. Le premier est que certains utilisateurs ont une syntaxe inhabituelle (des points dans la clé, et le caractère deux-points) :
{"registry.sol:Registry": {"abi": ...
jq n'accepte pas cette clé comme filtre :
% jq '.contracts.registry.sol:Registry.abi' abi-registry.json jq: error: syntax error, unexpected ':', expecting $end (Unix shell quoting issues?) at <top-level>, line 1: .contracts.registry.sol:Registry.abi jq: 1 compile error
Corrigeons cela en mettant la clé bizarre entre guillemets :
% jq '.contracts."registry.sol:Registry".abi' abi-registry.json
On récupère ainsi l'ABI du
contrat.
Mais, et là c'est gravement nul de la part du compilateur, l'ABI est
une chaîne de caractères à évaluer pour en faire du vrai JSON !
Heureusement, jq a tout ce qu'il faut pour cette évaluation, avec la
fonction fromjson
:
% jq '.contracts."registry.sol:Registry".abi | fromjson' abi-registry.json
Ce problème des clés qui ne sont pas des identificateurs à la
syntaxe traditionnelle se pose aussi si l'auteur du JSON a choisi,
comme la norme le lui permet, d'utiliser des caractères
non-ASCII pour les identificateurs. Prenons
par exemple le fichier JSON des verbes
conjugués du
français, en
. Le
JSON est :
https://github.com/Drulac/Verbes-Francais-Conjugues
[{ "Indicatif" : { "Présent" : [ "abade", "abades", "abade", "abadons", "abadez", "abadent" ], "Passé composé" : [ "abadé", "abadé", "abadé", "abadé", "abadé", "abadé" ], "Imparfait" : [ "abadais", "abadais", "abadait", "abadions", "abadiez", "abadaient" ], "Plus-que-parfait" : [ "abadé", "abadé", "abadé", "abadé", "abadé", "abadé" ], "Passé simple" : [ "abadai", "abadas", "abada", "abadâmes", "abadâtes", "abadèrent" ], "Passé antérieur" : [ "abadé", "abadé", "abadé", "abadé", "abadé", "abadé" ], ...
Il faut donc mettre les clés non-ASCII entre guillemets. Ici, la conjugaison du verbe « reposer » :
% jq 'map(select(.Infinitif."Présent"[0] == "reposer"))' verbs.json [ { "Indicatif": { "Présent": [ "repose", "reposes", "repose", ...
Notez toutefois que jq met tout en mémoire. Cela peut l'empêcher de lire de gros fichiers :
% ls -lh la-crime.json -rw-r--r-- 1 stephane stephane 798M Sep 9 19:09 la-crime.json % jq .data la-crime.json error: cannot allocate memory zsh: abort jq .data la-crime.json
Et un dernier exemple, pour montrer les manipulations de date en
jq, ainsi que la définition de fonctions. On va
utiliser l'API d'Icinga pour récupérer
l'activité de la supervision. La commande curl -k
-s -u operator:XXXXXXX -H 'Accept: application/json' -X POST
'https://ADRESSE:PORT/v1/events?types=StateChange&queue=operator'
va nous donner les changements d'état des machines et des services
supervisés. Le résultat de cette commande est du JSON : on souhaite
maintenant le formater de manière plus
compacte et lisible. Un des membres JSON est une date
écrite sous forme d'un nombre de secondes depuis une
epoch. On
va donc la convertir en jolie date avec la fonction
todate
. Ensuite, les états Icinga
(.state
) sont affichés
par des nombres (0 = OK, 1 = Avertissement, etc.), ce qui n'est pas
très agréable. On les
convertit en chaînes de caractères, et mettre cette conversion dans
une fonction, s2n
:
def s2n(s): if s==0 then "OK" else if s==1 then "Warning" else "Critical" end end; (.timestamp | todate) + " " + .host + " " + .service + " " + " " + s2n(.state) + " " + .check_result.output
Avec ce code, on peut désormais exécuter :
% curl -k -s -u operator:XXXXXXX -H 'Accept: application/json' -X POST 'https://ADRESSE:PORT/v1/events?types=StateChange&queue=operator' | \ jq --raw-output --unbuffered --from-file logicinga.jq ... 2017-08-08T19:12:47Z server1 example Critical HTTP CRITICAL: HTTP/1.1 200 OK - string 'Welcome' not found on 'http://www.example.com:80/' - 1924 bytes in 0.170 second response time
Quelques lectures pour aller plus loin :
Merci à Jean-Edouard Babin et Manuel Pégourié-Gonnard pour la correction d'une grosse bogue dans la première version de cet article.
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)