Comment pourrais-je valider qu'un programme existe, d'une manière qui retournera une erreur et quittera, ou continuera avec le script?
Il semble que ça devrait être facile, mais ça m'a bouleversé.
Comment pourrais-je valider qu'un programme existe, d'une manière qui retournera une erreur et quittera, ou continuera avec le script?
Il semble que ça devrait être facile, mais ça m'a bouleversé.
Réponses:
Compatible POSIX:
command -v <the_command>
Pour les environnements spécifiques à Bash:
hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords
A éviter which
. Non seulement est-ce un processus externe que vous lancez pour faire très peu (ce qui signifie des builds comme hash
, type
ou command
sont beaucoup moins chers), vous pouvez également compter sur les builtins pour faire réellement ce que vous voulez, tandis que les effets des commandes externes peuvent facilement varier de système à système.
Pourquoi s'en soucier?
which
qui ne définit même pas de statut de sortie , ce qui signifie if which foo
qu'il ne fonctionnera même pas là-bas et signalera toujours l'foo
existence, même si ce n'est pas le cas (notez que certains shells POSIX semblent le faire hash
aussi).which
choses personnalisées et malfaisantes comme changer la sortie ou même se connecter au gestionnaire de paquets.Alors, n'utilisez pas which
. Utilisez plutôt l'un d'eux:
$ 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>&-
la même chose 2>/dev/null
mais plus court - ce n'est pas vrai . 2>&-
Ferme FD 2 qui provoque une erreur dans le programme lorsqu'il essaie d'écrire sur stderr, ce qui est très différent de réussir à y écrire et à ignorer la sortie (et dangereux!))
Si votre hash bang est, /bin/sh
vous devriez vous soucier de ce que dit POSIX. type
et hash
les codes de sortie de ne sont pas terriblement bien définis par POSIX, et hash
sont vus se terminer avec succès lorsque la commande n'existe pas (je ne l'ai pas encore vu avec type
). command
Le statut de sortie de est bien défini par POSIX, de sorte que l'un 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ûres à utiliser. type
a maintenant -P
pour rechercher juste 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'utiliserez), ce qui est généralement une bonne chose car vous vérifiez probablement son existence afin de l'utiliser réellement .
À titre d'exemple simple, voici une fonction qui s'exécute gdate
si elle existe, sinon date
:
gnudate() {
if hash gdate 2>/dev/null; then
gdate "$@"
else
date "$@"
fi
}
2>&-
("close output file file descriptor 2", qui est stderr) a le même résultat que 2> /dev/null
; 2) >&2
est un raccourci pour 1>&2
, que vous pouvez reconnaître comme "rediriger stdout vers stderr". Consultez la page de redirection d'E / S du Guide de script avancé Bash pour plus d'informations.
while read element ; do .. done <<< $(echo ${ArrayVar[*]})
, for word in $(fgrep -l $ORIGINAL *.txt)
, ls -l "$directory" | sed 1d
, {{pour en seq $BEGIN $END
}}, ... Beaucoup ont essayé de contacter les auteurs et proposer des améliorations , mais il est pas wiki et les demandes ont atterri dans l' oreille d'un sourd.
2>&-
n'est pas le même que 2>/dev/null
. Le premier ferme le descripteur de fichier, tandis que le second le redirige simplement vers /dev/null
. Vous ne pouvez pas voir une erreur car le programme essaie de vous informer sur stderr que stderr est fermé.
Ce qui suit est un moyen portable de vérifier si une commande existe $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 des exécutables est nécessaire car bash renvoie un fichier non exécutable si aucun fichier exécutable portant ce nom ne se trouve dans $PATH
.
Notez également que si un fichier non exécutable portant le même nom que l'exécutable existe plus tôt $PATH
, dash renvoie le premier, même si le dernier est exécuté. Il s'agit d'un bogue et 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.
command -v
un chemin d'accès même pour un fichier non exécutable? Autrement dit, le -x vraiment nécessaire?
-x
teste que le fichier est exécutable, ce qui était la question.
command
testera lui-même son exécutabilité - n'est-ce pas?
$PATH
lors de l'exécution d'une commande. Cependant, le comportement de command -v
est très incohérent. Dans le tiret, il renvoie le premier fichier correspondant dans $PATH
, qu'il soit exécutable ou non. En bash, il renvoie la première correspondance exécutable dans $PATH
, mais s'il n'y en a pas, il peut renvoyer un fichier non exécutable. Et dans zsh, il ne renverra jamais un fichier non exécutable.
dash
est le seul parmi ces trois à ne pas être conforme à POSIX; [ -x "$(command -v COMMANDNAME)"]
fonctionnera dans les deux autres. Il semble que ce bogue ait déjà été signalé mais n'a pas encore reçu de réponse: bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264
Je suis d'accord avec lhunath pour décourager l'utilisation de which
, et sa solution est parfaitement valable pour les utilisateurs de 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 compatible POSIX. Voir ici pour sa spécification: commande - exécuter une commande simple
Remarque: type
est compatible POSIX, mais type -P
ne l'est pas.
exit 1;
tue un xterm, s'il est invoqué à partir de là.
&>/dev/null
. Cependant, je suis d'accord avec vous, ce qui compte vraiment, c'est la portabilité, j'ai modifié ma réponse en conséquence, maintenant en utilisant la redirection sh standard >/dev/null 2>&1
.
J'ai une fonction définie dans mon .bashrc qui facilite cela.
command_exists () {
type "$1" &> /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
&>
?
&>
redirige à la fois stdout et stderr ensemble.
&>
peut ne pas être disponible dans votre version de Bash. Le code de Marcello devrait bien fonctionner; ça fait la même chose.
then
par exemple. Consultez cette réponse si vous souhaitez que l'exécutable existe dans $PATH
.
Cela dépend si vous voulez savoir si elle existe dans l'un des répertoires de la $PATH
variable ou si vous en connaissez l'emplacement absolu. Si vous voulez savoir si elle se trouve dans la $PATH
variable, 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 which
programme.
En développant les réponses de @ lhunath et @ GregV, voici le code pour les personnes qui veulent facilement mettre cette vérification dans une if
déclaration:
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
command
réussit même pour les alias, ce qui pourrait être quelque peu contre-intuitif. Vérifier l'existence dans un shell interactif donnera des résultats différents lorsque vous le déplacerez dans un script.
shopt -u expand_aliases
ignore / masque les alias (comme ceux alias ls='ls -F'
mentionnés dans une autre réponse) et les shopt -s expand_aliases
résout via command -v
. Ainsi, il devrait peut-être être défini avant la vérification et unset après, bien qu'il puisse affecter la valeur de retour de la fonction si vous ne capturez pas et ne renvoyez pas explicitement la sortie de l'appel de commande.
Essayez d'utiliser:
test -x filename
ou
[ -x filename ]
Depuis la page de manuel Bash sous Expressions conditionnelles :
-x file True if file exists and is executable.
Pour utiliser 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 s'exécute hash
puis vérifie si le code de sortie de la commande la plus récente, la valeur stockée dans $?
, est égal à 1
. Si hash
ne le 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
sorte qu'elle n'apparaisse pas à l'écran et echo >&2
écrit le message en erreur standard.
if hash foo &> /dev/null; then ...
?
Je n'ai jamais réussi à faire fonctionner les réponses précédentes sur la boîte à laquelle j'ai accès. D'une part, type
a été installé (faire ce qui more
fait). 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
if
syntaxe, utilisez simplement if builtin type -p vim; then ...
. Et les backticks sont une syntaxe vraiment ancienne et obsolète, $()
prise en charge même par sh
sur tous les systèmes modernes.
Vérifiez les dépendances multiples 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 la 10
à la longueur de commande maximale. Ce n'est pas automatique, car je ne vois pas de méthode POSIX non verbeuse pour le faire:
comment puis-je aligner les colonnes d'une table séparée par des espaces dans Bash?
Vérifiez si certains apt
packages sont installés avec dpkg -s
et installez-les autrement .
Voir: Vérifier si un package apt-get est installé puis l'installer s'il n'est pas sous Linux
Il a été mentionné précédemment à: Comment puis-je vérifier si un programme existe à partir d'un script Bash?
column -t
(partie d'util-linux).
Si vous vérifiez l'existence du programme, vous allez probablement 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
C'est une vérification plus fiable que le programme s'exécute 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, les inconvénients sont que certains programmes peuvent être lourds à démarrer et certains n'ont pas la --version
possibilité de quitter immédiatement (et avec succès).
hash foo 2>/dev/null
: fonctionne avec Z shell (Zsh), Bash, Dash et ash .
type -p foo
: il semble fonctionner avec Z shell, Bash et ash ( BusyBox ), mais pas Dash (il interprète -p
comme un argument).
command -v foo
: fonctionne avec Z shell, Bash, Dash, mais pas ash (BusyBox) ( -ash: command: not found
).
Notez également que builtin
n'est pas disponible avec ash et Dash.
Utilisez les fonctions intégrées Bash si vous pouvez:
which programname
...
type -P programname
which
n'est pas un Bash intégré.
-P
n'est pas POSIX. Pourquoi est type -P
préféré?
La commande -v
fonctionne correctement si l'option POSIX_BUILTINS est définie pour le <command>
test, mais elle peut échouer sinon. (Cela a fonctionné pour moi pendant des années, mais j'en ai récemment rencontré un où cela n'a pas fonctionné.)
Je trouve que les éléments suivants sont plus résistants aux pannes:
test -x $(which <command>)
Puisqu'il teste trois choses: chemin d'accès, existence et permission d'exécution.
test -x $(which ls)
renvoie 0, comme le fait test -x $(which sudo)
, même s'il ls
est installé et exécutable et sudo
n'est même pas installé dans le conteneur docker dans lequel je cours.
test -x "$(which <command>)"
ls
- être a un alias? Je ne pense pas que cela fonctionnerait si la commande a un paramètre.
Pour les personnes intéressées, aucune des méthodologies des réponses précédentes ne fonctionne si vous souhaitez détecter une bibliothèque installée. J'imagine qu'il vous reste soit à vérifier physiquement le chemin (potentiellement pour les fichiers d'en-tête et autres), ou quelque chose comme ça (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.
cmd; if [ $? -eq 0 ]; then
devrait être refactorisé enif cmd; then
dpkg
ouapt
Il y a une tonne d'options ici, mais je n'ai été surpris par aucune ligne rapide. Voici 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 sur une autre source.
Je dirais qu'il n'y a aucun 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 est problématique (n'offense pas Ringo!). Mais tous sont valables alias
du point de vue command -v
.
Afin de rejeter ceux qui pendent comme ringo
, nous devons analyser la sortie de la alias
commande intégrée du shell et y revenir (ce command -v
n'est pas un supérieur à alias
ici.) Il n'y a pas de solution portable pour cela, et même un Bash- une solution spécifique est plutôt fastidieuse.
Notez qu'une solution comme celle-ci rejettera sans condition alias ls='ls -F'
:
test() { command -v $1 | grep -qv alias }
shopt -u expand_aliases
ignore / masque ces alias et les shopt -s expand_aliases
montre via command -v
.
Cela indiquera en fonction de l'emplacement si le programme existe ou non:
if [ -x /usr/bin/yum ]; then
echo "This is Centos"
fi
La which
commande peut être utile. homme qui
Il renvoie 0 si l'exécutable est trouvé et renvoie 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 executable
2 if an invalid option is specified
Ce which
qui est bien, c'est qu'il détermine si l'exécutable est disponible dans l'environnement qui which
est exécuté - cela évite quelques problèmes ...
Ma configuration pour un serveur Debian :
J'ai eu le problème lorsque plusieurs packages contenaient 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 les gars / filles ne pouvez pas faire fonctionner les choses dans les réponses ici et que vous vous arrachez les cheveux, essayez d'exécuter la même commande en utilisant bash -c
. Regardez ce délire somnambulaire. C'est ce qui se passe vraiment lorsque vous exécutez $ (sous-commande):
Première. Il 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 aucune sortie.
$ command -v nvm
nvm
$ bash -c "command -v nvm"
$ bash -c "nvm --help"
bash: nvm: command not found
.bashrc
j'ai un [ -z "$PS1" ] && return
préfixé par # If not running interactively, don't do anything
donc je suppose que c'est une raison pour laquelle même l'approvisionnement explicite de bashrc en mode non interactif n'aide pas. Le problème peut être contourné en appelant un script avec un opérateur point ss64.com/bash/source.html , . ./script.sh
mais ce n'est pas une chose que vous souhaitez vous rappeler de taper à chaque fois.
La variante de hachage a un écueil: sur la ligne de commande, vous pouvez par exemple saisir
one_folder/process
pour 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'
$PATH
" —Ceci est complètement inexact. Essayez-le. Pour que cela fonctionne, one_folder doit se trouver dans le répertoire courant .
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
J'ai dû vérifier si Git était installé dans le cadre du déploiement de notre serveur CI . Mon script Bash final était le suivant (serveur Ubuntu):
if ! builtin type -p git &>/dev/null; then
sudo apt-get -y install git-core
fi
sudo
: sans le conditionnel, il s'arrêterait toujours et demanderait un mot de passe (sauf si vous avez fait un sudo récemment). BTW, il peut être utile de le faire sudo -p "Type your password to install missing git-core: "
pour que l'invite ne sorte pas du bleu.
Pour imiter Bash's type -P cmd
, nous pouvons utiliser la compatibilité POSIX env -i type cmd 1>/dev/null 2>&1
.
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; }
type
semble être un builtin
dans la plupart des shells donc cela ne peut pas fonctionner car les env
utilisations execvp
pour s'exécuter ne peuvent command
donc command
pas être un builtin
(et le builtin
sera toujours exécuté dans le même environnement). Cela échoue pour moi bash
, ksh93
, zsh
, busybox [a]sh
et dash
tous qui fournissent type
une commande du shell.
S'il n'y a pas de type
commande externe disponible (comme pris pour acquis ici ), nous pouvons utiliser la conformité 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 sur Mac OS X v10.6.8 (Snow Leopard), l'utilisation de Bash 4.2.24 (2) command -v ls
ne correspond pas à un déplacement /bin/ls-temp
.
Dans le cas où vous souhaitez vérifier si un programme existe et est vraiment un programme, pas une commande intégrée Bash , alors command
, type
ethash
ne sont pas appropriés pour les tests car ils renvoient tous 0 l'état 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 de l' heure . Pour vérifier si le programme existe, je suggère 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
Je voulais une réponse à la même question, mais pour exécuter dans un Makefile.
install:
@if [[ ! -x "$(shell command -v ghead)" ]]; then \
echo 'ghead does not exist. Please install it.'; \
exit -1; \
fi
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 cela, car c'est très simple:
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 les commandes internes du shell et l'état d'écho des programmes sur la sortie standard et rien sur l'erreur standard. En revanche, si une commande n'est pas trouvée, elle renvoie l'état uniquement à l'erreur standard.
which
renvoie vrai pour ceux-ci.type
sans arguments renverra en outre true pour les mots réservés et les commandes internes du shell. Si "programme" signifie "échangeable en$PATH
", alors voyez cette réponse .