Faire du JSON avec PHP ou tout autre langage orienté web, c’est trivial. Mais lorsqu’il faut commencer à se le coltiner en Bash, c’est déjà plus galère..
Pour un besoin personnel, je dois justement lire du JSON dans un shell. Le cahier des charges est simple : J’ai PHP et Python sur la bécane (un serveur), et dans l’optique de partager ce code plus tard, je ne souhaite pas avoir de dépendances. Exit donc jq, qui est une librairie permettant de combler largement mon besoin. En plus, je ne connais pas grand chose à Python, et j’ai franchement pas le temps de m’y mettre.
Dans ce billet, je vous montre comment exploiter du JSON en Bash avec PHP. Je tiens tout de même à préciser que cet article n’a pas vocation à vous apprendre ces 2 langages, mais suppose que vous en connaissiez déjà les bases.

Je vous préviens, c’est long, mais c’est également l’occasion de voir quelques astuces Bash (notamment sur les tableaux) que peut-être vous ne connaissiez pas 😉

Petit aparté

J’avais dit vouloir prendre un peu de recul par rapport à ce blog, et me recentrer. Je continue donc le site dont je vous avais parlé, mais il est difficile pour moi de ne pas écrire ici dès lors que j’ai le clavier sous la main, dès que quelque chose me trotte dans la tête.
Aujourd’hui, reprise du boulot, je lâche ma tasse de café pour rédiger quelques lignes ici. On ne se refait pas. Et puis, je ne m’étais jamais imposé de rythme, alors autant continuer comme ça.
Oui, je change d’avis comme de chemise. Ou plutôt, je fais comme bon me semble, comme j’ai toujours fait. Si demain je n’ai pas envie de continuer cet article, qui ira me dire quelque chose ?

Je crois que je ne sais pas comment évoluera mon temps passé sur le PC. Il y a fort à parier qu’à terme les extras ne se fassent plus. Je consultais de temps à autre Mastodon, mais il ne s’agit pour la plupart que de geeks / libristes qui exposent leurs articles, des liens vers un tweet, une humeur à partager. Je n’ai pas besoin de ça.
Au contraire, je pense qu’en fait ce blog est devenu le centre de mon activité sur Internet, et que le reste ne fait que graviter autour. L’avenir nous le dira.

Utiliser PHP en Bash

Pour revenir à nos moutons, nous souhaitions exploiter du JSON en Bash avec PHP. On va tenter d’y aller étape par étape. Il s’agit tout d’abord d’exécuter un script PHP depuis un script Bash ou directement depuis un shell.

Nous partirons sur 2 fichiers : un script Bash, l’autre un PHP, tous 2 dans le même dossier.

Voilà le contenu du fichier ‘disk.php’, qui ne fera que retourner une valeur au script Bash :

À présent, le fichier ‘test.sh’, le fichier Bash :

Si vous lancez à présent le script test.sh, que vous aurez rendu exécutable auparavant, vous aurez ceci :

J’explique :

Pour lancer un script PHP depuis Bash, il suffit de faire php -f chemin/du/script.php  :

  • php  est la commande permettant de lancer la fonction PHP, que vous aurez installé bien sûr…
  • L’option -f  indique qu’on souhaite exécuter un fichier et non pas une commande
  • Le dernier argument est le chemin et le nom du script à traiter.

Au cas où : dirname $0  permet de récupérer le chemin absolu du script courant.

Enfin, il est bon de savoir que tout ce qui sortira du script PHP (les echo, print etc), ne seront pas affichés. Ils seront simplement retournés au script Bash.
On pourrait ainsi réécrire la partie Bash comme ceci, sans toucher au PHP :

Ça ne changerait rien, PHP n’affiche rien. La valeur 5 serait simplement affectée à la variable $nbDisks , qu’on afficherait ensuite.

Arguments

Tout ça c’est bien, mais c’est trop scolaire. À quoi sert d’appeler un script si on ne peut pas lui passer d’arguments ? Pas grand chose, en effet.
Il y a 2 méthodes pour passer des arguments à PHP depuis Bash, que nous allons voir.

Des arguments simples

Il s’agit de passer un seul argument, une seule valeur au script à exécuter.

Dans l’exemple qui va suivre, je souhaite récupérer en Bash le nombres d’élèves de certaines classes. Si le nom d’un professeur est renseigné, on va chercher le nombre d’élèves de sa classe (on peut imaginer une requête SQL depuis PHP par exemple). Si aucun professeur n’est trouvé, on indique une erreur.
Enfin, si aucun nom n’est indiqué, on retourne le nombre d’élèves de toutes les classes.

