Le site arthur.bebou.netlib.re - retour accueil
git clone git://bebou.netlib.re/arthur.bebou
Log | Files | Refs |
commit 9c69831a90f26c2dc3d4c5f1a6c49645cbf7f343 parent bef700cc8b6491e15219cb819f293cdecb9f0d61 Auterice: Arthur Pons <arthur.pons@unistra.fr> Date: Wed, 11 Sep 2024 14:17:41 +0200 Ajout article tsv2anything Diffstat:
A | contents/tsv2anything/index.sh | | | 342 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 342 insertions(+), 0 deletions(-)
diff --git a/contents/tsv2anything/index.sh b/contents/tsv2anything/index.sh @@ -0,0 +1,342 @@ +#! page +title: tsv2anything +author: Arthur Pons +description: Ou comment créer des systèmes de templating rudimentaires dans des systèmes POSIX +publication: 2024-09-11 + +sectionmd: main + +**article non relu** + +## Le Besoin + +Il m'arrive relativement fréquemment de devoir prendre un [TSV](/amourtsv/) et +de l'afficher dans un format arbitraire. Par exemple nous pouvons avoir le TSV +suivant : + + Index First Name Last Name Email Date of birth Job Title + 1 Shelby Terrell elijah57@example.net 1945-10-26 Games developer + 2 Phillip Summers bethany14@example.com 1910-03-24 Phytotherapist + 3 Kristine Travis bthompson@example.com 1992-07-02 Homeopath + 4 Yesenia Martinez kaitlinkaiser@example.com 2017-08-03 Market researcher + 5 Lori Todd buchananmanuel@example.net 1938-12-01 Veterinary surgeon + 6 Erin Day tconner@example.org 2015-10-28 Waste management officer + 7 Katherine Buck conniecowan@example.com 1989-01-22 Intelligence analyst + 8 Ricardo Hinton wyattbishop@example.com 1924-03-26 Hydrogeologist + 9 Dave Farrell nmccann@example.net 2018-10-06 Lawyer + 10 Isaiah Downs virginiaterrell@example.org 1964-09-20 Engineer, site + +et nous voulons afficher chaque personne sous le format : + + [id] | First Name Last Name - Job Title + Date of birth + (Email) + ------------------ + +## Implémentation + +J'imagine qu'il y a mille façons de faire cela. Je privilégie les +implémentations peu verbeuses, POSIX et faisant appel aux abstractions du shell +quand cela est possible. Cela écarte de nombreuses solutions possiblement plus +élégantes et/ou (beaucoup) plus performantes au profit d'une rapidité +d'implémentation et d'une certaine homogénéité d'environnement pour peu que l'on +ait l'habitude d'évoluer dans du shell. + +### Avec printf + +`printf` est en soit une commande qui permet de mettre des données dans un +certain format. En lui donnant à manger notre tableau et en inscrivant notre +template en argument de printf on peut recréer très rapidement un système de +template. + +L'argument correspondant pour printf serait : + + %s | %s %s - %s + %s + (%s) + ------------ + +Il y a quelques temps j'aurais naïvement écrit quelque chose sous la forme + + < in.tsv tail -n+2 | #retirer le header + tr ' ' '\n' | #mettre un élément par ligne + xargs -d'\n' -n6 printf '%s | %s %s - %s\n%s\n(%s)\n-----------' #donner exactement 6 arguments à printf à chaque fois + +ce qui aurait exécuté : + + printf %s | %s %s - %s\n%s\n(%s) 1 Shelby Terrell elijah57@example.net 1945-10-26 Games developer + printf %s | %s %s - %s\n%s\n(%s) 2 Phillip Summers bethany14@example.com 1910-03-24 Phytotherapist + printf %s | %s %s - %s\n%s\n(%s) 3 Kristine Travis bthompson@example.com 1992-07-02 Homeopath + printf %s | %s %s - %s\n%s\n(%s) 4 Yesenia Martinez kaitlinkaiser@example.com 2017-08-03 Market researcher + [...] + +Sauf que `printf` est malin. S'il reçoit plus de paramètres que d'endroits où +les placer dans le template il boucle sur les paramètres suivant. On peut donc +faire un unique appel à printf avec tous les arguments et en plaçant un +judicieux retour à la ligne à la fin du template : + + < in.tsv tail -n+2 | #retirer le header + tr ' ' '\n' | #mettre un élément par ligne + xargs -d'\n' printf '%s | %s %s - %s\n%s\n(%s)\n-----------\n' #donner tous les arguments d'un coup + + + +ce qui exécutera cette commande de la mort : + + printf '%s | %s %s - %s\n%s\n(%s)\n-----------\n' 1 Shelby Terrell elijah57@example.net 1945-10-26 Games developer 2 Phillip Summers bethany14@example.com 1910-03-24 Phytotherapist 3 Kristine Travis bthompson@example.com 1992-07-02 Homeopath 4 Yesenia Martinez kaitlinkaiser@example.com 2017-08-03 Market researcher 5 Lori Todd buchananmanuel@example.net 1938-12-01 Veterinary surgeon 6 Erin Day tconner@example.org 2015-10-28 Waste management officer 7 Katherine Buck conniecowan@example.com 1989-01-22 Intelligence analyst 8 Ricardo Hinton wyattbishop@example.com 1924-03-26 Hydrogeologist 9 Dave Farrell nmccann@example.net 2018-10-06 Lawyer 10 Isaiah Downs virginiaterrell@example.org 1964-09-20 Engineer, site + +qui donnera : + + 1 | Shelby Terrell - elijah57@example.net + 1945-10-26 + (Games developer) + ----------- + 2 | Phillip Summers - bethany14@example.com + 1910-03-24 + (Phytotherapist) + ----------- + 3 | Kristine Travis - bthompson@example.com + 1992-07-02 + (Homeopath) + ----------- + [...] + +Ah mais l'adresse et le métier on été interchangés ! Normal, l'adresse vient +avant le métier dans le tableau et printf ne nous donne pas moyen de "nommer" +spécifiquement une colonne dans template. Si dans le template la seconde colonne +doit être imprimée avant la première il faut qu'elles apparaissent dans cet +ordre là dans la source. Autrement dit il faut réarranger la source des données +dans l'ordre d'apparition voulue dans le template. Pour notre cas cela pourrait +être : + + $ < in.tsv awk -F'\t' -vOFS='\t' '{print $1,$2,$3,$6,$5,$4}' | + head -n2 + Index First Name Last Name Job Title Date of birth Email + 1 Shelby Terrell Games developer 1945-10-26 elijah57@example.net + +Donc si l'on met tout bout à bout : + + $ < in.tsv awk -F'\t' -vOFS='\t' '{print $1,$2,$3,$6,$5,$4}' | + tail -n+2 | #retirer le header + tr ' ' '\n' | #mettre un élément par ligne + xargs -d'\n' printf '%s | %s %s - %s\n%s\n(%s)\n-----------\n' #donner tous les arguments d'un coup + + 1 | Shelby Terrell - Games developer + 1945-10-26 + (elijah57@example.net) + ----------- + 2 | Phillip Summers - Phytotherapist + 1910-03-24 + (bethany14@example.com) + ----------- + 3 | Kristine Travis - Homeopath + 1992-07-02 + (bthompson@example.com) + ----------- + 4 | Yesenia Martinez - Market researcher + 2017-08-03 + (kaitlinkaiser@example.com) + ----------- + [...] + +On peut déplier le format printf pour plus de clarté : + + xargs -d'\n' printf ' + %s | %s %s - %s + %s + (%s) + -----------' + +#### Avantages + + * Relativement rapide + +Prenons notre fichier de test de 10 lignes et dupliquons le de façon à avoir un +fichier de 1 100 000 lignes. L'impression des environs 4 millions de ligne +prend autour de 6/7 secondes sur ma machine, à vous de décider si c'est +raisonnable ou pas. On doit évidemment pouvoir faire beauuuuucoup plus rapide +avec un programme compilé et un peu optimisé. + + * Permet de profiter des fonctionnalités de formatage de printf + +`printf` inclu de nombreuses possibilités de formatage des données. Par +exemple, si l'on ne veut pas que le métier dépasse 10 caractères de long : + + %s | %s %s - %.5s + %s + (%s) + ----------- + + * Peu de ligne de code, possibilité de le réécrire de zéro très rapidement en + l'adaptant au besoin + +Une remarque ici est que passé une certaine quantité d'arguments il faut +préciser à xargs un nombre max par appel à printf. Si on ne le fait pas il se +pourrait que l'on rencontre la limite du nombre d'argument de votre système et +que la totalité du tableau ne soit pas imprimées. [Cet excellent +article](https://www.in-ulm.de/~mascheck/various/argmax/) évoque le sujet en +détail. En l'occurence `xargs --show-limits` permet de savoir ce que l'on +peut mettre derrière `-n` mais pour être safe côté portabilité on peut utiliser +le plus grand multiple du nombre d'éléments à afficher dans le template +inférieur à 4096. + +#### Inconvénients + + * Pas de séparation template/code + +En l'état le template vit dans le code. Il faut donc dupliquer le script en autant de temaplte que l'on a. On devrait pouvoir faire un peu de méta-programmation pour contourner cette contrainte mais je ne l'ai pas fait. + + * Nécessite un prétraitement + +Tout se passe parfaitement bien si toutes les données sont dans l'ordre mais il +est obligatoire d'avoir un léger prétraitement pour réordonner le tableau dans +l'ordre du template. + + * Le template ne comporte pas de variables nommées + +Il faut jeter un oeil aux données (et possiblement au prétraitement) pour se +souvenir de quoi va où. Peut mieux faire en terme de maintenabilité. + +### Variables shell + here-doc "à la francium" + +Si on instancie les variables shell + + name="Jean" + dob="1994-06-09" + job="mailman" + +On peut ensuite les appeler dans un here-doc + + <<delim cat + Mister $name, born on $dob, is a $job + delim + +Le here-doc peut vivre dans un fichier séparé, nommé `layout` par exemple. +L'astuce est donc d'avoir un peu de code qui pour chaque ligne de notre TSV +instancie les variables et appel le here-doc. Le nom de la variable contenant +les valeurs de la première colonne sera la valeur se trouvant dans l'en-tête du +tableau de la première colonne. Le code suivant en est un exemple, il prendra +dans stdin le tableau et en argument le fichier contenant le here-doc : + + tmpd=$(mktemp -d) + tee $tmpd/all | + head -n1 | + tr ' ' '\n' > $tmpd/vars + tail -n+2 $tmpd/all | + while read line;do + eval $(echo "$line" | tr ' ' '\n' | + paste -d '=' $tmpd/vars - | + sed -E "s/=/&'/;s/$/'/") + . "$1" + done + rm -rf $tmpd + +En reprenant notre exmple de printf le layout ressemblera à : + + <<delim cat + $id | $firstname $lastname - $job + $dob + ($email) + -----------' + delim + +Les variables ne pouvant pas contenir d'espace il faudra renommer les entêtes : + + id firstname lastname email dob job + 1 Terrell elijah57@example.net 1945-10-26 Games developer + 2 Phillip Summers bethany14@example.com 1910-03-24 Phytotherapist + [...] + +Il reste a appeler notre script (nommé `printlayout` pour l'occasion) avec le fichier de notre here-doc en argument : + + $ < in.tsv ./printlayout ./layout + + 1 | Terrell - Games developer + 1945-10-26 + (elijah57@example.net) + -----------' + 2 | Phillip Summers - Phytotherapist + 1910-03-24 + (bethany14@example.com) + -----------' + 3 | Kristine Travis - Homeopath + 1992-07-02 + (bthompson@example.com) + -----------' + 4 | Yesenia Martinez - Market researcher + 2017-08-03 + (kaitlinkaiser@example.com) + -----------' + [...] + +#### Avantages + + * Le template est explicite avec des noms de variables aussi claires que vous + ne pouvez les nommer + +Cela implique également que le prétraitement n'est pas nécessaire pour +réordonner le tableau. L'ordre des des données et du template peut être +décorrélé. Le template est également plus facile à maintenir. + + * N'importe quel traitement shell peut être inclus dans le here-doc + +Il est possible de mettre du shell plus avancé dans le template. Par exemple, +en utilisant l'interpolation des variables shell, mettre une valeur par défaut +si elle n'existe pas dans les données ou terminer l'affichage avec un erreur +s'il manque une donnée cruciale : + + <<delim cat + ${id:?missing id, something is wrong} | $firstname $lastname - ${job:-no known job} + ${dob:-date of birth unknown} + ($email) + -----------' + delim + +Il est également possible de mettre des conditions pour que le template diffère +légèrement basé sur la valeur d'une variable par exemple : + + <<delim cat + $([ "$firstname" = "Alice" ] \ + && $id | $firstname $lastname - $job \ + || $id | $firstname (wow what a pretty name) $lastename - $job + ) + ${dob:-date of birth unknown} + ($email) + -----------' + delim + +Qui donnera sur les lignes avec Alice comme prénom : + + 1 | Alice (wow what a pretty name) Terrell - Games developer + 1945-10-26 + (elijah57@example.net) + -----------' + 2 | Phillip Summers - Phytotherapist + 1910-03-24 + (bethany14@example.com) + -----------' + +Si cela est vraiment un avantage pourrait être sujet à débat. Il pourrait être +considéré comme de mauvais goût d'ajouter de l'intelligence dans le template +directement. A vous de vous faire votre avis. + +#### Inconvénients + + * C'est lent + +Cette technique prend entre trois et quatre secondes pour formater 1000 lignes +du tableau. Elle n'est pas appropriée pour de grands jeux de données. Pour générer +des petites listes pour un site web c'est tout à fait correct et déjà utilisé +en production sur plusieurs sites. + +### D'autres + +On peut imaginer pleins d'autres implémentations et je vais tenter de les +renseigner ici. Une version assez simpliste contenu dans un binaire C avec un +format de templating raisonnable pourrait être le meilleur des deux mondes bien +que moins simple à recoder/modifier à la volée. + +Je suis également persuadé que l'on pourrait faire quelque chose de bien plus +malin et efficace entre perl/awk mais je n'ai pas l'énergie d'y réfléchir à +l'instant. +