Comment configurer et utiliser efficacement l’historique bash

Dans cet article, nous allons voir comment améliorer notre productivité en s’intéressant à l’historique bash. L’historique permet de conserver les dernières commandes tapées dans un shell bash. C’est très utile pour retrouver certaines commandes oubliées, éviter de devoir les ré-écrire ou regarder rapidement ce qu’un autre administrateur a fait sur le système.

Dans un premier temps, nous allons voir qu’il est possible de configurer cet historique, c’est-à-dire ce qui s’affiche lorsqu’on tape la commande history. Dans un second temps, nous allons voir comment utiliser cet historique de manière efficace.

Continuer la lecture

Gérer le presse-papier en ligne de commande sous Linux avec xclip

Dans cet article, nous allons voir comment gérer le presse-papier en ligne de commande sous Linux grâce au programme xclip. Cela nous permettra de réaliser des copier/coller en ligne de commande ou via des scripts bash. Par exemple, nous pouvons remplir le presse-papier à l’aide d’un script de bash de telle sorte que la fonction de collage renvoi le texte issue du script.

Cela peut s’avérer utile dans une démarche d’optimisation des actions quotidiennes, cela peut faire gagner énormément de temps.

Continuer la lecture

Manipuler facilement du JSON en ligne de commande avec Jq

Dans cet article, nous allons parler d’un outil que j’ai découvert récemment : jq. Cet outil permet de manipuler des données JSON facilement en ligne de commande ou dans des scripts SHELL.

L’outil est très léger, n’a pas de dépendance et permet de faire des choses assez puissante.

Avant d’avoir découvert cet outil, j’utilisais principalement les commandes sed et grep pour extraire des données formatées en JSON. Évidemment, ce n’est pas très fiable et les commandes générées peuvent vite devenir complexes. Depuis, j’utilise Jq pour extraire les données ou les reformer dans un format JSON différent que celui d’origine.

Présentation de Jq

Sur la page de présentation de l’outil, Jq est présenté comme l’équivalent de sed pour les données Json. Cette description est assez juste, l’outil permet de filtrer, découper, transformer et grouper des données avec la même simplicité que des outils comme sed, awk et grep.

Pour être plus précis, Jq est un programme de type filtre.

Il accepte une entrée et produit une sortie. Pour produire cette sortie, il y a de nombreux outils et filtres mis à disposition par l’outil, pour extraire/manipuler des données. Par un exemple extraire un champ particulier d’un objet Json.

Les filtres peuvent être combinés de différentes manières, il est possible de de créer des pipes (tube) : la sortie d’un filtre peut être passé à l’entrée d’un autre filtre. C’est le même fonctionnement que les pipes sur Linux qui permet de chaîner les commandes avec la barre verticale |.

Installation de Jq

L’installation est très simple, le paquet étant dans les dépôts de la plupart des distributions Linux.

yum install epel-release
yum install jq
Installation de Jq

Pour une installation manuelle, avec la compilation, ce n’est pas très compliqué. Jq n’ayant pas de dépendance, c’est du code C portable. Il faut installer Make et GCC pour procéder à la compilation.

yum install make gcc
wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-1.5.tar.gz
tar -xvf jq-1.5.tar.gz
cd jq-1.5/
./configure
make
make install
Compilation et installation de Jq

Extraire des données

Entrons dans le vif du sujet, voyons quelques exemples d’utilisation de l’outil. Je vais travailler avec l’API de cPanel qui peut renvoyer des données JSON.

Pour ce premier exemple, je vais utiliser l’API2 de cPanel et la fonction qui permet de lister les comptes de messageries présent sur un compte d’hébergement. La documentation se trouve ici.

Si je lance la commande, sans Jq, voici la sortie produite :

cpapi2 --output=json --user=mondev Email listpops
{"cpanelresult":{"func":"listpops","event":{"result":1},"preevent":{"result":1},"data":[{"suspended_login":0,"suspended_incoming":0,"email":"test3@o2-dev.info","login":"test3@o2-dev.info"},{"suspended_incoming":0,"suspended_login":0,"login":"test1@mon-dev.xyz","email":"test1@mon-dev.xyz"},{"suspended_incoming":0,"suspended_login":0,"login":"test4@mon-dev.xyz","email":"test4@mon-dev.xyz"},{"suspended_login":0,"suspended_incoming":0,"email":"test2@o2-dev.eu","login":"test2@o2-dev.eu"},{"login":"Main Account","email":"mondev","suspended_incoming":0,"suspended_login":0}],"postevent":{"result":1},"module":"Email","apiversion":2}}
Affichage des comptes emails d'un compte d'hébergement avec l'api cPanel

On constate que cela renvoi des données json mais ce n’est pas formaté, l’affichage n’est pas lisible. Cela nous donne occasion de voir le premier filtre Jq, qui ne fait rien de particulier sauf mettre en forme le Json et lui appliquer quelques couleurs.

