Comment vérifier si un programme existe à partir d’un script Bash?

Comment pourrais-je valider qu’un programme existe, de manière à renvoyer une erreur et à quitter, ou à continuer avec le script?

Il semble que ça devrait être facile, mais ça me frappe.

Répondre

Compatible POSIX:

command -v  

Pour les environnements spécifiques bash :

 hash  # For regular commands. Or... type  # To check built-ins and keywords 

Explication

Évitez which . Non seulement c’est un processus externe que vous lancez pour faire très peu (ce qui signifie que les commandes internes comme le hash , le type ou la command sont beaucoup moins chères), vous pouvez également compter sur les commandes intégrées pour faire ce que vous voulez, facilement varier d’un système à l’autre.

Pourquoi s’en soucier?

  • De nombreux systèmes d’exploitation ont un système which ne définit même pas de statut de sortie , c’est-à-dire if which foo ne fonctionnera même pas et signalera toujours que foo existe, même si ce n’est pas le cas (notez que certains shells POSIX semblent le faire). ceci pour le hash aussi).
  • De nombreux systèmes d’exploitation font en sorte which les éléments personnalisés et malveillants, tels que la modification de la sortie ou même l’intégration au gestionnaire de paquets.

Donc, n’utilisez pas which . Utilisez plutôt l’un de ces:

 $ command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; } $ type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; } $ hash foo 2>/dev/null || { echo >&2 "I require foo but it's not installed.  Aborting."; exit 1; } 

(Note secondaire mineure: certains suggéreront 2>&- est le même 2>/dev/null mais plus court – ceci est faux . 2>&- ferme FD 2 qui provoque une erreur dans le programme quand il essaie d’écrire sur stderr , ce qui est très différent d’écrire avec succès et de supprimer la sortie (et dangereux!))

Si votre hash bang est /bin/sh vous devriez vous soucier de ce que POSIX dit. type codes de sortie de type et de hash ne sont pas très bien définis par POSIX, et le hash se termine avec succès lorsque la commande n’existe pas (cela n’a pas encore été vu avec le type ). Le statut de sortie de la command est bien défini par POSIX, de sorte que celui-ci est probablement le plus sûr à utiliser.

Si votre script utilise bash , les règles POSIX n’ont plus aucune importance et le type et le hash deviennent parfaitement sûrs. type maintenant un -P pour rechercher uniquement le PATH et hash a l’effet secondaire que l’emplacement de la commande sera haché (pour une recherche plus rapide la prochaine fois que vous l’utiliserez), ce qui est généralement une bonne chose puisque afin de l’utiliser réellement.

Comme exemple simple, voici une fonction qui exécute gdate si elle existe, sinon date :

 gnudate() { if hash gdate 2>/dev/null; then gdate "$@" else date "$@" fi } 

Voici un moyen portable de vérifier si une commande existe dans $PATH et est exécutable:

 [ -x "$(command -v foo)" ] 

Exemple:

 if ! [ -x "$(command -v git)" ]; then echo 'Error: git is not installed.' >&2 exit 1 fi 

La vérification de l’exécutable est nécessaire car bash renvoie un fichier non exécutable si aucun fichier exécutable portant ce nom n’est trouvé dans $PATH .

Notez également que si un fichier non exécutable portant le même nom que l’exécutable existe plus tôt dans $PATH , le tiret renvoie le premier, même si ce dernier est exécuté. Ceci est un bogue et viole la norme POSIX. [ Bug report ] [ Standard ]

De plus, cela échouera si la commande que vous recherchez a été définie comme un alias.

Je suis d’accord avec lhunath pour en décourager l’utilisation, et sa solution est parfaitement valable pour les utilisateurs de BASH . Cependant, pour être plus portable, la command -v doit être utilisée à la place:

 $ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed. Aborting." >&2; exit 1; } 

La commande de command est compatible avec POSIX, voir ici sa spécification: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html

Remarque: le type est compatible POSIX, mais le type -P ne l’est pas.

J’ai une fonction définie dans mon .bashrc qui rend cela plus facile.

 command_exists () { type "$1" &> /dev/null ; } 

Voici un exemple de son utilisation (à partir de mon .bash_profile ).

 if command_exists mvim ; then export VISUAL="mvim --nofork" fi 