Script PHP :

2 points tout de même :

  • $argv  est un tableau contenant les arguments passés au script lorsque PHP est utilisé en CLI. Quelque soit le nombre d’arguments, $argv[0]  contient le chemin et le nom du script en cours.
  • $argc  contient le nombre d’arguments passés au script. Lorsque aucun argument n’est donné, cette variable est égale à 1.

Passons au script Bash :

Le résultat :

À noter qu’il est possible de passer plusieurs arguments avec cette méthode, simplement en les espaçant :

Cette méthode permet de passer plusieurs valeurs simplement, mais elle ne couvre pas tous les besoins.

Bon à savoir : Chaque argument est envoyé comme une chaîne de caractères, qu’il soit entouré de guillemets ou pas.
Aussi, dans l’exemple précèdent, le ’55’ passé au script sera de type string lorsqu’il sera utilisé par PHP.

Arguments associatifs

Il existe une astuce que j’ai découvert il y a peu, c’est de pouvoir passer des paramètres avec un nom et une valeur à un tel script. Quelle utilité à vrai dire ?
Dans l’exemple précèdent, avec les classes et le nombre d’élèves, comment passer au script que la classe de Mme Michu possède maintenant 30 élèves ?

Et bien il suffit de passer les paramètres précédés d’un double tiret --  et de les assigner comme suit :

Coté PHP, nous allons toujours utiliser les variables $argc et $argv. Cependant, pour pouvoir les utiliser correctement, il va falloir faire une petite manipulation :

Une fois le script Bash lancé, le résultat sera celui-ci :

Je n’explique pas le print_r($argv), je pense que vous comprendrez. En revanche, on voit clairement que pour exploiter les arguments, il va falloir séparer le nom de l’argument (nom et eleves) de leurs valeurs (Mme Michu et 30).

C’est ce que fait le script ensuite : Il explose chaque argument s’il y en a avec le signe ‘=’, et on se retrouve avec un tableau ($args) qui contient les arguments exploitables. Pas beau ça ?

Variables globales

Avant d’attaquer les choses sérieuses, je voulais vous parler d’un petit truc qui m’est bien utile.
Dans mes scripts, j’ai régulièrement besoin de passer des variables de Bash à PHP. Je peux utiliser les arguments décrits ci-dessus bien sûr, mais il existe des cas où cela devient redondant.
C’est le cas par exemple lorsqu’il s’agit de passer encore et toujours la même valeur, la même variable.

En Bash

Il est possible de passer, en Bash, des variables en globales, c’est à dire que cette variable sera connue de tous les scripts qui seront exécutés.

Vous connaissiez cette façon pour assigner une valeur à une variable :

Et bien, il y a une autre façon de déclarer une variable pour qu’elle soit globale, qu’elle puisse être utilisée dans tous les scripts lancés depuis le processus :

La variable bar  est ici accessible depuis n’importe quel script ! Essayons cela.
Créez un fichier qui va déclarer 2 variables, une locale et une globale, et qui lancera un autre script Bash :

Puis créez le second fichier, export.sh, qui sera appelé depuis le premier script, et qui aura pour simple fonction d’afficher le contenu de ces 2 variables :

Lançons le premier script (test.sh) et observons le résultat :

Seul le résultat de $bar est affiché, car $foo  n’est pas connu du script export.sh. En revanche, Bash ne lève aucune erreur si une variable n’existe pas, elle sera simplement considérée comme une chaîne de caractères vide.

En PHP

À présent, voyons comment récupérer une variable globale déclarée en Bash depuis PHP.
Cela est possible grâce à la fonction getenv(), qui permet de récupérer une variable de l’environnement.

Comme tout à l’heure, nous allons déclarer une variable globale dans notre script, et appeler un script PHP, qui devra l’afficher :

Et le script PHP :

Lançons le script Bash, et voyons le résultat :

Il est possible de récupérer les variables de Bash en PHP. Génial non ?
À savoir qu’une fois la variable déclarée avec export , il est possible de la modifier à souhait en Bash, sans la précéder de export , elle sera toujours globale.

Du JSON en Bash avec PHP

Maintenant que nous avons vu qu’utiliser PHP avec Bash n’était pas bien difficile, il est l’heure de nous intéresser au sujet important : Exploiter du JSON en Bash avec PHP.

Dans les exemples qui vont suivre, pour éviter les répétitions, nous partirons du principe que notre fichier JSON system.json contient ceci :

