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.
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
Évitez which
name__. Non seulement c’est un processus externe que vous lancez pour très peu de choses (des noms tels que hash
name__, type
ou command
sont 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?
which
qui 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 foo
existe, même s'il non (notez que certains shells POSIX semblent le faire aussi pour hash
name__).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 which
name__. 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. type
et hash
name __ Les codes de sortie de __ ne sont pas très bien définis par POSIX, et hash
est vu se fermer correctement lorsque la commande n'existe pas (n'a pas encore vu cela avec type
name__). command
name 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 bash
cependant, les règles POSIX ne comptent plus et type
et hash
deviennent parfaitement sûrs à utiliser. type
a maintenant un -P
pour rechercher uniquement le PATH
et hash
a 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 gdate
si elle existe, sinon date
name__:
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 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.
Je suis d'accord avec lhunath pour décourager l'utilisation de which
name__, 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 command
est conforme à POSIX, voir ici pour sa spécification: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html
Remarque: type
est conforme à POSIX, mais type -P
ne l’est pas.
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
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
.
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
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.
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.
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
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.
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
.
Pourquoi ne pas utiliser les commandes intégrées de Bash si vous le pouvez?
which programname
...
type -P programname
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.
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 alias
es 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 }
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
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é.
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
}
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'
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; }
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
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.
Il dira selon le lieu si le programme existe ou non
if [ -x /usr/bin/yum ]; then
echo This is Centos
fi
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
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
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.
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.
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.
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