Cela dépend si vous voulez savoir s’il existe dans l’un des répertoires de la variable $PATH ou si vous en connaissez l’emplacement absolu. Si vous voulez savoir si elle est dans la variable $PATH , utilisez

 if which programname >/dev/null; then echo exists else echo does not exist fi 

sinon utiliser

 if [ -x /path/to/programname ]; then echo exists else echo does not exist fi 

La redirection vers /dev/null/ dans le premier exemple supprime la sortie du programme en question.

En élargissant les réponses à @ lhunath et @ GregV, voici le code pour les personnes qui souhaitent placer facilement cette vérification dans une instruction if :

 exists() { command -v "$1" >/dev/null 2>&1 } 

Voici comment l’utiliser:

 if exists bash; then echo 'Bash exists!' else echo 'Your system does not have Bash' fi 

Essayez d’utiliser:

 test -x filename 

ou

 [ -x filename ] 

À partir de la page de manuel bash sous Expressions conditionnelles :

  -x file True if file exists and is executable. 

Pour utiliser le hash , comme le suggère @lhunath , dans un script bash:

 hash foo &> /dev/null if [ $? -eq 1 ]; then echo >&2 "foo not found." fi 

Ce script exécute le hash et vérifie ensuite si le code de sortie de la commande la plus récente, la valeur stockée dans $? , est égal à 1 . Si le hash ne trouve pas foo , le code de sortie sera 1 . Si foo est présent, le code de sortie sera 0 .

&> /dev/null redirige l’erreur standard et la sortie standard du hash pour qu’il n’apparaisse pas à l’écran et echo >&2 écrit le message en erreur standard.

Je n’ai jamais eu les solutions ci-dessus pour travailler sur la boîte à laquelle j’ai access. D’une part, le type a été installé (en faisant plus que ça). La directive intégrée est donc nécessaire. Cette commande fonctionne pour moi:

 if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi 

Si vous vérifiez l’existence du programme, vous allez probablement le lancer plus tard de toute façon. Pourquoi ne pas essayer de l’exécuter en premier lieu?

 if foo --version >/dev/null 2>&1; then echo Found else echo Not found fi 

C’est une vérification plus fiable que le programme s’exécute que de simplement regarder les répertoires PATH et les permissions de fichiers.

De plus, vous pouvez obtenir des résultats utiles de votre programme, comme sa version.

Bien sûr, les inconvénients sont que certains programmes peuvent être lourds à démarrer et d’autres ne disposent pas d’une option --version pour quitter immédiatement (et avec succès).

Recherchez plusieurs dépendances et informez les utilisateurs finaux

 for cmd in "latex" "pandoc"; do printf "%-10s" "$cmd" if hash "$cmd" 2>/dev/null; then printf "OK\n"; else printf "missing\n"; fi done 

Sortie de l’échantillon:

 latex OK pandoc missing 

Ajustez le 10 à la longueur maximale de la commande. Pas automatique car je ne vois pas de méthode POSIX non verbeuse: comment aligner les colonnes d’une table séparée par des espaces dans Bash?

Pour les personnes intéressées, aucune des méthodologies ci-dessus ne fonctionne si vous souhaitez détecter une bibliothèque installée. J’imagine que vous êtes soit en vérifiant physiquement le chemin (potentiellement pour les fichiers d’en-tête et autres), ou quelque chose comme ça (si vous êtes sur une dissortingbution basée sur Debian):

 dpkg --status libdb-dev | grep -q not-installed if [ $? -eq 0 ]; then apt-get install libdb-dev fi 

Comme vous pouvez le voir ci-dessus, une réponse “0” à la requête signifie que le paquet n’est pas installé. C’est une fonction de “grep” – un “0” signifie qu’une correspondance a été trouvée, un “1” signifie qu’aucune correspondance n’a été trouvée.

Pourquoi ne pas utiliser Bash builtins si vous le pouvez?

 which programname 

 type -P programname 

hash foo 2>/dev/null : fonctionne avec zsh, bash, dash et ash.

type -p foo : il semble fonctionner avec zsh, bash et ash (busybox), mais pas dash (il interprète -p comme argument).

command -v foo : fonctionne avec zsh, bash, dash, mais pas ash (busybox) ( -ash: command: not found ).

Notez également que l’ builtin n’est pas disponible avec les ash et les dash .

La commande which pourrait être utile. homme qui

