Artisan Numérique

/cli/bash/ Bash et l'auto-complètement

il n'est nul besoin de présenter GNU-BASH (Bourne-Again SHell), le célèbre interpréteur de ligne de commande disponible sur à peu prés toutes les plate-formes, d'UNIX bien évidement, à Windows (via cigwin). Il n'est pas non plus nécessaire de présenter une des fonctionnalités que tout utilisateur de bash utilise de manière constante et permanente, l'auto-complètement avec la touche [TAB] pour éviter de taper de longues successions de noms de dossier, gagnant ainsi beaucoup de temps. Ce qui est en revanche beaucoup moins connu, c'est la capacité de bash d'appliquer l'auto-complètement à bien plus que de simple dossier ou noms de fichier.

Étendre l'auto-complètement

Quel utilisateur d'aptitude (ce n'est qu'un exemple) n'a pas rêvé taper aptitude [TAB] pour obtenir la liste de toutes les commandes disponibles et ainsi éviter d'avoir à taper et/ou se rappeler chacune d'entre elles ? De même quel utilisateur d'urpmi n'a pas un jour espéré obtenir d'un simple appui sur TAB puisse la liste des paquets disponibles ? Et bien ce genre de petits miracles est à porté d'installation d'un simple paquet.

sur une mandriva
rooturpmi bash-completion

sur une debian
rootaptitude install bash-completion
installation des extensions de l'auto-completion

Pour tester, fermez votre console et ouvrez-en une nouvelle :

rootaptitude [TAB]
autoclean       clean           forbid-version  hold            markauto        remove          show            update          why-not
build-dep       dist-upgrade    forget-new      install         purge           safe-upgrade    unhold          upgrade
changelog       download        full-upgrade    keep-all        reinstall       search          unmarkauto      why
yoran@badbox:~aptitude
test de l'auto-complètement sur debian avec aptitude
rooturpmi apache-mod[TAB]
Display all 215 possibilities? (y or n)
apache-mod_activex_filter      apache-mod_but                 apache-mod_form                apache-mod_ndb                 apache-mod_sqil
apache-mod_annodex             apache-mod_bw                  apache-mod_form-devel          apache-mod_nss                 apache-mod_ssl
apache-mod_anticrack           apache-mod_bwshare             apache-mod_fortress            apache-mod_ntlm                apache-mod_stats
...
apache-mod_bt-utils            apache-mod_file_cache          apache-mod_mya                 apache-mod_spin                apache-mod_zrkadlo
root@antinea [~] # urpmi apache-mod
test de l'auto-complètement sur mandriva avec urpmi

Magique non ? Bien évidement, cela ne se limite pas à ces deux commandes et ce paquet ajoute l'auto-complètement sur des choses aussi diverses que gitn, svn, chkconfig, gcc, configure, etc, etc, etc... Pour avoir une idée des possibles, regardez ce que vous avez maintenant dans le dossier /etc/bash_completion.d, cela peut surprendre.

Écrire ses propres greffons

Une fois que l'on a mis le doigt dans l'auto-complètement, difficile de se limiter à ce que l'on veut bien nous fournir. On passe ainsi bien vite sa vie à râler lorsque bash devient aphone à l'appui frénétique sur la touche tab. Fort heureusement, même un âne (pour preuve j'y arrive !), peut étendre l'auto-complètement. Commençons directement par un exemple (stupide) et créez le fichier /etc/bash_completion.d/compte. L'exemple en question va faire l'auto-complètement sur une commande fictive compte qui prend comme premier paramètre la langue (anglais, français, espagnol) et comme second un chiffre en toute lettres (ex. un, tres, etc..). J'ai prévenu que c'était stupide !!

_compte() {
  local choix mot_courant
  case "$COMP_CWORD" in
    # si nous sommes au premier paramètres, nous donnons la liste des langues
    1) 
      choix="français anglais espagnol"
      ;;

    # second niveau, la langue a donc été choisie
    2) 
      langue=${COMP_WORDS[1]}
      case "$langue" in
        anglais)
          choix="one two three"
          ;;
        français)
          choix="un deux trois"
          ;;
        espagnol)
          choix="un dos tres"
          ;;
        *)
          choix="késako"
          ;;
      esac
      ;;
    *)
      words=()
      ;;
  esac

  # création de la liste finale de choix
  mot_courant=${COMP_WORDS[COMP_CWORD]}
  COMPREPLY=( $( compgen -W '$choix' -- $mot_courant  ) )
}

complete -F _compte compte
contenu de /etc/bash_completion.d/compte

Avant de dépioter ce code, testons notre oeuvre :

d'abord on le charge dans l'espace de travail courant
gaston. ./compte

en on teste
gastoncompte [TAB][TAB]
anglais   espagnol  français

Ok, ça fonctionne pour les premiers arguments, testons les suivant :