cpapi2 --output=json --user=mondev Email listpops | jq .
Notre premier filtre Jq

Voici le retour qui s’affiche, pour cette fois, je mets cela en capture d’écran pour que vous puissiez voir l’indentation et les couleurs supplémentaires qui s’affichent.

Premier filtre Jq pour avoir un Json indenté correctement

Premier filtre Jq pour avoir un Json indenté correctement

L’opérateur . est l’opérateur le plus simple qui existe, il retourne les mêmes données qu’à son entrée. Cependant par défaut, Jq indente cela correctement et ajoute des couleurs. C’est l’opérateur d’identité.

Un autre opérateur est celui qui permet de sélectionner une clé particulière dans un objet Json.

C’est l’opérateur .nomDeMaclée, c’est possible d’en coupler plusieurs également, par exemple .nomDeClée.deuxiemeClé.

En prenant l’exemple précédent, si je souhaite juste conserver la partie intéressante, c’est-à-dire cpanelresult->data, je peux utiliser le filtre suivant :

cpapi2 --output=json --user=mondev Email listpops | jq '.cpanelresult.data'
Filtre permettant d'extraire certaines valeurs de l'objet json

Avec cette commande, j’obtiens le résultat suivant :

[
  {
    "email": "test3@o2-dev.info",
    "login": "test3@o2-dev.info",
    "suspended_login": 0,
    "suspended_incoming": 0
  },
  {
    "suspended_incoming": 0,
    "suspended_login": 0,
    "login": "test2@o2-dev.eu",
    "email": "test2@o2-dev.eu"
  },
  {
    "suspended_incoming": 0,
    "suspended_login": 0,
    "login": "test1@mon-dev.xyz",
    "email": "test1@mon-dev.xyz"
  },
  {
    "suspended_incoming": 0,
    "suspended_login": 0,
    "login": "test4@mon-dev.xyz",
    "email": "test4@mon-dev.xyz"
  },
  {
    "email": "mondev",
    "login": "Main Account",
    "suspended_login": 0,
    "suspended_incoming": 0
  }
]
Résultat de la commande précédente

Il est recommandé d’entourer l’expression Jq par des guillemets simple. En effet de nombreux caractères spéciaux utilisés par Jq sont également des caractères ayant une signification particulière sur les shells comme bash. Le fait de mettre des guillemets simple empêche l’interprétation de ces caractères par le shell.

Dans l’exemple précédent, nous avons donc récupéré un tableau, nous pouvons récupérer une valeur spécifique de ce tableau à l’aide des crochets [index]. Par exemple, cela donne l’expression suivante .cpanelresult.data[0].

cpapi2 --output=json --user=mondev Email listpops | jq '.cpanelresult.data[0]'
# Récupérer un élément spécifique
cpapi2 --output=json --user=mondev Email listpops | jq '.cpanelresult.data[0].email'
Sélection d'un élément dans un tableau

Cela nous permet de récupérer le premier élément du tableau, dans la deuxième commande, nous voyons qu’il est possible de descendre encore plus pour récupérer un élément spécifique :

// Sortie de la première commande
{
  "suspended_incoming": 0,
  "email": "test2@o2-dev.eu",
  "login": "test2@o2-dev.eu",
  "suspended_login": 0
}
// Sortie de la deuxième commande
"test1@mon-dev.xyz"
Sortie des commandes précédentes

Transformations

Il est également possible de transformer les données, c’est-à-dire réarranger les éléments Json de l’objet de base pour changer la manière dont il est organisé.

Si on reprend l’exemple précédent, on constate qu’on récupère pas mal d’informations pas très utiles. Par exemple, on ne voudrait que la liste des comptes emails et oublier le reste. C’est possible de faire cela avec Jq, avec trois nouvelles syntaxes :

  • Le chaînage avec le pipe |. Permet d’itérer sur un tableau.
  • Le constructeur d’objet {}. Permet de créer un nouvel objet Json et de réarranger les données comme on le souhaite.
  • Le constructeur de tableau []. Permet de retourner une réponse sous forme d’un tableau.
# Version 1
cpapi2 --output=json --user=mondev Email listpops | jq '.cpanelresult.data[] | {email: .email}'
# Version 2
cpapi2 --output=json --user=mondev Email listpops | jq '[.cpanelresult.data[] | .email]'

La version 1 permet de renvoyer plusieurs éléments sous la forme d’une clé : valeur. Un nouvel objet est construit avec le {}.

Le pipe permet de faire une boucle sur tous les éléments du tableau. Par conséquent, .email contient la valeur courante de l’itération en cours. Cela serait l’équivalent de .cpanelresult.data[i].email, i étant le compteur de l’itération.

A noter que le caractère | est bien entre les guillemets simple, c’est un | interne à Jq, ce n’est pas le même | que celui permettant de chaîner des commandes Linux (même si le fonctionnement est similaire).

