Imbrication scripts sh et gnuplot : produire des graphiques en ligne de commande _ partie 1

Auteurice : Elsa Van Kote

Temps de lecture : ~7 minutes


Point de départ

Trois scripts de départ différents

À la séance précédante, j’avais pu créer trois scripts sh différents exécutant chacun une fonction particulière :

Dans l’exemple ci-dessous, on compte le nombre de cellule de la colonne 8 où le contenu est supérieur ou égal à 1.

#! /bin/sh

TSV=$1
COLUMN=$2

cut $TSV -f$COLUMN | grep -E '^[1-9]+' | wc -l

Exemple : ./tot stats.tsv 8 renvoie 44.

#! /bin/sh

NOMBRE=$1
TOTAL=$2

echo $(($1*100/$2))

Exemple : ./prct 44 241 renvoie 18.

set style data histogram
set boxwidth 0.5 relative
set style fill solid
set yrange [1:300]
set terminal png size 1320,1240
set output "histogram.png"
plot '../ressources/sortie.data' u 2:xtic(1) with boxes title 'Nombre de categories', "" using 0:($2+10):2 with labels font "WingDings,12" notitle

Exemple : gnuplot religion.p crée un fichier png sur son ordinateur.

Combinaison des trois scripts

Dans mon esprit, je souhaiterais avoir un script unique qui puisse créer un histogramme en entrant deux paramètres : le fichier tsv + les colonnes du fichier qui nous intéressent pour calculer un pourcentage.

En résumé, quelque chose comme ça : ./plot_prct fichier.tsv 6,7,10.

Procédons alors étape par étape.

Imbrication de scripts sh

Tout d’abord, il faut savoir qu’il est possible de créer des scripts sh s’appelant entre eux. On fait appel pour cela à des captures de commandes. Pour cela il s’agit de mettre un $ suivi de parenthèse dans lequel va s’executer un premier script : ./prct $(./tot stats.tsv 8) 241.

l’outil xargs et ses arguments

Contraiement au pipe, qui ne fait ‘que’ reprendre le stdout d’une commande pour la donner comme stdin à une autre commande, xargs vient récupérer le stdout pour en faire un argument d’une seconde commande.

Exemple : ls | echo ne renvoie rien. En effet, la sortie induite par ls, c’est à dire la visualisation du nombre d’éléments présents dans mon dossier, n’est pas réutilisé comme argument par echo, puisque ce dernier à besoin d’une argument à sa suite pour fonctionner. Les deux informations restent séparées.

En revanche, dans ls | xargs echo, xargs envoie comme argument à echo le résultat de ls. Dans mon cas la sortie représente donc les différents éléments présents dans mon dossier sur une seule ligne : Inkscape-091e20e-x86_64.AppImage org.inkscape.Inkscape.desktop squashfs-root.

Pour n’executer qu’un seul processus par argument dans les paramètres de xargs, on peut ajouter -n1.

Exemple : echo "1 2 3 4 5 6 7 8 9"|xargs -n 4 renvoie :

1 2 3 4
5 6 7 8
9

On voit bien que xargs a regroupé les résultats des processus par 4.

Avec tout cela, il est possible maintenant de jouer avec les stdout et les arguments créés. Si nous prenons par exemple cette commande : echo 44 | xargs -I{} -n1 ./prct {} 241, on oobtient comme résultat 18%. Le stdout de la première commande echo 44 a donné 44, qui a été repris comme argument par le script ./prct qui permet de calculer un pourcentage (voir le script plus haut). L’argument est appelé au niveau des doubles accolades, grâce à l’option visible après xargs -I{}.

Une autre option de xargs permet des manipulations d’arguments plus complexe : sh -c qui prend la chaine de caractère après -c et l’exécute dans un sub-shell. Nous verrons son développement un peu plus bas.

Objectif en vue

Notre volonté est donc d’avoir à la fin un script unique sous cette forme : ./tabl 8,9,10 tableau_pourcentages.tsv. Ce dernier permetrait de créer un tableau de pourecentages (appelé ici tableau_pourcentages.tsv) en fonction de sommes présentes dans des colonnes souhaitées (ici 8,9 et 10) appelées sous forme de pourcentages.

Voici à quoi devrait ressembler notre tableau :

Catégorie Pourcentage
Religion 44
Grande Court 12
Tierce Intervention 25

