Vérifier si un programme existe depuis un script

En interactif, on peut avoir l’habitude d’utiliser which qui répond facilement à la problématique. Mais dans un script, il ne faut pas. Et cette question simple en apparence cache en fait quelques subtilités que je vais essayer de retranscrire ici.

Remarque : C’est à l’origine un post sur Stack Overflow. Je me permets de le reprendre et traduire ici à des fins d’archivage, au cas où le post ou le site venaient à disparaitre.

POSIX

command -v <the_command>

Exemple :

if ! command -v <the_command> &> /dev/null
then
    echo "<the_command> could not be found"
    exit
fi

Spécifique Bash

hash <the_command> # Programmes sur le système, ou bien ...
type <the_command> # Vérifier les built-ins, mots-clefs réservés et les alias

Plus d’explications

Il faut éviter which. Non seulement ça utilise un processus en plus pour pas grand-chose, mais en plus, en fonction des systèmes, les effets d’une commande externe peuvent varier. Utiliser les built-ins du shell (comme command, hash ou type) est optimal car bien plus économe en ressources.

Pourquoi s’en soucier ?

Sur certains systèmes, which ne définit pas de statu de retour. Cela signifie que if which foo retournera toujours vrai, même si foo n’est pas présent sur la machine. Remarque : a priori certains shells POSIX font de même avec hash.

De plus, il arrive sur certains systèmes que which fasse des choses en plus comme changer la sortie, voire interroger le gestionnaire de paquets.

En conclusion

Si le shebang est /bin/sh, soyez POSIX et utilisez command. À noter que type sans option est également POSIX.

Si vous utilisez Bash, type ou hash sont recommandés. type a maintenant une option -P pour ne rechercher que dans le PATH (non POSIX, mais en bash pur, peu importe). hash aura pour conséquence de mettre en cache le chemin si la commande existe, ce qui est une bonne chose puisque le but de rechercher une commande est probablement de l’utiliser ensuite.

Un dernier exemple pour la route :

if hash systemclt 2>/dev/null; then
    systemctl stop "${service}"
else
    /etc/init.d/${service} stop
fi

Remarque : l’exemple ci-dessus provient d’un script qui a nécessité que je fasse une recherche sur la question, pas du post d’origine.