Comment vérifier si un programme existe depuis un script Bash?

Comment puis-je valider qu'un programme existe, d'une manière qui retournera une erreur et sortira, ou qui continuera avec le script?

on dirait que ça devrait être facile, mais ça me dérange.

1637

30 réponses

réponse

compatible POSIX:

command -v <the_command>

pour bash environnements spécifiques:

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

explication

éviter which . Non seulement c'est un processus externe que vous lancez pour faire très peu (ce qui signifie que les builtins comme hash , type ou command sont beaucoup moins cher), vous pouvez également compter sur les builtins pour faire réellement ce que vous voulez, tandis que le les effets des commandes externes peuvent facilement varier d'un système à l'autre.

pourquoi s'en soucier?

  • beaucoup de systèmes d'exploitation ont un which que ne fixe même pas un statut de sortie , ce qui signifie que le if which foo ne fonctionnera même pas là et toujours signaler que foo existe, même si elle n'existe pas (notez que certains shell POSIX semblent le faire pour hash aussi).
  • de nombreux systèmes d'exploitation font which faire des trucs personnalisés et diaboliques comme changer la sortie ou même s'accrocher dans le gestionnaire de paquets.

donc, n'utilisez pas which . Utilisez plutôt l'une d'entre elles:

$ 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; }

(petit côté-note: certains vont suggérer que 2>&- est le même 2>/dev/null mais plus court – c'est faux . 2>&- ferme FD 2 qui provoque une erreur dans le programme quand il essaie d'écrire à stderr, ce qui est très différent de réussir à lui écrire et de rejeter la sortie (et dangereux!))

si votre hash bang est /bin/sh alors vous devez vous soucier de ce que POSIX dit. Les codes de sortie de type et hash ne sont pas très bien définis par POSIX, et hash est vu pour sortir avec succès quand la commande n'existe pas (n'ont pas vu cela avec type encore). Le statut de sortie de command est bien défini par POSIX, de sorte que L'on est probablement le plus sûr à utiliser.

si votre script utilise bash cependant, les règles POSIX n'ont plus vraiment d'importance et les deux type et hash deviennent parfaitement sûrs à utiliser. type a maintenant un -P pour rechercher juste le PATH et hash a l'effet secondaire que l'emplacement de la commande sera hachée (pour une recherche plus rapide La prochaine fois que vous l'utilisez), ce qui est généralement une bonne chose car vous avez probablement vérifiez son existence 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
}
2349
répondu lhunath 2017-09-26 23:02:09

ce qui suit est 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 .

aussi noter que si un fichier non exécutable avec le même nom que l'exécutable existe plus tôt dans $PATH , dash renvoie le premier, même si le second serait exécuté. Ceci est un bug et est en violation du standard POSIX. [ rapport de bogue ] [ Standard ]

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

268
répondu nyuszika7h 2017-10-26 14:18:46

je suis d'accord avec lhunath pour décourager l'utilisation de which , et sa solution est parfaitement valide pour les utilisateurs de BASH . Toutefois, pour être plus portable, command -v doit être utilisé à la place:

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

Commande command est conforme à POSIX, voir ici pour son cahier des charges: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html

Note: type est conforme à POSIX, mais type -P ne l'est pas.

193
répondu GregV 2014-07-23 20:31:46

j'ai une fonction définie dans mon .bashrc qui rend cela plus facile.

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

voici un exemple de la façon dont il est utilisé (de mon .bash_profile .)

if command_exists mvim ; then
    export VISUAL="mvim --nofork"
fi
82
répondu Josh Strater 2010-10-14 09:24:40

Cela dépend si vous voulez savoir si il existe dans l'un des répertoires dans le $PATH variable ou si vous 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

autre utilisation

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 which .

67
répondu dreamlax 2009-02-26 22:01:52

en développant les réponses de @lhunath et @GregV, voici le code pour les gens qui veulent mettre facilement ce chèque à l'intérieur d'une déclaration if :

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

Voici comment l'utiliser:

if exists bash; then
  echo 'Bash exists!'
else
  echo 'Your system does not have Bash'
fi
29
répondu Romário 2015-12-11 20:42:45

Essayez d'utiliser:

test -x filename

ou

[ -x filename ]

de la page de manuel de bash sous Expressions conditionnelles :

 -x file
          True if file exists and is executable.
