JSON par-ci, JSON par-là. Le format de données JSON a conquis le monde du développement. Il a l’avantage d’être lisible par les humains et il est supporté par la plupart des langages des programmation (via un composant externe ou non). Cela en fait un format d’échange parfait ! Parfait ? Êtes-vous vraiment sûr ? Avez-vous déjà essayé de manipuler du JSON dans un shell ?

En cherchant un peu, il est possible de trouver deux projets :

  • jq : un sed pour JSON
  • jsawk : un awk pour JSON

Pour les cas d’usages les plus courants et les plus simples, à savoir extraire des informations et éventuellement lancer des traitements sur des éléments de liste, jq est amplement suffisant. Cet article s’attarde donc que sur cet outil.

Extraction de données

jq est assez simple. Il permet d’extraire des informations d’un flux JSON. Il est également capable de générer un flux JSON en sortie. Prenons l’exemple de l’appel à une API de météo :

$ curl -s 'http://api.openweathermap.org/data/2.5/weather?q=Paris,fr&appid=44db6a862fba0b067b1930da0d769e98'

Qui retourne le JSON suivant :

{
  "coord": {
    "lon": 2.35,
    "lat": 48.85
  },
  "weather": [
    {
      "id": 800,
      "main": "Clear",
      "description": "Sky is Clear",
      "icon": "01n"
    }
  ],
  "base": "cmc stations",
  "main": {
    "temp": 272.941,
    "pressure": 1031.21,
    "humidity": 85,
    "temp_min": 272.941,
    "temp_max": 272.941,
    "sea_level": 1044.04,
    "grnd_level": 1031.21
  },
  "wind": {
    "speed": 3.81,
    "deg": 15.5004
  },
  "clouds": {
    "all": 0
  },
  "dt": 1455577647,
  "sys": {
    "message": 0.0041,
    "country": "FR",
    "sunrise": 1455519492,
    "sunset": 1455556316
  },
  "id": 2988507,
  "name": "Paris",
  "cod": 200
}

Avec jq, il est assez simple d’extraire la description du temps actuel sur Paris :

curl -s 'http://api.openweathermap.org/data/2.5/weather?q=Paris,fr&appid=44db6a862fba0b067b1930da0d769e98' | jq '.weather[0].description'
"Sky is Clear"

Transformation JSON

Il est également possible de générer un nouveau flux JSON :

$ curl -s 'http://api.openweathermap.org/data/2.5/weather?q=Paris,fr&appid=44db6a862fba0b067b1930da0d769e98' | jq '{country: .sys.country, city: .name, weather: .weather[].description}'

Pour obtenir le document JSON suivant :

{
  "country": "FR",
  "city": "Paris",
  "weather": "Sky is Clear"
}

Il est possible de combiner plusieurs filtres jq avec l’opérateur |, itérer sur des tableaux JSON1, appliquer des opérations, des expressions régulières, etc… Toutes les options sont décrites sur le site.

Traitement sur une liste d’éléments

Docker permet de lister les ports exposés d’un container avec la commande docker port. La commande ci-dessous ne retourne rien si l’option -p n’a pas été utilisée pour créer le container :

$ docker port myproject_cassandra_1

Pourtant le fichier Dockerfile de Cassandra indique que les ports 7000, 7001, 7199, 9042 et 9160 sont exposables. Alors comment automatiser le contrôle de l’accès à ces ports ? Cette surveillance se base sur la commande docker inspect qui retourne la configuration d’un container au format JSON. Il est possible d’afficher l’adresse IP du container et la liste des ports exposables avec la commande suivante :

$ docker inspect myproject_cassandra_1 | jq -r '.[] | {ip: .NetworkSettings.IPAddress, ports: .Config.ExposedPorts | keys} | {ip: .ip, port: .ports[] | sub("/tcp"; "")} | .ip+" "+.port'
172.17.0.3 7000
172.17.0.3 7001
172.17.0.3 7199
172.17.0.3 9042
172.17.0.3 9160

Cet exemple est assez complet puisqu’il utilise :

  • l’opérateur | pour enchainer les filtres jq
  • le filtre keys pour extraire les clés d’un objet JSON : "ExposedPorts":{"7000/tcp":{},"7001/tcp":{},"7199/tcp":{},"9042/tcp":{},"9160/tcp":{}}
  • la fonction sub() pour substituer les chaines de caractères : suppression des “/tcp
  • l’opérateur + pour concaténer des chaines de caractères
  • l’option -r pour obtenir un résultat brut (ie. sans les guillemets JSON)

Pour tester l’accès à ces ports depuis l’extérieur du container, il suffit d’exécuter le programme nc pour chaque ligne avec cette commande :

docker inspect myproject_cassandra_1 | jq -r '.[] | {ip: .NetworkSettings.IPAddress, ports: .Config.ExposedPorts | keys} | {ip: .ip, port: .ports[] | sub("/tcp"; "")} | .ip+" "+.port' | xargs -L 1 nc -w 1
nc: can't connect to remote host (172.17.0.3): Connection refused
nc: can't connect to remote host (172.17.0.3): Connection refused
nc: can't connect to remote host (172.17.0.3): Connection refused
nc: can't connect to remote host (172.17.0.3): Connection refused
nc: can't connect to remote host (172.17.0.3): Connection refused

Du point de vue Docker, cet exemple est complètement inutile mais il montre comment utiliser jq pour lancer des traitements sur un tableau d’éléments se trouvant dans un document JSON.

jq play

En plus de fournir un outil simple et efficace, le projet propose également jqplay : un outil en ligne pour tester les filtres jq.

Avec jq, il n’est plus nécessaire d’écrire des scripts dans des langages de programmation “avancés” pour automatiser certaines tâches. Un simple script bash peut être amplement suffisant !

  1. Assez pratique pour lancer des commandes sur chaque éléments avec le programme xargs