Le nombre de ligne dépendant du nombre de colonnes que l’on interroge.

Un tableau étant composé de lignes, il a donc fallu créer un script qui crée d’abord les lignes. Voici le script ./ligne auquel nous sommes parvenus :

#! /bin/sh

TSV=$1
COLUMN=$2

./tot $1 $2 | xargs sh -c './prct $1 241' -- > out
cut $1 -f$2 | head -n1 | paste - out

Expliquons plus en détails :

(on appelle le script tot avec deux arguments : le fichier tsv exploité ($1) puis la colonne en quesion ($2) on donne le résultat à xargs qui le renvoie comme premier argument du script prct le résultat est stocké dans un fichier out)

./tot $1 $2 | xargs sh -c './prct $1 241' --> out

(on récupère uniquement la colonne $1 du fichier $2 on récupère la première ligne avec head qui est en fait l’intitulé de la colonne on colle avec paste la sortie stdout avec le fichier out)

cut $1 -f$2 | head -n1 | paste - out

Exemple :

Si l’on execute cette commande ./ligne stats2.tsv 8 on obtient une ligne Religion 18%.

Or, ce que nous voulons au final est un tableau ! Et donc une succession de lignes.

Pour cela nous avons créé un script méta ui appelle ./ligne et intitulé tabl. Voici son contenu cei dessous :

#! /bin/sh

TSV=$1
COL=$2

IFS=,

for col in $2
do ./ligne $1 $col
done

On utilise une boucle for pour boucler sur les colonnes. ATTENTION le délimiteur par défaut de sh est un espace, tandis que mes colonnes sont déparées par des virgules. Il faut donc modifier le séparateur, ce que l’on peut voir dans le script juste en haut où l’on instancie IFS=,.

C’est à dire que pour chaque colonne dans les plusieurs colonnes données en arguments, le script doit s’executer. Par exemple pour la commande ./tabl stats2.tsv 8,7, le script execute ces commandes :

./ligne stats2.tsv 8 et ./ligne stats2.tsv 7

On enregistre le résultat obtenu en ajoutant une redirection > comme ceci : ./tabl stats2.tsv 8,7 > tableau_pourcentages.tsv.

Et voici le résultat pour notre tableau :

Représentant 9
Tierce-intervention 94
Religion 18
Grande Chambre 52

scripts finaux

Voici à quoi resessemblent finalement l’ensemble des scripts utilisés :

tot

#! /bin/sh

TSV=$1
COLUMN=$2

cut $TSV -f$COLUMN | grep -E '^[1-9]+' | wc -l'|

prct

#! /bin/sh

NOMBRE=$1
TOTAL=$2

echo $(($1*100/$2))'%'

ligne

#! /bin/sh

TSV=$1
COLUMN=$2

./tot $1 $2 | xargs sh -c './prct $1 241' -- > out
cut stats2.tsv -f$2 | head -n1 | paste - out

Prochaine et dernière étape

gnuplot est un outil puissant pour créer ses propres diagrammes. J’ai souhaité m’y essayer en créant tout d’abord un scrit avec les infos données en dur dedans, puis en externalisant les variables.

Ainsi, si on reprend le script histogram.p présenté plus haut, nous avions mis en dur l’appel au fichier tsv duquel extraire nos données (visible à la ligne 7, ../ressources/histogram.p). Le but est de pouvoir externaliser cette donnée pour qu’elle devienne un argument de la commande.

Pour faire cela, nous avons changé la ligne 7 par ceci : plot ARG1 u 2:xtic(1) with boxes title 'Nombre de categories', "" using 0:($2+10):2 with labels font "WingDings,12" notitle, ainsi le tableau de données est remplacé par un argument et peut-être appelé au moment de la commande gnuplot, comme ci-dessous, en prenant soin de ne pas oublier l’option -c qui est essentielle :

gnuplot -c histogram.p '../ressources/sortie.data' ce qui rend plus générale le script. Cependant, puisque les graph produits restent très liés aux données utilisées, on peut se demander si une telle externalisation est utile. En effet, le script histogram.p actuel produit un graphique avec le titre “Nombre de catégories”, alors que nous ne savons pas si nous souhaiterions avoir le même titre pour un jeu de données différent. Cela reste donc à déterminer.