20
répondu dmckee 2012-11-26 11:19:09

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

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

ce script exécute 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 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 de hash pour qu'elle n'apparaisse pas à l'écran et echo >&2 écrit le message à l'erreur standard.

16
répondu dcharles 2017-05-23 11:55:11

je n'ai jamais eu les solutions ci-dessus pour travailler sur la boîte, j'ai accès. Pour l'un, le type a été installé (faire ce que plus fait). La directive builtin est donc nécessaire. Cette commande fonctionne pour moi:

if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi
9
répondu Magnus 2013-01-24 06:27:47

Si vous vérifiez l'existence du programme, vous allez probablement pour l'exécuter 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

il s'agit d'une vérification plus fiable que le programme exécute simplement en regardant les répertoires de chemin et les permissions de fichier.

Plus vous pouvez obtenir un résultat utile de votre programme, comme sa version.

bien sûr, les inconvénients sont que certains programmes peuvent être lourds à démarrer et certains n'ont pas l'option --version pour sortir immédiatement (et avec succès).

7
répondu 0xF 2013-07-08 15:14:30

pourquoi ne pas utiliser Bash builtins si vous le pouvez?

which programname

...

type -P programname
6
répondu Steven Penny 2013-01-24 06:28:09

vérifiez les dépendances multiples et informez les utilisateurs finaux du statut

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

ajuster la 10 à la longueur de commande maximale. Pas automatique parce que je ne vois pas une façon non verbeuse POSIX de le faire: comment aligner les colonnes D'une table séparée par des espaces dans Bash?

5

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 qu'il vous reste à vérifier physiquement le chemin (potentiellement pour les fichiers d'en-tête et autres), ou quelque chose comme ceci (si vous êtes sur une distribution 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, un "0" réponse de 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 non la correspondance a été trouvée.

4
répondu Nathan Crause 2010-01-15 23:16:17

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 avec 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 builtin n'est pas disponible avec ash et dash .

4
répondu blueyed 2014-07-21 00:18:23

la commande which pourrait être utile. l'homme qui

renvoie 0 si l'exécutable est trouvé, 1 s'il n'est pas trouvé ou 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
       strictly  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

bonne chose à propos de ce qui est qu'il détermine si l'exécutable est disponible dans l'environnement ce qui est exécuté dans - permet d'économiser quelques problèmes...

- Adam

3
répondu Adam Davis 2009-02-26 21:56:53

je dirais qu'il n'y a pas de moyen portable et 100% fiable à cause de la pendaison alias es. 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 (aucune offense à Ringo!) Mais Tous sont valides alias es du point de vue de command -v .

pour rejeter ceux qui sont pendants comme ringo , nous devons analyser la sortie du shell intégré alias commande et y revenir ( command -v n'est pas supérieur à alias ici. Il n'y a pas de solution portable pour cela, et même une solution spécifique à Bash est assez fastidieuse.

notez que cette solution rejettera inconditionnellement alias ls='ls -F'

test() { command -v  | grep -qv alias }
3
répondu nodakai 2016-03-10 02:31:54

S'il n'y a pas de commande externe type disponible (prise pour acquis ici ), nous pouvons utiliser POSIX conforme 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="" 
      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
   ' _ "" || exit 1
}

# get your standard $PATH value
#PATH="$(command -p getconf PATH)"
typep ls
typep builtin
typep ls-temp

au moins sur Mac OS X 10.6.8 en utilisant Bash 4.2.24(2) command -v ls ne correspond pas à un /bin/ls-temp déplacé .

2
répondu freno 2017-05-23 11:55:11

pour imiter le type -P cmd de Bash, nous pouvons utiliser le env -i type cmd 1>/dev/null 2>&1 conforme à 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; }
1
répondu tim 2011-06-01 17:49:22

la variante de hachage a un pitfall: sur la ligne de commande vous pouvez par exemple taper

one_folder/process

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

hash one_folder/process; echo $? # will always output '0'
1
répondu anycast.cw 2011-12-14 12:41:26

je seconde l'utilisation de la commande "- 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
1
répondu user2961933 2013-11-06 19:06:48

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

function _apt_install() {
    apt-get install -y  > /dev/null
}

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