gastoncompte espagnol [TAB][TAB]
un dos tres

Voilà, tout marche parfaitement. Maintenant quelques explications sur le fonctionnement. La fonction magique est celle qui se trouve à la fin du script complete -F _compte compte. Cette fonction instruit BASH sur l'association entre l'auto-complètement de la commande compte (dernier paramètre) et une fonction BASH _compte. Ainsi, dés que la commande compte est saisie, BASH utiliser la fonction _compte pour en faire l'auto-complètement.

Dans la fonction _compte la première chose que nous regardons est le contenu de la variable COMP_CWORD qui bash renseigne avec le nombre de "mots" sur la ligne de commande. Au démarrage il n'y en a qu'un, la commande compte elle-même. C'est le sens du test (case "$COMP_CWORD" in) qui entame la fonction. Dans le cas où nous avons un seul mot, nous renseignons la liste des choix avec la liste des langues. Si la langue est déjà fournie, la valeur de $COMP_CWORD est 2 et nous utilisons cette fois le tableau COMP_WORDS qui contient chaque mot déjà saisi (de même que le dernier mot saisi partiellement). Nous récupérons donc le second élément de ce tableau, la langue, et nous faisons un simple test pour définir une liste de choix pour chacune d'entre elles.

Les deux dernières lignes de la fonction sont les plus importantes. La première ligne récupère le dernier mot de la liste. Il s'agit donc du mot saisi partiellement (ou vide). La seconde utilise la fonction BASH compgen qui va prendre la liste des choix que nous avons créée et le mot vide ou partiellement saisi pour opérer un filtrage des choix possibles (ex. si le mot partiel est a, que les choix sont 'anglais français espagnol', la liste des choix sera filtrée pour ne plus laisser que anglais). Cette liste est assigné, sous la forme d'un tableau NASH; à la variable COMPREPLY qui comme son nom le suggère contient la liste des choix à afficher à l'utilisateur.

Voilà, c'est tout et ça fonctionne à merveille. Pour rendre ce "greffon" utilisable pour tous sans avoir à le lancer, il suffit soit de le placer dans le dossier /etc/bash_completion.d ou alors de faire un symlink vers ce dossier. Dans les deux cas, il faudra démarrer une nouvelle session pour que ce soit actif par défaut.

Attention, chose étrange sur certaines débians, il semble que le fait d'appeler une commande écrite php-cli au sein d'une fonction d'auto-completion, casse complétement le fonctionnement de celle-ci. Je n'ai pas encore réussi à trouver pourquoi, donc si quelqu'un a des idées...

Réglage du fonctionnement de l'auto-complétement

Sous bash, l'auto-complétion est géré par la librairie GNU/readline. Cette librairie est hautement paramétrable pour personnaliser encore un peu mieux la gestion de l'auto-complétion. Un exemple, je déteste le fonctionnement des débian impliquant de devoir taper deux fois sur [TAB] pour faire apparaître la liste des choix. Voyons comment changer cela.

La librairie readline est configurée à travers le fichier globale /etc/inputrc ou localement par ~/.inputrc. Ce fichier contient tout ce qu'il faut à readline pour fonctionner comme le mapping des touches, la gestion des caractères étendus, de la touche méta, et aussi de l'auto-complétion. Une liste complète des paramétrages est disponible ici.

Dans le cas du double tab, il s'agit en fait d'un effet de bord lié à ce que readline est configuré sur la debian pour n'afficher la liste des choix de manière directe que s'il n'y a aucune ambiguïté. Du coup un ls [TAB] est considéré comme un monde d'ambiguïté qu'il faut forcer en tapant une seconde fois sur [TAB].

Pour régler cela, il suffit donc de dire à readline de présenter la liste dans toutes les situations, ambiguïté ou pas. Cela se fait en ajoutant à la fin du fichier /etc/inputrc les réglages suivants :

set show-all-if-ambiguous on
set page-completions off
Passage en auto-complétion simple tab

Le premier réglage est assez évident, le second permet en plus de ne pas paginer (more) la liste des possibles. A vous de voir si cela vous convient, et ne mettez pas ce second paramètre si ce n'est pas le cas.

Synthèse

complete -F fonction commande Association d'une la fonction BASH à une commande
$COMP_CWORD Le nombre de mots saisis (y compris la commande et les mots incomplets)
$COMP_WORDS Le tableau des mots saisis
compgen -W 'liste' -- courant Génère une liste de choix à partir d'une liste source et d'un mot courant (qui peut être vide)
$COMPREPLY Le tableau des choix à afficher

Conclusion

Cette auto-complètement est une bénédiction pour qui utilise massivement la ligne de commande. Et cette possibilité, tellement simple, de pouvoir écrire ses propres greffons lui donne un nouveau souffle. En tout cas c'est mon avis et je le partage ;-)