Follow me
RSS feed
My sources
My Viadeo

Mettre en place de l'autocomplétion sur une commande shell

Greg | 15 May 2011

Projets Dans la continuité de mon développement de vf, je me suis demandé comment mettre en place de l'autocomplétion. En effet, si vf remplace très avantageusement cd, l'autocomplétion par défaut du shell ne permet d'accéder facilement qu'aux répertoires. Ainsi, un vf <TAB> ne présente que la liste des fichiers et répertoires, masquant totalement l'intérêt de vf, à savoir la présence des alias.

$ vf <TAB>
--< file >--
Capcode/                         lipsum/                          Fragaria/
...

Il serait bien plus intéressant que vf présente non seulement la liste des répertoires, mais également des alias :

$ vf <TAB>
github                           vidal                            docs
...
--< file >--
Capcode/                         lipsum/                          Fragaria/
...

J'ai donc fouillé un peu.

Etant un utilisateur convaincu de Zsh, je m'étais déjà amusé un peu avec le système de complétion de ce dernier. Pour ce qui est des autres, j'ai du googler un peu. Pour ce qui est du Bash, je suis très rapidement tombé sur des articles présentant complete. J'ai également cherché du côté de Tcsh pour finalement le laisser de côté -- pour le moment.

Sachant que les solutions pour mettre en place de la complétion sont différentes pour chaque shell, il faut commencer par déterminer celui utilisé par l'utilisateur. Ceci se fait très simplement :

ps -p $$ | tail -1 | awk '{print $NF}' | sed -e 's/-//g'

Le résultat de cet enchainement de commandes donnera bash pour une utilisateur de Bash, zsh pour un utilisateur de Zsh et sh pour un utilisateur de Csh ou Tcsh. Si l'on sait que seul Tcsh permet la complétion, alors vous avez un premier élément de réponse à la question : pourquoi j'ai laissé tomber Csh/Tcsh pour le moment ?

Maintenant que nous savons déterminer quel shell est utilisé, nous pouvons avancer.

Zsh

Zsh utilise deux commandes pour mettre en place de la complétion : compadd et compdef. La première prend en paramètre une liste de termes de complétion. Dans le cas de vf il s'agirait de la liste des alias créés par l'utilisateur. compdef attend, quant à elle, deux paramètres : la fonction définissant les termes de complétion (et utilisant donc compadd) et la commande sur laquelle cette complétion doit se faire. Dans cet article, nous allons nous amuser à définir une commande fictive ma_commande :

ma_commande() 
{
  echo "ma_commande a été appelée avec les arguments : $@"
}

Pour mettre en place la complétion via Zsh, nous mettrons donc en place le script suivant :

ma_commande() 
{
  echo "ma_commande a été appelée avec les arguments : $@"
}

__zsh_make_comp_list()
{
  compadd "option1" "option2" "option3" "option4"
}

compdef __zsh_make_comp_list ma_commande

Si maintenant nous chargeons ce script et que nous testons, nous aurons le résultat suivant :

$ . ./completion_test.sh
$ ma_commande <TAB>
option1        option2        option3        option4

Comme vous pouvez le voir, si nous voyons bien apparaitre les options, nous n'avons pas accès aux fichiers et répertoires. Pour les ajouter, rien de plus simple, il suffit de modifier __zsh_make_comp_list et ajouter un appel à la fonction _files :

ma_commande() 
{
  echo "ma_commande a été appelée avec les arguments : $@"
}

__zsh_make_comp_list()
{
  compadd "option1" "option2" "option3" "option4"
  _files
}

compdef __zsh_make_comp_list ma_commande

Ce qui donnera :

$ . ./completion_test.sh
$ ma_commande <TAB>
option1        option2        option3        option4
--< file >--
Capcode/                         lipsum/                          Fragaria/
...

Bash

Comme vous allez le voir, mon travail sur Bash est moins avancé que pour Zsh.

Comme Zsh, il faut utiliser deux commandes pour mettre en place de la complétion en Bash. compgen permet de définit une liste d'options de complétion comme le fait compadd. complete est quant à elle le pendant de compdef. Nous aurons donc la solution suivante en Bash :

ma_commande() 
{
  echo "ma_commande a été appelée avec les arguments : $@"
}

__bash_make_comp_list()
{  
  COMPREPLY=( $(compgen -W "option1 option2 option3 option4") )
  return 0
}

complete -df -F __bash_make_comp_list ma_commande

Comme vous pouvez le voir ci-dessus, Bash utilise la variable COMPREPLY pour stocker le résultat de la commande compgen, variable qui sera par la suite utilisée par complete. Et pour remplacer l'utilisation de la fonction _files de Zsh, Bash propose les options -d et -f pour complete

Voici le résultat obtenu :

$ . ./completion_test.sh
$ ma_commande <TAB>
Capcode        lipsum        option1        option2        option3        option4        Fragaria

Résultat

Maintenant que nous avons une solution pour Bash et Zsh, il faut merger le tout. Le script suivant donne le résultat de mon avancement sur l'autocomplétion. Comme vous pouvez le voir, si le résultat semble satisfaisant pour Zsh, il l'est bien moins pour Bash. Et il me reste encore le cas Csh/Tcsh à traiter.

#!sh

OPTIONS=(option1 option2 option3 option4)

ma_commande() {
  echo "ma_commande a été appelée avec les arguments : $@"
}

# -- ZSH ----------------------------------------------------------------------

__zsh_make_comp_list()
{
  compadd $OPTIONS
  _files
}

__zsh_complete()
{
  compdef __zsh_make_comp_list ma_commande
}

# -- BASH ---------------------------------------------------------------------

__bash_make_comp_list()
{  
  COMPREPLY=( $(compgen -W "${OPTIONS[*]}") )
  return 0
}

__bash_complete()
{
  complete -df -F __bash_make_comp_list ma_commande
}

# -- MAIN ---------------------------------------------------------------------

export SHELL=$(ps -p $$ | tail -1 | awk '{print $NF}' | sed -e 's/-//g')

if [ "X$SHELL" = "Xzsh" ]; then
  __zsh_complete
fi

if [ "X$SHELL" = "Xbash" ]; then
  __bash_complete
fi
Copyright © 2009 - 2011 Grégoire Lejeune.
All documents licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License, except ones with specified licence.
Powered by Jekyll.