Bash Argsparse : mieux gérer sa ligne de commande dans ses scripts.

Posté par  (site web personnel) . Édité par Xavier Teyssier, Benoît Sibaud et ZeroHeure. Modéré par claudex. Licence CC By‑SA.
46
10
oct.
2013
Ligne de commande

Je vous présente une bibliothèque écrite en Shell : bash-argsparse

Bash-argsparse permet une gestion des options de la ligne de commande en plus haut niveau qu'un simple getopt, un peu comme le module argparse de python.

Parmi la liste des fonctionnalités, on trouve :

  • la génération automatique du --help ;
  • la vérification des entrées utilisateurs, soit suivant des énumérations, soit suivant des formats pré-établis (nombres entiers, adresses IP, etc.), soit via des résultats de fonctions définies par le développeur du script ;
  • l'exclusion mutuelle de plusieurs options ;
  • le cumul de valeur par répétition d'une option ;
  • et plein d'aut'trucs.

Bash-Argsparse est écrit pour GNU Bash version 4 et fait un usage intensif des tableaux, tableaux associatifs et autres built-ins de Bash.

Il manque encore certaines fonctionnalités, notamment la gestion des paramètres positionnels (les trucs après le '--') qui fera partie d'une prochaine version, mais le code existant est plutôt stable. Libre aussi à vous de soumettre des requêtes pour de nouvelles fonctionnalités.

