Si vous avez déjà travaillé dans un environnement de type Unix, vous avez sans doute remarqué cette erreur quand vous tentiez d'exécuter une commande inexistante.
$ cqt output.txt
zsh: command not found: cqt
Que se passe-t-il sous le capot ? Le manuel Linux nous l'explique.
... Si la recherche est infructueuse, l'interpréteur de commandes recherche une fonction définie de l'interpréteur de commandes nomméecommand_not_found_handle
.
Si cette fonction existe, elle est invoquée dans un environnement d'exécution distinct avec la commande d'origine et les arguments de la commande d'origine comme arguments, et l'état de sortie de la fonction devient l'état de sortie de ce sous-shell.
Si cette fonction n'est pas définie, l'interpréteur de commandes affiche un message d'erreur et renvoie un état de sortie de 127.
(Extrait traduit du manuel Linux)
Dans notre cas, nous n'avons pas (encore) défini la fonction command_not_found_handle
, donc un message d'erreur est affiché et le code de sortie est 127.
$ cqt output.txt
zsh: command not found: cqt
$ echo $?
127
handle
ou handler
?
Précision importante avant de commencer. Pour le shell Bash (l'interprète par défaut sur de nombreux systèmes Linux), la fonction à implémenter est command_not_found_handle
. Pour le shell ZSH, le shell par défaut pour macOS depuis la version Catalina, il faudra implémenter le command_not_found_handler
. Cependant, bash
reste toujours disponible sur les MAC modernes.
Si les deux shells partagent beaucoup de similitudes, nous allons nous concentrer sur le shell Bash pour le reste de l'article, qui a l'« avantage » d'être présent sur les systèmes Linux et les macOS récents.
Les mains dans le code
Ouvrons le fichier de configuration de shell ~/.bashrc
et ajoutons la fonction command_not_found_handle
.
command_not_found_handle() {
echo "Commande introuvable"
echo "Veuillez vérifier votre saisie ou utiliser une autre commande."
}
Pour prendre en compte les modifications dans la session shell en cours, n'oubliez pas de lancer source ~/.bashrc
.
$ vi ~/.bashrc
$ source ~/.bashrc
$ sfeir
Commande introuvable
Veuillez vérifier votre saisie ou utiliser une autre commande.
On peut enrichir la fonction en exploitant les arguments à notre disposition.
command_not_found_handle() {
local command="$1"
echo "Oh non ! La commande '$command' est introuvable."
return 42
}
Comme le manuel l'indique, la fonction peut récupérer la commande d'origine. On peut donc l'utiliser comme variable. Ici, on décide de l'afficher. On peut aussi modifier le code de sortie. Dans le cas ci dessous, le code est non nul pour signaler que la commande introuvable est un échec.
command_not_found_handle() {
local command="$1"
echo "Commande introuvable : $command"
commandes_similaires=$(apropos --long "$command" | awk '{print $1}')
if [ -n "$commandes_similaires" ]; then
echo "Vouliez-vous dire l'une de ces commandes similaires ?"
echo "$commandes_similaires"
fi
}
Dans l'exemple suivant, on va utiliser la variable comme paramètre d'autres fonctions. apropos
retourne les pages du manuel en lien avec la commande puis awk
transforme la sortie. Ainsi, quand on va saisir une commande inexistante, une liste d'alternatives sera proposée.
$ wge
Commande introuvable : wge
Vouliez-vous dire l'une de ces commandes similaires ?
wget
Ne plus réinventer la roue
Nous avons codé pour découvrir et comprendre la fonction command_not_found_handle
mais il existe des outils qui se basent sur ce qu'on a vu et vont beaucoup plus loin.
Ubuntu et Debian possèdent un package command-not-found
qui peut aller chercher dans les dépôts si on essaie de lancer un programme introuvable sur la machine. Pour les utilisateurs de Homebrew et macOS, le Homebrew Command Not Found permet de reproduire ce comportement. Enfin pour les fans de Oh My Zsh, la fonctionnalité similaire se trouve dans le plugin command-not-found
.