web-dev-qa-db-fra.com

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

Comment pourrais-je valider l'existence d'un programme, de manière à renvoyer une erreur et à quitter ou à poursuivre le script?

On dirait que ça devrait être facile, mais ça m’arrache mal.

1893
gregh

Réponse

Compatible POSIX:

command -v <the_command>

Pour bashenvironnements spécifiques:

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

Explication

Évitez whichname__. Non seulement c’est un processus externe que vous lancez pour très peu de choses (des noms tels que hashname__, typeou commandsont beaucoup moins chers), mais vous pouvez également compter sur ceux-ci pour faire ce que vous voulez, tandis que les effets des commandes externes peuvent varient facilement d'un système à l'autre.

Pourquoi se soucier?

  • De nombreux systèmes d'exploitation ont un whichqui ne définit même pas un statut de sortie, ce qui signifie que le if which foo ne fonctionnera même pas là-bas et toujours signalera que fooexiste, même s'il non (notez que certains shells POSIX semblent le faire aussi pour hashname__).
  • De nombreux systèmes d'exploitation obligent whichà faire des choses personnalisées et malveillantes, telles que modifier la sortie ou même se connecter au gestionnaire de paquets.

Donc, n'utilisez pas whichname__. Utilisez plutôt l'un de ceux-ci:

$ 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 que 2>&- est le même 2>/dev/null mais plus court - c'est faux . 2>&- ferme le FD 2 ce qui provoque un erreur dans le programme quand il essaie d’écrire dans stderr, ce qui est très différent d’écrire avec succès et d’annuler la sortie (et dangereux!))

Si votre hasch bang est /bin/sh, vous devriez alors vous soucier de ce que dit POSIX. typeet hashname __ Les codes de sortie de __ ne sont pas très bien définis par POSIX, et hashest vu se fermer correctement lorsque la commande n'existe pas (n'a pas encore vu cela avec typename__). commandname Le statut de sortie de __ est bien défini par POSIX, de sorte que celui-ci est probablement le plus sûr à utiliser.

Si votre script utilise bashcependant, les règles POSIX ne comptent plus et typeet hashdeviennent parfaitement sûrs à utiliser. typea maintenant un -P pour rechercher uniquement le PATHet hasha pour effet secondaire que l'emplacement de la commande sera haché (pour une recherche plus rapide la prochaine fois que vous l'utilisez), ce qui est généralement une bonne chose puisque vous vérifiez probablement son existence dans l'ordre. réellement l'utiliser.

À titre d’exemple simple, voici une fonction qui exécute gdatesi elle existe, sinon datename__:

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

Voici un moyen portable de vérifier si une commande existe dans $PATH et que 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 auparavant dans $PATH, dash renvoie le premier, même si ce dernier est exécuté. Ceci est un bogue et est en violation du standard POSIX. [ Rapport de bug ] [ Standard ]

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

405
nyuszika7h

Je suis d'accord avec lhunath pour décourager l'utilisation de whichname__, et sa solution est parfaitement valide pour les utilisateurs BASH . Cependant, 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; }

La commande commandest conforme à POSIX, voir ici pour sa spécification: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html

Remarque: typeest conforme à POSIX, mais type -P ne l’est pas.

196
GregV

J'ai une fonction définie dans mon .bashrc qui facilite cela.

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

Voici un exemple d'utilisation (tiré de mon .bash_profile.)

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

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 s'il se trouve 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 which.

74
dreamlax

Pour en savoir plus sur les réponses de @ lhunath et de @ GregV, voici le code des personnes souhaitant insérer 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
31
Romário

Essayez d'utiliser:

test -x filename

ou

[ -x filename ]

De la page de manuel bash sous Expressions conditionnelles :

 -x file
          True if file exists and is executable.
20
dmckee

Pour utiliser hash, comme suggéré par @ lhunath , 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 afin qu'elle n'apparaisse pas à l'écran et que echo >&2 enregistre le message en erreur standard.

15
dcharles

Je n'ai jamais eu les solutions ci-dessus pour travailler sur la boîte à laquelle j'ai accès. D'une part, le type a été installé (fait ce que plus fait). Donc, la directive intégrée est nécessaire. Cette commande fonctionne pour moi:

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

Si vous vérifiez l'existence du programme, vous l'exécuterez probablement 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 est plus fiable de vérifier que le programme est exécuté que de simplement regarder les répertoires PATH et les autorisations de fichiers.

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