Aller plus loin

  • # passé sous zsh...

    Posté par  . Évalué à 5.

    Tu as l'air d'avoir fait un sacré taff. Malheureusement pour moi je suis passe a zsh en shell principal. Mais je note pour mes scripts ;)

    • [^] # Re: passé sous zsh...

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

      sous zsh tu as zparseopts qui est assez magique aussi :), man zshmodules pour plus d'infos

      • [^] # Re: passé sous zsh...

        Posté par  . Évalué à 3.

        Et je crois que zsh peut interpréter le bash. De toute façon tu peux créer un script bash et l'exécuter depuis zsh.

        Écrit en Bépo selon l’orthographe de 1990

      • [^] # Re: passé sous zsh...

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

        Je trouve que bash-argsparse à l'air vraiment plus pratique à utiliser, et surtout à retenir !

        Merci pour ce code !

  • # Vulgaire ?

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

    getopt n'est pas vulgaire, juste un peu rustre …

    http://la-rache.com

    • [^] # Re: Vulgaire ?

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

      Disons plutôt simple. Je modifie.

      • [^] # Re: Vulgaire ?

        Posté par  . Évalué à 1.

        Pendant qu'on est dans les corrections, c'est getopt**s**, et non getopt (le man de dash indiquant que celle-là est une évolution de celle-ci, qui ne supportait pas les espaces au sein des arguments). :)

        • [^] # Re: Vulgaire ?

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

          Non, je voulais bien parler de [/usr]/bin/getopt qui fait partie de util-linux. Mais getopts etant sensiblement moins evolue (pas d'option longue.. -_-) que getopt, tu vois bien la transitivite.

          • [^] # Re: Vulgaire ?

            Posté par  . Évalué à 2.

            Ah oui d'accord, j'avais pas vu qu'il y avait toujours un utilitaire. C'est un peu spécial comme fonctionnement: tu parses une première fois la ligne de commande avec getopt, tu redéfinis la première à partir de sa sortie avec set, puis tu parses à nouveau la nouvelle ligne de commande avec le shell… je croyais faire des trucs tordus ci-dessous, mais le /usr/share/getopt/getopt-parse.bash n'est pas mal non plus dans le genre. :)

            Pour l'absence d'option longue avec getopts, c'est sûrement parce qu'ils se sont arrêtés à une version POSIX, pour qui les options ne peuvent être que courtes.

  • # Exemple d'utilisation

    Posté par  . Évalué à 8.

    Merci pour le logiciel et pour la dépêche. Cependant j'aurais bien aimé un petit exemple directement dans la dépêche. Je pourrais fouiller sur github et/ou lire le code, mais ça serait tellement plus pratique d'avoir juste au-dessous un exemple basique pour illustrer le propos, surtout que c'est la première fois que ce logiciel est présenté et donc personne n'est censé déjà connaitre.

  • # Les grands esprits toussa...

    Posté par  . Évalué à 3.

    Amusant, je suis en ce moment également en train de chercher une alternative plus souple à getopts, dans une autre approche cependant: je cherche quelque chose de compact et POSIX. Comme j'aime beaucoup les interfaces BSD/POSIX (à base d'options courtes exclusivement), voici à quoi je suis arrivé:

    getopt () {
        local opt=${1:-'X].[X'}; shift 1
        awk 'BEGIN{ for (i=x=1; i < ARGC; i++) {
                        s = ARGV[i]
                        if (X || ! sub(/^-/,"",s)) {
                            LT[j++] = i-x; x=i
                            continue
                        }
                        (s == "-") && (X=1) && s=""
                        while (o=substr(s,1,1)) {
                            if (o ~ /^['$opt']/) {
                                print (s == o) ? (o""++i) : (o i"#-*" o)
                                if (i < ARGC)
                                    break
                                print "!"o # will be the last argument.
                            }
                            sub(/./,"",s); print o
                        }
                    }
                    exit (E = (j>0) ? 0 : 1)
                  }
             END{ print "--"; for (i=0; i < j; i++) print LT[i]; exit E }' "$@"
    }
    
    t=0 # type d'argument: 0=option; 1=littéral
    tog=0
    for arg in $(getopt xyz "$@"); do
        case "$t$arg" in
            1*) shift $arg; echo "argument: \"$1\"";;
            0--) t=1; continue;;
            0h) echo "aide."; exit 0;;
            0a) echo "interrupteur: \"$arg\" (tog=1)"; tog=1;;
            0b) echo "interrupteur: \"$arg\" (tog=0)"; tog=0;;
            0x*) eval x=\${${arg#x}}; echo "definition: x=\"$x\"";;
            0y*) eval y=\${${arg#y}}; echo "definition: y=\"$y\"";;
            0z*) eval z=\${${arg#z}}; echo "definition: z=\"$z\"";;
            0!?*) echo "${arg#!}: requiert un argument."; exit 1;;
            *) echo "$arg: option inconnue"; exit 1
        esac
    done

    Qui à l'exécution nous donne ceci:

    $ dash getopt.sh toto -bax "lili lulu" -ylolo titi "tata tutu" -- -a -bz lala
    interrupteur: "b" (tog=0)
    interrupteur: "a" (tog=1)
    definition: x="lili lulu"
    definition: y="lolo"
    argument: "toto"
    argument: "titi"
    argument: "tata tutu"
    argument: "-a"
    argument: "-bz"
    argument: "lala"
    

    C'est bien sûr loin d'être aussi complet que ce qui est proposé par argsparse, mais c'est portable et pour ma part je préfère avoir la pleine maîtrise de l'interface (dans la gestion des erreurs en particulier).

  • # Exemple simple

    Posté par  . Évalué à 4.

    Votre librairie apporte certainement de la souplesse d'utilisation.
    Cependant le code ci-dessous suffit dans la plupart des cas.

    # Récupération des arguments
    while [ -n "$1" ]; do
        param=$1
        case "$param" in
            # Option simple
            --option1|-o)
                option1="true"
                ;;
            # Option avec argument
            --option2=*)
                option2=${param##--option2=}
                ;;
            -*)
                echo "$param: option inconnue"; exit 1
                ;;
            *)
                # Liste d'arguments (fichiers par exemple)
                files[${#files[@]}]="$param"
                ;;
        esac
        shift
    done
    • [^] # Re: Exemple simple

      Posté par  . Évalué à 2.

      Bof, moi j'aime pouvoir combiner les flags ("-l -o" devient "-lo" voir "lo" comme avec tar).

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: Exemple simple

      Posté par  (site web personnel) . Évalué à 5. Dernière modification le 12 octobre 2013 à 15:34.

      Je ne force personne (a part certains, qui se reconnaitront - private joke) mais je refute l'argument.

      J'ai constate que ce genre de bout de code etait tres souvent 1. dupliqué partout, tout le temps 2. penible a ecrire 3. penible a ecrire correctement, a la fois dans la syntaxe (manque de double-quotes, "eval" utilise a mauvais escient et mal controle) et dans le design (gestions des options longues hasardeuse, variables utilisees nommees aleatoirement, ce qui nuit a la lisibilite).

      Typiquement, ton exemple ne gere pas le cas d'une chaine vide passee en parametre.

      Pour la ligne de commande

      foo.sh "" 1 2 3

      Ton script ignorera completement 1, 2 et 3. C'est peut-etre un detail pour vous mais pour moi ca veut dire beaucoup. Et peut-etre que tu n'en as rien a faire pour ton cas specifique mais de la a dire que ca convient dans la plupart des cas je trouve ca un tantinet exagere.

      Pour moins de lignes de code, je te propose d'avoir en plus :

      1. Une meilleure robustesse (ce que l'utilisateur rentre, le script l'aura entre les mains)
      2. Un usage-message genere automatiquement
      3. Une nomenclature claire (enfin, relativement claire : la syntaxe de bash est ce qu'elle est) :
        • la valeur de option 2 dans "${program_options[option2]}"
        • tes fichiers dans "${program_params[@]}"
        • la possibilite de tester option1 avec des syntaxe "if argsparse_is_option_set option1; then … ; fi"
      4. Un comportement non-ambigu : pour reprendre ton bout de code : tu permets "--option2=tata" mais pas "--option2 tata" ? Pourquoi peut-on faire l'un mais pas l'autre alors que ce sont 2 formes d'ecritures permises par getopt et que de facto l'utilisateur espere pouvoir les utiliser ?

      Je pense qu'argsparse est encore moins contraignant pour les gens qui ne veulent pas se prendre la tete.

Suivre le flux des commentaires

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