Récupérer une valeur

Le plus simple pour démarrer, c’est de demander à PHP de nous retourner une simple valeur contenue dans ce fichier JSON.
Pour cela, il nous faudra en Bash fournir un argument : La clé que nous souhaitons récupérer. Le script PHP s’occupera de nous retourner le résultat.

Je vous donne le code complet du script PHP (je l’ai appelé system.php). Je me suis aidé des trucs que l’on a vu plus haut, et je vous explique le reste ensuite :

Au début, on voit que je fais appel à la variable ROOT_PATH  qui sera définie dans le script Bash. Elle nous servira en PHP à aller récupérer le chemin du fichier JSON.
Puis on définit le chemin du fichier JSON à utiliser.

La partie suivante est expliquée en haut, jusqu’à la récupération du fichier JSON, que l’on décode en un objet JSON. Si vous n’êtes pas à l’aise avec l’utilisation de JSON en PHP, je ne peux que vous conseiller cet excellent article sur le sujet 😉

Enfin, nous allons ‘découper’ chaque partie de la clé si elle est fournie, en cherchant les éventuels  ->  présents, et tenter de récupérer la valeur correspondante dans le JSON. Par exemple en fournissant la clé ‘system->memory->ram’, le script va découper celle-ci en 3 parties et tenter d’accéder successivement à :
$system->system , puis $system->system->memory , et enfin $system->system->memory->ram .
Si celle-ci existe, sa valeur est retournée, sinon, on retourne la dernière valeur trouvée.

L’utilisation de ce script avec Bash est donc toute aisée, puisqu’il suffit de définir le dossier courant du script, la variable globale ROOT_PATH , et la clé de la valeur à récupérer avec le nom ‘key’ :

Résultat :

Pas bien ça ? Si, c’est bien, mais pas tant que ça.
Imaginez que vous souhaitez récupérer tous les packages contenus dans le fichier JSON. Si vous ne les connaissez pas à l’avance, c’est impossible avec cette méthode.
C’est ce que nous allons voir tout de suite.

Récupérer un tableau

Si à première vue il est assez facile de passer un tableau de PHP à Bash, en réalité c’est tout autre. Comme vous le savez (ou le découvrez), PHP ne peut retourner que du texte à Bash. Il va donc falloir ruser pour récupérer un tel tableau.

Tableau indexé

Il s’agit simplement de récupérer une liste, sans notion d’association, de valeurs. On souhaite par exemple récupérer les applications installées dans notre fichier JSON, à savoir Firefox, Netbeans et VLC.

Comme dit juste au dessus, le problème est que le retour de PHP à Bash ne se fait que par une chaîne de caractères. Pour en extraire un tableau, il va donc falloir trouver un délimiteur, un truc qui va séparer chaque valeur.
Ce séparateur peut être plein de choses, comme un espace, un retour à la ligne, une lettre, …Dans tous les cas, il faut être certain de ne pas croiser ce caractère dans les données JSON à traiter.
Pour ma part, comme je sais que j’aurai éventuellement des espaces, des tabulations et des sauts de ligne, j’ai choisi d’utiliser un symbole que je ne croiserai certainement pas : ¶

Si l’on est sûr que les valeurs du tableau ne contiennent pas ce caractère spécial, on peut donc l’utiliser pour en séparer les valeurs.

On va modifier un peu notre script PHP pour que, si la clé demandée contient un tableau (objet StdClass de notre JSON), il nous retourne le tableau sous forme de chaîne de caractère, en séparant les valeurs par le caractère choisi plus haut :

J’ai mis en bleu les lignes que j’ai rajouté par rapport au script précédent. C’est ce bout de code qui va permettre de retourner toutes les valeurs si la clé passée ne contient pas qu’une seule valeur mais plusieurs.

Rien de compliqué là-dedans, vous devriez vous en sortir avec les commentaires. Passons au Bash :

Plusieurs choses à noter ici :

  • declare -a apps  : On déclare la variable apps comme un tableau indexé.
  • IFS  : C’est une variable du Shell, qui définit le séparateur. Par défaut, elle contient un espace, une tabulation (\t)et un retour à la ligne (\n). Chaque caractère qu’elle contient est un séparateur à lui-seul, il n’est pas possible d’utiliser une suite de caractères.
    En le faisant suivre d’une autre instruction, le séparateur est modifié uniquement pour cette instruction.
  • read  : Attend une réponse de l’utilisateur, comme un prompt
  • -a apps  : Détermine le nom de la variable dans laquelle le résultat de read sera stockée.
  • <<<  : Permet de ne pas attendre que l’utilisateur tape quelque chose, mais va envoyer la résultat du script PHP à l’instruction read, comme si l’utilisateur l’avait tapé lui-même dans le Shell.
  • On lance le script PHP system.php en donnant la clé ‘apps’, pour récupérer les applications de notre fichier JSON.