Il retourne 0 si l’exécutable est trouvé, 1 s’il n’est pas trouvé ou n’est pas exécutable:

 NAME which - locate a command SYNOPSIS which [-a] filename ... DESCRIPTION which returns the pathnames of the files which would be executed in the current environment, had its arguments been given as commands in a ssortingctly POSIX-conformant shell. It does this by searching the PATH for executable files matching the names of the arguments. OPTIONS -a print all matching pathnames of each argument EXIT STATUS 0 if all specified commands are found and executable 1 if one or more specified commands is nonexistent or not exe- cutable 2 if an invalid option is specified 

Une bonne chose à propos de ce qui est de savoir si l’exécutable est disponible dans l’environnement dans lequel il est exécuté – enregistre quelques problèmes …

-Adam

Je dirais qu’il n’y a pas de moyen portable et 100% fiable en raison des alias suspens. Par exemple:

 alias john='ls --color' alias paul='george -F' alias george='ls -h' alias ringo=/ 

Bien sûr, seul le dernier est problématique (pas d’offense pour Ringo!) Mais tous sont des alias valables du sharepoint vue de la command -v .

Afin de rejeter ceux qui se ringo comme ringo , nous devons parsingr la sortie de la commande d’ alias intégrée du shell et y rentrer ( command -v n’est pas supérieur à l’ alias ici). Il n’y a pas de solution portable, et même un Bash La solution spécifique est plutôt fastidieuse.

Notez que cette solution rejettera inconditionnellement les alias ls='ls -F'

 test() { command -v $1 | grep -qv alias } 

Pour imiter le type -P cmd de Bash type -P cmd nous pouvons utiliser le env -i type cmd 1>/dev/null 2>&1 conforme env -i type cmd 1>/dev/null 2>&1 POSIX.

 man env # "The option '-i' causes env to completely ignore the environment it inherits." # In other words, there are no aliases or functions to be looked up by the type command. ls() { echo 'Hello, world!'; } ls type ls env -i type ls cmd=ls cmd=lsx env -i type $cmd 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; } 

La variante de hachage a un piège: sur la ligne de commande, vous pouvez par exemple taper

 one_folder/process 

faire exécuter le processus. Pour cela, le dossier parent de one_folder doit être dans $ PATH . Mais lorsque vous essayez de hacher cette commande, elle réussira toujours:

 hash one_folder/process; echo $? # will always output '0' 

J’appuie l’utilisation de “command -v”. Par exemple, comme ceci:

 md=$(command -v mkdirhier) ; alias md=${md:=mkdir} # bash emacs="$(command -v emacs) -nw" || emacs=nano alias e=$emacs [[ -z $(command -v jed) ]] && alias jed=$emacs 

S’il n’y a pas de commande de type externe disponible (comme cela a été pris pour acquis ici ), on peut utiliser env -i sh -c 'type cmd 1>/dev/null 2>&1' conforme à POSIX env -i sh -c 'type cmd 1>/dev/null 2>&1' :

 # portable version of Bash's type -P cmd (without output on stdout) typep() { command -p env -i PATH="$PATH" sh -c ' export LC_ALL=C LANG=C cmd="$1" cmd="`type "$cmd" 2>/dev/null || { echo "error: command $cmd not found; exiting ..." 1>&2; exit 1; }`" [ $? != 0 ] && exit 1 case "$cmd" in *\ /*) exit 0;; *) printf "%s\n" "error: $cmd" 1>&2; exit 1;; esac ' _ "$1" || exit 1 } # get your standard $PATH value #PATH="$(command -p getconf PATH)" typep ls typep builtin typep ls-temp 

Au moins sous Mac OS X 10.6.8 avec Bash 4.2.24 (2), la command -v ls ne correspond pas à un déplacé /bin/ls-temp .