function _package_install_no_recommends {
    _apt_available 
    if [ "${PACKAGE_INSTALL}" = "1" ]; then
        if [ "$(dpkg-query -l  | tail -n1 | cut -c1-2)" = "ii" ]; then
             echo  "package is already_installed: "
        else
            echo  "installing package : , please wait.."
            _apt_install_norecommends 
            sleep 0.5
        fi
    fi
}
1
répondu ThCTLo 2015-03-27 14:26:27

si vous ne pouvez pas obtenir les choses ci-dessus/ci-dessous pour travailler et tirer les cheveux hors 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 quand vous lancez $(sous-commande):

D'abord. Il peut vous donner la sortie complètement différente.

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

à la Seconde. Il peut vous donner aucune sortie du tout.

$ command -v nvm
nvm
$ bash -c "command -v nvm"
$ bash -c "nvm --help"
bash: nvm: command not found
1
répondu user619271 2015-08-26 09:23:12

dans le cas où vous voulez vérifier si un programme existe et est vraiment un programme, pas un bash intégré à la commande , puis command , type et hash ne sont pas appropriés pour tester car ils retournent tous l'état de sortie 0 pour les commandes intégrées.

par exemple, il y a le programme time qui offre plus de fonctionnalités que la commande time intégrée. Pour vérifier si le programme existe, je suggérez d'utiliser 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
1
répondu rpr 2016-06-18 23:19:53

il y a une tonne d'options ici mais je n'ai pas été surpris d'avoir des montages rapides, 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; }

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

espérons que ce sera pratique pour les autres.

1
répondu keisar 2018-06-19 12:02:30
checkexists() {
    while [ -n "" ]; do
        [ -n "$(which "")" ] || echo "": command not found
        shift
    done
}
0
répondu Anonymous 2012-02-18 07:54:44

j'utilise ceci 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 shell builtin et programme echo status à stdout et rien à stderr par l'autre main si une commande n'est pas trouvée, il echos status seulement à stderr.

0
répondu A.N 2015-09-22 00:48:43

Script

#!/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=; 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.
0
répondu ecwpz91 2017-09-06 00:42:27
La commande

- v fonctionne bien si L'option POSIX_BUILTINS est définie pour l'option <command> à tester mais peut échouer si non. (il a travaillé pour moi pendant des années, mais récemment tombé sur un où il n'a pas fonctionné).

je trouve que ce qui suit est plus à l'épreuve des échecs:

test -x $(which <command>)

Puisqu'il teste 3 choses: le chemin, l'exécution et la permission.

0
répondu AnthonyC 2018-06-25 16:53:46

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 coché.

0
répondu Xynox 2018-07-10 12:29:48

Super réponse et explication de @lhunath. Sauvé ma journée. J'ai prolongé un peu. Ne pouvais pas me contrôler partage - en espérant qu'il pourrait être utile pour quelqu'un. Si quelqu'un a besoin de vérifier (un tableau de) plusieurs programmes, voici l'extrait rapide.

Que fait-il? (1) Lire le tableau des programmes. (2) afficher le message pour échec du programme. (3) invite l'utilisateur à continuer (forçant la boucle) O/N options pour la validation du reste des programmes.

#!/bin/bash

proginstalldir=/full/dir/path/of/installation
progsbindir=$proginstalldir/bin
echo -e "\nMy install directory - $proginstalldir"
echo -e "My binaries directory - $progsbindir"

VerifyInstall () {
clear
myprogs=( program1 program2 program3 program4 program5 programn ); 
echo -e "\nValidation of my programs started...."
for ((i=0; i<${#myprogs[@]}; i++)) ; do 
command -v $progsbindir/${myprogs[i]} >/dev/null && echo -e "Validating....\t${myprogs[i]}\tSUCCESSFUL"  || { echo -e "Validating.... \t${myprogs[i]}\tFAILED" >&2;
while true; do 
printf "%s:  "  "ERROR.... Validation FAILED for ${myprogs[i]} !!!! Continue?"; read yn; 
case $yn in [Yy] )  echo -e "Please wait..." ; break;;
[Nn]) echo -e "\n\n#################################\n##   Validation Failed .. !!   ##\n#################################\n\n" ; exit 1; break;;
*) echo -e "\nPlease answer y or n then press Enter\n"; esac; done; >&2; }; done
sleep 2
}

VerifyInstall
0
répondu Ajay Kumar 2018-09-25 16:58:06