Journal choose, pour des scripts shells interactifs

Posté par  . Licence CC By‑SA.
Étiquettes : aucune
21
29
déc.
2022

J'aime écrire des scripts shells. Pour tout.
Mais parfois, une tâche ne peut pas être entièrement automatisée sans intervention.
Alors lorque l'utilisateur doit prendre une décision au milieu d'un script, j'utilise choose.

choose prend des propositions en entrée, les affiche, permet d'en sélectionner une selon différentes méthodes, et retourne le choix sélectionné par l'utilisateur.
On peut voir ça comme un filtre contrôlé par l'utilisateur.

Exemple : file=$(ls | choose)

choose aurait probablement dû être écrit en C avec ncurses.
Mais puisque j'aime le shell, et que je voulais que ça soit un script facile à copier, il est écrit en bon vieux Bourne shell.
Et puisque la vieillerie est un vice qui m'est cher, il y a une page man écrite dans ce langage incompréhensible qu'est roff.

J'utilise choose avec beaucoup de plaisir depuis 10 ans. En espérant que d'autres y trouveront leur bonheur, voici un lien : https://github.com/tmonjalo/choose

  • # Shellcheck: pour amélioration

    Posté par  . Évalué à 4.

    Merci pour ce partage.

    Je t'invite à utiliser https://www.shellcheck.net/ (finds bugs in your shell scripts) et ainsi améliorer ton script (même après 10 ans d'utilisation).

  • # cool cool cool

    Posté par  (site web personnel) . Évalué à 1. Dernière modification le 29 décembre 2022 à 11:16.

    Sympa ce petit outil.

    Perso pour ce genre de chose j'utilise fzf ou mieux, skim .

    Tu prévois d'ajouter un support de fuzzy finding ?

    Je ne sais pas pourquoi, mais dans le terminal de vim/neovim le choix se place mal (en haut au lieu du niveau de la sortie de la commande) :

       choose.1
        examples
    ❯ echoecho
    
    ❯ ls | ./choose
        choose
        choose.1
        examples
        README.md
        TODO

    autre remarque, il existe déjà une commande du même nom, ça pourrait préter à confusion

    https://github.com/theryangeary/choose

    • [^] # Re: cool cool cool

      Posté par  . Évalué à 1.

      Pas sûr de comprendre fzf. Tu peux donner un lien vers skim stp ?

      Non je ne prévois pas de truc compliqué comme le fuzzy finding.
      choose est plutôt prévu pour des choix simples parmi quelques propositions.

      Je n'utilise pas le terminal de vim. Peux-tu me dire comment reproduire ton test ?

      À propos du lien vers l'autre choose, je tiens à dire que j'étais là bien avant lui :)

  • # dialog

    Posté par  (site web personnel) . Évalué à 3.

    Pourquoi utiliser choose plutôt que dialog ou whiptail?

    pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

    • [^] # Re: dialog

      Posté par  . Évalué à 3.

      Exactement ce que j'allais demander!

      Techniquement, dialog a moins de dépendances que whiptail, les deux sont en terminal. Il existe aussi une variante de dialog en gtk, zenity, que je n'ai pas regardé. Il y avait xdialog dans le passé, mais je crois que ce n'est plus maintenu depuis longtemps: dommage, ça aurait été pas mal de pouvoir utiliser les mêmes commandes pour avoir du ncurses ou du graphique.

      Pour ma part, je trouve dialog pas génial, mais ça reste ce que j'ai trouvé de mieux, et la doc est raisonnablement claire. Non pas que j'ai cherché très loin, certes, mais whiptail me semble plus obscur pour ce qui est de la doc, et j'aime limiter les dépendances de mon système. Quant aux alternatives graphiques, le problème est que je travaille pas mal dans des terminaux, et que ce type de script est bien pratique via ssh (sans serveur xorg sur la machine distante), du coup je n'ai pas regardé plus que ça.

      Pour les curieux, ce que je reproche à dialog c'est:

      • l'utilisation de STDERR pour récupérer le résultat, donc on ne peux pas faire INPUT=$(dialog foobar), il faut passer par un fichier temporaire qu'il faut ensuite lire. Techniquement (en C), il est possible de faire autrement, mais pour ça il faudrait que dialog n'utilise pas STDOUT/STDIN, mais plutôt interagisse directement avec le terminal dans lequel il tourne. Ce n'est pas bien compliqué, et ça marche de nos jours (j'ai un PoC pour ça, je n'ai jamais fini le programme complet par contre), mais je ne sais pas si ça ne fonctionne que "depuis peu" ou si c'était déjà faisable il y a 20 ans. Le problème est peut-être lié à ncurses, pas sûr (je ne suis pas fan du tout de cette lib).
      • il est impossible de construire des boîtes de dialogue composite, à ma connaissance. C'est à dire d'afficher par exemple une checkbox avec un champ d'entrée. Ceci dit, ce type de fonctionnalité n'est pas utile dans le cas de ce que semble avoir besoin l'auteur du journal.

      Outre ces deux points, dialog fait largement le job, et est probablement packagé voire installé par défaut sur pas mal de distributions.

      • [^] # Re: dialog

        Posté par  (site web personnel) . Évalué à 3. Dernière modification le 29 décembre 2022 à 12:04.

        il faut passer par un fichier temporaire qu'il faut ensuite lire

        Non.

        $ cat err.c 
        #!/usr/bin/tcc -run
        #include <stdio.h>
        #include <stdlib.h>
        
        int main(void) {
                fprintf(stderr, "Hello, World!\n");
                return EXIT_SUCCESS;
        }
        $ HELLO=$(./err.c 2>&1)
        $ echo $HELLO
        Hello, World!

        pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

        • [^] # Re: dialog

          Posté par  . Évalué à 3.

          Essaies avec dialog, tu vas bien rire. Ce que fais ton code, c'est que le rendu ncurses sera capturé par ta variable, rendant de facto dialog complètement inutile.

          La solution serait plutôt ceci:

          #include <unistd.h>
          #include <sys/types.h>
          #include <sys/stat.h>
          #include <fcntl.h>
          
          int main()
          {
            int fd = open( ttyname( STDIN_FILENO ), O_RDWR );
            fprintf( "ceci ne sera pas capturé mais affiché\n" );
            printf( "ceci sera capturé, ou affiché si pas de capture\n" );
          }
          
          

          C'est le seul moyen à ma connaissance d'implémenter un dialog qui ne souffrirait pas du problème que j'ai décrit, c'est à dire qu'il reste possible de capturer la sortie sans rendre caduc le rendu.

          • [^] # Re: dialog

            Posté par  (site web personnel) . Évalué à 2.

            OK, du coup ça se voit que j'ai jamais vraiment utilisé dialog. Mais maintenant que je regarde, l'option --stdout fait ce que tu veux je pense.

            $ FOO=$(dialog  --inputbox input 42 42 --stdout)

            pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

            • [^] # Re: dialog

              Posté par  . Évalué à 2.

              On dirait bien que ça fonctionne, en effet. Je suppose que je n'avais pas prêté assez attention à ceci:

              If you use this option, dialog attempts to reopen the terminal so it can write to the display.

              qui est basiquement ce que le bout de C que j'ai mis plus haut fait.

      • [^] # Re: dialog

        Posté par  (site web personnel, Mastodon) . Évalué à 2.

        Pour ma part, je trouve dialog pas génial, mais ça reste ce que j'ai trouvé de mieux, et la doc est raisonnablement claire. Non pas que j'ai cherché très loin, certes, mais whiptail me semble plus obscur pour ce qui est de la doc, et j'aime limiter les dépendances de mon système.

        Je doute qu'il y ait plus de dépendances… mais si t'as des pointeurs je prends.
        La doc de whiptail se limite à la page de man… il me semble. Le programme lui-même, plus petit, se limite à un sous-ensemble (celui utilisé dans l'installation de Debian en mode console) de dialog : par exemple pas widget calendrier ni fichier je crois. Il n'y a pas de formulaire de plusieurs widgets non plus.

        “It is seldom that liberty of any kind is lost all at once.” ― David Hume

        • [^] # Re: dialog

          Posté par  . Évalué à 3. Dernière modification le 29 décembre 2022 à 14:17.

          Je doute qu'il y ait plus de dépendances…

          dialog:

          • debianutils (ça m'a l'air nouveau ça, j'en avais pas souvenir)
          • libc6
          • libncursesw6
          • libtinfo6

          Pas de dépendances supplémentaires si on descend dans l'arbre (tout ça ne dépend que de libc6 et de libtinfo6).

          whiptail:

          • libc6
          • libnewt0.52
          • libpopt0
          • libslang2

          Donc, à priori, même nombre de dépendances… sauf que debianutils, concrètement, je me demande ce que ça fait la, ce ne sont que des scripts shell. A vue de nez, c'est utilisé par l'installation, mais du coup, pourquoi ce n'est pas utilisé par whiptail? Je n'en sais rien. Qui plus est, je pense que c'est "récent", mais ma mémoire me trompe peut-être.

          Honnêtement, ce n'est pas énorme, c'est juste "une" dépendance, mais si on prend sur un système standard, il est hyper probable que libtinfo6 et libncursesw6 soient déjà installés, puisque requis par d'autres outils fréquemment utilisés. C'est beaucoup moins le cas des outils slang.
          Selon aptitude:

          • libtinfo6 est requis (je prend pas les pre-depends, la flemme) par 622 paquets
          • libncursesw6 par 176
          • libpopt0 par 95
          • libslang2 par 36

          libtinfo6 est requis par bash, qui n'est pas optionnel sur debian. libncursesw6 par aptitude, alsa-utils, fdisk, powertop, procps, entres autres, qui sont des outils particulièrement courants.
          Libpopt0 est requis par samba-libs, qui n'est installé pour aucune raison valable sur mes systèmes (non, vraiment, aucune valable: mpd et mpv peuvent supporter SBM, donc c'est actif dans debian, mais c'est tout. C'est d'ailleurs un truc qu'il faut que je fasse, me compiler mpd, mpv, sdl pour ne pas dépendre de ça, parce que ça tire pas mal de dépendances dont je n'ai pas l'usage, mais qui sont sûrement très utiles pour d'autres). libslang2 n'a même pas cette "chance" d'être requis par un composant "inutile" (dans mon cas d'usage).

          Après, il est évident que ça ne change pas grand chose, concrètement on parle de 232Kio décompressés pour libpopt et 1670Kio décompressés pour libslang, de nos jours c'est négligeable (environ 2meg, une paille).
          D'un point de vue accessibilité et i18n, whiptail gagne probablement compte tenu de la dépendance optionnelle (recommandation) à libfribidi (support des scripts qui s'écrivent de droite à gauche).

          Malgré tout, il s'agit d'un élément que je prend en compte: j'essaie de garder les choses gérables, et multiplier les dépendances a faible valeur ajoutée pour mon usage n'est pas quelque chose qui m'aide a aller lire les changelog. Et puis ça m'amuse d'avoir un système entièrement fonctionnel tout en connaissant quasi par coeur la liste des paquets installés :) c'est une vieille manie que je tiens de l'époque ou j'utilisais windows et connaissais par coeur les processus et services qui tournaient sur ma machine.
          Ca ne sert pas à grand chose (ça m'a bien permis de dégommer ou un deux malwares, mais franchement, ça reste peu pertinent) mais c'est comme ça :)

          Le programme lui-même, plus petit,

          Effectivement, et la différence est de taille: 72.7k pour whiptail, contre 1251 pour dialog (archives debian décompressées), dialog lui-même pesant ses 248K contre 27K pour whiptail.
          La doc de dialog se limite aussi à une page de man, de mémoire, mais celle-ci est, toujours de mémoire, nettement plus claire que celle de whiptail. Mais je suis peut-être biaisé.

          • [^] # Re: dialog

            Posté par  (site web personnel, Mastodon) . Évalué à 4.

            De toutes les distributions que j’utilise couramment, Debian fait un découpage très très fin qui peut multiplier les dépendances. debianutils en est le résultat (plus on décompose et plus on peut factoriser)
            À l’opposé quand on regarde certains paquets RHEL (j'ai pas retesté depuis CentOS 8, mais je repense à des trucs comme SNMP ou Vim) y a parfois trop peu de découpage …et pour le cas présent le paquet s’appelle newt et la bibliothèque n’y est pas séparée (mais je ne vois pas les dépendances en ligne, juste leurs évolutions ?) Je pense que c’est la même chose (peu de découpage) pour dialog

            P.S. 1 : Je viens de voir : whiptail a une doc en ligne aussi riche que la doc en ligne de dialog, avec en moins l’aspect historique et comparaison aux autres
            P.S. 2 : C’est newt qui se veut un substitut de ncurse et qui dépend de slang2
            P.S. 3 : Faut que je vérifie, mais me semble avoir déjà fait des installations Debian sans libncursesw6 (et de par le fonctionnement de fdisk je ne vois pas pourquoi il en aurait besoin, par contre cfdisk oui)

            “It is seldom that liberty of any kind is lost all at once.” ― David Hume

      • [^] # Re: dialog

        Posté par  (site web personnel) . Évalué à 2.

        Pour les curieux, ce que je reproche à dialog c'est […] l'utilisation de STDERR pour récupérer le résultat, donc on ne peux pas faire INPUT=$(dialog foobar)

        Je ne me rappelle plus comment j'en suis arrivé à cette solution, mais voici un petit script montrant comment j'utilise dialog :

        #!/bin/bash
        INPUT=$(dialog\
          --title "Test"\
          --menu "" 9 21 3\
            "a" "Entrée 1"\
            "b" "Entrée 2"\
            "c" "Entrée 3"\
          3>&1 1>&2 2>&3)
        echo -e "\nValeur retournée : '$INPUT'"

        Pour nous émanciper des géants du numérique : Zelbinium !

        • [^] # Re: dialog

          Posté par  . Évalué à 2.

          J'ai souvenir d'avoir écrit un script shell qui exécutait des requêtes SQL dans mariadb pour récupérer les valeurs qui peuplaient des listes, et permettre la configuration de cette même DB. Même s'il est vrai que je n'avais pas vu (ou mal compris plus probablement) l'option --stdout je me souviens que le code était assez horrible à écrire (mais je n'ai pas de regret, cet outil a vraiment aidé le tech qui intervenait sur les machines).

          Ce que tu montres est d'ailleurs assez symptomatique: ton contenu est statique, hard-codé, et pourtant ça sent déjà le souffre, avec ces redirections (qui sont inutiles du coup) et des valeurs de taille "aléatoires".

          De manière générale, je pense que dialog gagnerait a recevoir un gros coup de jeune. Et si on pouvais au passage avoir plusieurs backends hé bien ça ne serait pas plus mal (un backend printf/scanf permettrait d'automatiser des tests, des backends X11/wayland/framebuffer permettrait d'avoir un truc acceptable pour des utilisateurs et non juste pour une personne qui fait la config ou le déploiement d'un logiciel…). Mais bon, yaka fokon. En attendant, dialog fonctionne pour les cas complexes, même si des gens préfèreront choose pour des cas plus simples.

          Pour reprendre ton exemple, à priori cela donnerait:

          INPUT=$(choose -cr "Entrée 1" "Entrée 2" "Entrée 3" )
          echo -e "\nValeur retournée : '$INPUT'"
          

          La différence est claire, je pense. Reste qu'a vue de nez, choose ne supporte pas les tags, qui sont quand même bien pratiques, parce que ça permets d'associer un truc facile à comparer dans le code avec un texte facile à lire pour l'utilisateur final.

      • [^] # Re: dialog

        Posté par  . Évalué à 1.

        Pour les curieux, ce que je reproche à dialog c'est:

        • l'utilisation de STDERR pour récupérer le résultat, donc on ne peux pas faire INPUT=$(dialog foobar), il faut passer par un fichier temporaire qu'il faut ensuite lire.

        Je ne comprends pas vraiment cette remarque :
        - avec les redirections shell, il est possible d'inverser stdout/stderr d'un process
        - dialog semble capable de sortir son résultat sur stdout

        Dans les exemples de dialog, voir par exemple timebox et timebox-stdout (dans /usr/share/doc/dialog/examples sur une Debian)

    • [^] # Re: dialog

      Posté par  . Évalué à 1.

      choose est moins intrusif visuellement, question de goût je suppose.

      • [^] # Re: dialog

        Posté par  (site web personnel) . Évalué à 2.

        Pourquoi ne pas ajouter une option pour être moins visuellement intrusif à dialog alors ? :)

        pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

        • [^] # Re: dialog

          Posté par  . Évalué à 4.

          Parce que dialog a un objectif beaucoup plus large que choose.

        • [^] # Re: dialog

          Posté par  . Évalué à 2.

          En terme de complexité, ce serait se prendre la tête pour pas grand chose. Choose ne pèse même pas 1KLoC, dialog, en revanche… je ne serai pas surpris qu'il atteigne plusieurs 10aines de KLoC.

          • [^] # Re: dialog

            Posté par  (site web personnel) . Évalué à 2. Dernière modification le 29 décembre 2022 à 23:37.

            Ça dépend comment tu mesures la complexité. Combien de lignes de code ça ajouterais à dialog ? Qui les maintiendrait comparé à choose ? Quel est le coût en complexité d'ajouter et maintenir une dépendance au projet ? Quel est le coût en complexité pour les utilisateurs concernés s'ils doivent maintenant connaitre deux outils au lieu d'un ? Si envoyer un patch pour dialog est « se prendre la tête pour pas grand chose », ajouter une dépendance (écrite en shell, sans test et pas packagée) au projet me semble potentiellement pire.

            Je veux bien croire qu'il y ait des gens qui aient l'usage de choose mais en l'occurrence, dans un vrai projet, l'intérêt ne me semble pas probant.

            pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

            • [^] # Re: dialog

              Posté par  . Évalué à 3.

              Un script shell de moins de 400 lignes contre ~24KLoC de vieux C (1994 quand même) serait plus complexe à maintenir?
              Je ne crois pas.
              Personnellement, choose je peux relire et comprendre complètement le code en moins de 2H, dialog me prendra probablement plusieurs jours.
              Il est aussi à mon avis bien plus aisé d'apprendre le bourne shell que le C, même si l'on peu regretter qu'il y ait très peu d'outils d'analyse statique qui tiennent la route en shell (je n'en connais aucun que je considère bon, tous ceux que j'ai utilisés sortaient plus de faux positifs qu'autre chose), comparé au C.

              Pour comprendre dialog, il faut aussi comprendre ncurses, qui a le "bon goût" (erk) d'user de macro pour tout et n'importe quoi si ma mémoire ne me trompe pas. Je ne parierai pas non plus sur la résilience de cette lib quand le processus se mange un signal…

              Dans un vrai projet genre embarqué, on pourrais aussi citer le poids. 7.68Kio pour choose contre ~250Kio pour dialog (sans les libs), ce n'est pas rien. A titre de comparaison, busybox pèse 1.1Meg, c'est à dire ~4 fois plus, mais c'est un environnement complet, qui est possiblement capable de faire fonctionner choose (je n'ai pas lu le code de choose, donc je ne sais pas).
              Et, oui, ce genre de choses signifie encore quelque chose. Il reste des µcontrolleurs ou l'espace de stockage est limité.

              Maintenant, pour faire un installateur ou un outil de diag pour un technicien capable de se ssh dans un système, si j'ai la place, je vais clairement préférer dialog, de ce que j'ai compris c'est quand même nettement plus puissant.

              • [^] # Re: dialog

                Posté par  (site web personnel) . Évalué à 2.

                Nous avons visiblement des expériences et contraintes de production très différentes :)

                pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

                • [^] # Re: dialog

                  Posté par  . Évalué à 2.

                  Probablement, puisque je ne suis plus dev :D (mais je commence a me demander si je vais y retourner donc…)

                  De manière générale je privilégierais aussi l'usage de dialog sauf vraie bonne raison, pour info, mais je pense que choose peut être valide dans un certain nombre de projets, du fait que dialog reste quand même pas le truc le plus léger du monde (surtout si on inclue ncurses) et ça peut compter.

    • [^] # Re: dialog

      Posté par  (site web personnel, Mastodon) . Évalué à 3.

      Bah, c'est une solution sans curse donc sans bibliothèque additionnel et sans changer l'interface. Accessoirement la syntaxe est nettement plus simple pour faire la même chose.

      Sinon, perso, je recherche plutôt le select de ksh et bash mais en shell-independant

      “It is seldom that liberty of any kind is lost all at once.” ― David Hume

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.