ma configuration pour un serveur debian. J’ai eu un problème lorsque plusieurs paquets contiennent le même nom. par exemple apache2. c’était donc ma solution.

 function _apt_install() { apt-get install -y $1 > /dev/null } function _apt_install_norecommends() { apt-get install -y --no-install-recommends $1 > /dev/null } function _apt_available() { if [ `apt-cache search $1 | grep -o "$1" | uniq | wc -l` = "1" ]; then echo "Package is available : $1" PACKAGE_INSTALL="1" else echo "Package $1 is NOT available for install" echo "We can not continue without this package..." echo "Exitting now.." exit 0 fi } function _package_install { _apt_available $1 if [ "${PACKAGE_INSTALL}" = "1" ]; then if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then echo "package is already_installed: $1" else echo "installing package : $1, please wait.." _apt_install $1 sleep 0.5 fi fi } function _package_install_no_recommends { _apt_available $1 if [ "${PACKAGE_INSTALL}" = "1" ]; then if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then echo "package is already_installed: $1" else echo "installing package : $1, please wait.." _apt_install_norecommends $1 sleep 0.5 fi fi } 

Si vous ne pouvez pas faire fonctionner les choses ci-dessus / ci-dessous et les retirer de votre dos, essayez d’exécuter la même commande en utilisant bash -c . Il suffit de regarder ce délire somnambulaire, c’est ce qui se passe vraiment lorsque vous exécutez $ (sous-commande):

Premier. Cela peut vous donner une sortie complètement différente.

 $ command -v ls alias ls='ls --color=auto' $ bash -c "command -v ls" /bin/ls 

Seconde. Il ne peut vous donner aucun résultat.

 $ command -v nvm nvm $ bash -c "command -v nvm" $ bash -c "nvm --help" bash: nvm: command not found 

Au cas où vous voudriez vérifier si un programme existe et est vraiment un programme, pas une commande intégrée bash , alors la command , le type et le hash ne sont pas appropriés pour le test car ils renvoient tous le statut 0 de sortie pour les commandes intégrées.

Par exemple, il y a le programme horaire qui offre plus de fonctionnalités que la commande intégrée du temps . Pour vérifier si le programme existe, je vous suggérerais d’utiliser celui- which comme dans l’exemple suivant:

 # first check if the time program exists timeProg=`which time` if [ "$timeProg" = "" ] then echo "The time program does not exist on this system." exit 1 fi # invoke the time program $timeProg --quiet -o result.txt -f "%S %U + p" du -sk ~ echo "Total CPU time: `dc -f result.txt` seconds" rm result.txt 
 checkexists() { while [ -n "$1" ]; do [ -n "$(which "$1")" ] || echo "$1": command not found shift done } 

Je l’utilise parce que c’est très facile:

 if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then echo exists;else echo "not exists";fi 

ou

 if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then echo exists else echo "not exists" fi 

Il utilise le shell intégré et le statut echo du programme à stdout et rien d’autre à stderr par contre si une commande n’est pas trouvée, cela ne fait écho qu’à stderr.

Scénario

 #!/bin/bash # Commands found in the hash table are checked for existence before being # executed and non-existence forces a normal PATH search. shopt -s checkhash function exists() { local mycomm=$1; shift || return 1 hash $mycomm 2>/dev/null || \ printf "\xe2\x9c\x98 [ABRT]: $mycomm: command does not exist\n"; return 1; } readonly -f exists exists notacmd exists bash hash bash -c 'printf "Fin.\n"' 

Résultat

 ✘ [ABRT]: notacmd: command does not exist hits command 0 /usr/bin/bash Fin. 

Il y a une tonne d’options ici, mais j’ai été surpris de ne pas avoir de raccourci rapide, c’est ce que j’ai utilisé au début de mes scripts: [[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; } [[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; } [[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; } [[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; }

Ceci est basé sur la réponse sélectionnée ici et sur une autre source (et moi qui joue un peu).

J’espère que cela sera utile pour les autres.

La commande -v fonctionne correctement si l’option POSIX_BUILTINS est définie pour que la testée, mais peut échouer si ce n’est pas le cas. (cela a fonctionné pour moi pendant des années mais a récemment rencontré un problème là où ça ne fonctionnait pas).

Je trouve que ce qui suit est plus sûr:

 test -x $(which ) 

Comme il teste 3 choses: chemin, exécution et permission.

J’utilise une version très pratique et courte:

 dpkg -s curl 2>/dev/null >/dev/null || apt-get -y install curl 

Si facile si un seul programme doit être vérifié.

Je voudrais juste essayer d’appeler le programme avec par exemple --version ou --help et vérifier si la commande a réussi ou échoué

Utilisé avec le script set -e , il se fermera si le programme est introuvable et vous obtiendrez un message d’erreur significatif:

 #!/bin/bash set -e git --version >> /dev/null