La version 2 est différentes, elle est plus courte et retourne un tableau grâce à l’utilisation des crochets []. Cette fois-ci, on récupère juste une liste d’adresses emails, sans la clé ‘email‘ contrairement à la version 1.

// Résultat de la commande 1
{
  "email": "test3@o2-dev.info"
}
{
  "email": "test1@mon-dev.xyz"
}
{
  "email": "test4@mon-dev.xyz"
}
{
  "email": "test2@o2-dev.eu"
}
{
  "email": "mondev"
}
// Résultat de la commande 2
[
  "test2@o2-dev.eu",
  "test3@o2-dev.info",
  "test1@mon-dev.xyz",
  "test4@mon-dev.xyz",
  "mondev"
]
Résultat des commandes Jq précédentes

Boucler sur un tableau JSON en Bash avec Jq

C’était l’objet principal de cet article avant que je décide de faire une introduction plus complète à Jq. Pour finir, je vous présente une petite astuce : comment boucler sur un tableau Json, en Bash, à l’aide de Jq.

C’est pratique lorsque vous avez besoin de faire des traitements avec des commandes à chaque itération. Si vous n’avez pas besoin de faire de traitement, autant partir sur l’utilisation du pipe Jq.

Imaginons qu’on ai besoin de faire un traitement particulier sur chaque adresse email : envoyer un message. Il est possible de faire cela à l’aide d’un script bash et d’une boucle.

#!/bin/bash
# Example for : https://blog.madrzejewski.com/traiter-json-shell-cli
set -euo pipefail
LC_ALL=C
OLD_IFS=$IFS
IFS=$'\n'
cpapi2 --output=json --user=mondev Email listpops | jq -c '.cpanelresult.data[] | {email: .email}' | while read json ; do
	email=$(echo $json | jq -r .email)
	echo "Envoi à $email"
	echo "Contenu de mon message" | mail -s "Sujet" $email
done

Décortiquons un peu ce code, il y a plusieurs choses intéressantes :

  • On change l’IFS (internal field separator) par \n. Autrement dit, cela nous permet d’itérer sur les sauts de lignes. Par défaut, les espaces sont également pris en compte, ce qui est problématique. On stocke l’ancienne valeur dans OLD_IFS, c’est toujours une bonne pratique de faire cela.
  • Ensuite, on lance la commande vu précédemment. J’ai fait exprès de prendre la version qui ne retourne pas un tableau de valeur, cela est légèrement plus complexe car le résultat est sur plusieurs lignes par défaut. L’option -c est ajouté à Jq, ce qui permet d’avoir une valeur du tableau sur une seule ligne. C’est là que se trouve la subtilité, on peut itérer la dessus pour parcourir toutes les valeurs du tableau ou de l’objet.
  • Ensuite, je récupère la valeur brute de l’email sans la clé avec le filtre .email. Par défaut, Jq retourne la valeur avec des guillemets autour. Pour éviter de faire un cut on peut ajouter l’option -r. (qui signifie raw et enlève les guillemets)
  • Enfin, je fais mon traitement sur la valeur concernée, ici l’envoi d’un message.

Conditions

Avec Jq, il est également possible de créer des conditions, par exemple, afficher un objet que si une certaine condition est respectée.

Pour illustrer cela, je viens de suspendre un compte email. Par conséquent suspended_login et suspended_incoming ont une valeur à 1 pour une adresse email. Les autres adresses ont toujours des valeurs à 0. Si je veux récupérer cette adresse en particulier, je peux utiliser deux options de Jq :

  • select : qui permet de récupérer un élément si la condition qui lui est passé est validé
  • une condition : qui permet de tester une valeur de l’objet json par exemple
cpapi2 --output=json --user=mondev Email listpops | jq '.cpanelresult.data[] | select(.suspended_login == 1) '
On récupère que les comptes emails suspendus

Dans cette commande, j’itère sur la liste des comptes emails retournés par l’api cPanel grâce à l’utilisation du pipe à l’intérieur de Jq (celui entre guillemet donc). Ensuite, j’utilise select qui permet de retourner l’objet si la condition qui lui est passé est vraie. J’obtiens le résultat suivant :

{
  "suspended_login": 1,
  "suspended_incoming": 1,
  "email": "test1@mon-dev.xyz",
  "login": "test1@mon-dev.xyz"
}
Résultat de la commande précédente

Conclusion

Ce n’est qu’une présentation rapide de l’outil. Jq est bien plus puissant que les exemples présentés dans cet article. C’est un outil à garder sous le coude pour éviter de faire des commandes monstrueuse avec sed, grep et awk.

Pour approfondir le sujet, je vous recommande fortement de lire l’article de Stéphane Bortzmeyer : il évoque tout un aspect statistique que je n’ai pas utilisé.

La page de manuel de l’outil qui est très complète et bien faites également.