Bien sûr, l’inconvénient est que certains programmes peuvent être lourds à démarrer et que certains n’ont pas d’option --version pour pouvoir quitter (immédiatement) avec succès.

8
0xF

Recherchez plusieurs dépendances et informez les utilisateurs finaux de leur statut

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

Exemple de sortie:

latex     OK
pandoc    missing

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

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 en tant qu'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.

6
blueyed

Pourquoi ne pas utiliser les commandes intégrées de Bash si vous le pouvez?

which programname

...

type -P programname
6
bin

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, une réponse "0" à la requête signifie que le package n'est pas installé. Ceci 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.

4
Nathan Crause

Je dirais qu'il n'y a pas de moyen portable et fiable à 100% en raison de la suspension alias. Par exemple:

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

Bien sûr, seul le dernier problème est problématique (aucune offense à Ringo!), Mais ils sont tous valides aliases du point de vue de command -v.

Pour rejeter ceux qui sont suspendus comme ringo, nous devons analyser la sortie de la commande intégrée au shell alias et y renvoyer (command -v n'est pas supérieur à alias. ici.) Il n’existe pas de solution portable, et même une solution spécifique à Bash est plutôt fastidieuse.

Notez qu'une solution comme celle-ci rejettera inconditionnellement alias ls='ls -F'

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

La commande which pourrait être utile. homme qui

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

Ce qui est bien, c’est qu’il détermine si l’exécutable est disponible dans l’environnement dans lequel il est exécuté - évite quelques problèmes ...

-Adam

3
Adam Davis

S'il n'y a pas de commande externe type disponible (comme pris pour acquis ici ), nous pouvons utiliser le code env -i sh -c 'type cmd 1>/dev/null 2>&1' conforme à POSIX:

# 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 sur Mac OS X 10.6.8 avec Bash 4.2.24 (2), command -v ls ne correspond à un /bin/ls-temp déplacé.

2
freno

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 $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
}
1
ThCTLo

La variante de hachage comporte un écueil: sur la ligne de commande, vous pouvez par exemple saisir

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'
1
anycast.cw

Pour imiter le type -P cmd de Bash, nous pouvons utiliser 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
tim

Je seconde l'utilisation de "commande -v". Par exemple. comme ça:

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
user2961933

Il y a une tonne d'options ici mais j'ai été surpris qu'il n'y ait pas de solution simple, 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 cela sera utile pour les autres.

1
keisar

Il dira selon le lieu si le programme existe ou non

if [ -x /usr/bin/yum ]; then
    echo This is Centos
fi
1
Klevin Kona

Si vous voulez vérifier si un programme existe et qu’il s’agit bien d’un programme et non d’une commande intégrée bash, alors command, type et hash sont non approprié pour les tests car ils renvoient tous un état de sortie 0 pour les commandes intégrées.

Par exemple, il existe le programme time qui offre davantage de fonctionnalités que la commande intégrée time. Pour vérifier si le programme existe, je suggérerais 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
rpr

Si vous ne parvenez pas à faire fonctionner les objets situés au-dessus/ci-dessous et à vous arracher les cheveux, essayez d'exécuter la même commande en utilisant bash -c. Il suffit de regarder ce délire somnambulaire, voici ce qui se passe réellement 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. Cela ne peut vous donner aucune sortie.

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

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.
0
ecwpz91

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 intégré et programme echo status sur stdout et rien sur stderr de l’autre côté. Si une commande n’est pas trouvée, elle renvoie le statut à stderr uniquement.

0
A.N

la commande -v fonctionne bien si l'option POSIX_BUILTINS est définie pour que le <command> soit testé, mais qu'il puisse échouer sinon. (Cela a fonctionné pour moi pendant des années mais récemment en a rencontré un où cela n'a pas fonctionné).

Je trouve que les éléments suivants sont plus infaillibles:

test -x $(which <command>)

Puisqu'il teste pour 3 choses: chemin, existence et autorisation d'exécution.

0
AnthonyC

Hey:

if [[ `command --help` ]]; then
  echo "This command exists"
else
  echo "This command does not exist";
fi

Placez un interrupteur de travail tel "- help" ou "- v" dans le contrôle si: if [[command --help]]; puis

0
Farhad Sakhaei