À l’issue de la commande read …, la variable $apps  contient un tableau indexé avec les valeurs Firefox, Netbeans et VLC.

Puis on parcours simplement le tableau et on affiche chaque valeur.
Enfin, on affiche uniquement l’élément du tableau qui a pour index 1. Pour mémoire, les index commencent à 0.

Si on lance le script, voilà le résultat :

Tableaux associatifs

On touche au but, mais également la partie la plus chiante.
Toujours dans notre fichier JSON de départ, comment pourriez-vous associer les disques (sda7, sda5 etc) à leurs mémoires respectives ?
Depuis Bash 4, il est possible d’utiliser des tableaux associatifs. Et vous allez voir que ça vaut le coup de se faire chier un peu, car on va commencer à pouvoir profiter pleinement de notre fichier JSON.

Mais le problème rencontré à la création d’un simple tableau est double. Il va maintenant falloir séparer chaque entité du tableau, mais également séparer la clé de sa valeur. Pour cela j’ai utilisé une solution toute simple : J’ai pris un second caractère spécial : þ . Ainsi dans le script que PHP doit me retourner, un tableau va ressembler à ça :

Et Bash s’occupera de découper cela.
Mais il y a un autre soucis : Comment faire savoir à PHP que l’on souhaite récupérer un tableau associatif, et non pas un tableau indexé ?
Et bien nous allons encore une fois utiliser un argument, que l’on nommera ‘assoc’. Si celui-ci est passé à 1, alors PHP devra retourner un tableau associatif, sinon un tableau indexé.

Vous y êtes ? C’est parti !

Encore une fois j’ai mis les lignes ajoutées en évidence.
En gros si l’argument ‘assoc’ est passé à 1, on insère dans la chaîne de retour la clé et la valeur, avec les séparateurs choisis. Sinon, on n’insère que la valeur.

Passons au Bash à présent, ça risque d’être un peu long…

Pratiquement tout a été modifié depuis l’étape plus haut.
J’ai modifié la variable $apps  en en $tmp , car ce tableau n’est maintenant que temporaire : Une fois le script PHP exécuté, c’est un tableau contenant les clés et les valeurs sous cette forme :

Remarquez qu’on déclare ensuite le tableau $disks . Ce n’est pas une faute de frappe, j’ai volontairement mis un A majuscule, à l’instar de $tmp . Et oui, un tableau associatif sous Bash se déclare de telle façon :

Puis on parcourt les disques (la variable $tmp ), dont on va extraire la clé et la valeur.
Pour cela, on utilise encore la méthode du read <<< , avec un autre séparateur, le second (ici þ ). La clé et la valeur sont donc stockées dans un autre tableau temporaire, $arr , puis on stocke ces 2 informations dans notre tableau associatif $disks .
Vous suivez encore ?

Une fois la boucle for disk in ...  terminée, le tableau $disks  est rempli. Nous pouvons donc le parcourir afin d’y afficher son contenu. C’est ce que fait la boucle for K ...  qui suit.

Pour info, le ‘!’ dans l’instruction ${!disks[@]  signifie que l’on souhaite regarder dans les clés du tableau associatif. Le « @ » est utilisé pour récupérer toutes les données.
On peut donc traduire vulgairement :

Par :

« Pour toutes les clés appelées K trouvées dans le tableau $disks »

Mais… À ce stade, il est impossible d’afficher un seul élément du tableau, ni la clé ni sa valeur (à moins de connaître la clé). Si vous pensiez pouvoir faire ceci :

Et bien ça ne fonctionne pas. En fait, il n’y a pas moyen de récupérer la clé d’un tableau associatif en Bash.
C’est pour cela que le code suivant est fait :

On va de nouveau créer un tableau indexé $keys , et on va récupérer dans celui-ci toutes les clés de notre tableau $disks .
À partir de ce moment là, on pourra récupérer la clé et la valeur de chaque élément indépendamment :

Et le résultat de ce script :

On dit merci qui ?

En bonus

Comme je suis un mec bien, je vais vous donner une autre petite astuce sur les tableaux : Il est possible de connaître le nombre d’éléments que possède un tableau :

Enjoy !