Как я могу проверить, существует ли программа, которая будет либо возвращать ошибку и выйти, либо продолжить с помощью script?
Кажется, что это должно быть легко, но это меня колотило.
Как я могу проверить, существует ли программа, которая будет либо возвращать ошибку и выйти, либо продолжить с помощью script?
Кажется, что это должно быть легко, но это меня колотило.
Совместимость с POSIX:
command -v <the_command>
Для bash
определенных сред:
hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords
Избегайте which
. Мало того, что это внешний процесс, который вы запускаете для выполнения очень мало (это означает, что встроенные функции, такие как hash
, type
или command
, намного дешевле), вы также можете полагаться на встроенные функции, чтобы фактически делать то, что вы хотите, в то время как эффекты внешних команд могут легко варьироваться от системы к системе.
Зачем заботиться?
which
, что даже не устанавливает статус выхода, что означает, что if which foo
там даже не работает и будет всегда что foo
существует, даже если это не так (обратите внимание, что некоторые оболочки POSIX, похоже, тоже делают это для hash
).which
обычным и злым, как изменение вывода или даже подключение к диспетчеру пакетов.Итак, не используйте which
. Вместо этого используйте один из них:
$ command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it not installed. Aborting."; exit 1; }
$ type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it not installed. Aborting."; exit 1; }
$ hash foo 2>/dev/null || { echo >&2 "I require foo but it not installed. Aborting."; exit 1; }
(Незначительная сторона примечания: некоторые предполагают, что 2>&-
является тем же самым 2>/dev/null
, но короче - это неверно. 2>&-
закрывает FD 2, который вызывает ошибку в программе, когда он пытается писать в stderr, что сильно отличается от успешной записи на него и отбрасывания вывода (и опасного!))
Если ваш хеш-трек /bin/sh
, тогда вам следует заботиться о том, что говорит POSIX. type
и hash
коды выхода не очень хорошо определены POSIX, и hash
, как видно, успешно завершает работу, когда команда не существует (еще не видели этого с type
). command
статус выхода хорошо определен POSIX, так что, вероятно, самый безопасный для использования.
Если ваш script использует bash
, хотя правила POSIX больше не имеют значения, и оба type
и hash
становятся совершенно безопасными в использовании. type
теперь имеет -P
для поиска только PATH
, а hash
имеет побочный эффект, что местоположение команды будет хэшировано (для более быстрого поиска в следующий раз, когда вы его используете), что обычно хорошо, поскольку вы, вероятно, проверяете его существование, чтобы фактически использовать его.
Как простой пример, здесь функция, которая запускает gdate
, если она существует, в противном случае date
:
gnudate() {
if hash gdate 2>/dev/null; then
gdate "[email protected]"
else
date "[email protected]"
fi
}
Ниже приведен переносимый способ проверить, существует ли команда в $PATH
и является исполняемым:
[ -x "$(command -v foo)" ]
Пример:
if ! [ -x "$(command -v git)" ]; then
echo 'Error: git is not installed.' >&2
exit 1
fi
Требуется выполнить проверку, потому что bash возвращает неисполняемый файл, если в $PATH
нет исполняемого файла с этим именем.
Также обратите внимание, что если неиспользуемый файл с тем же именем, что и исполняемый файл, существует ранее в $PATH
, черточка возвращает первый, хотя последний будет выполнен. Это ошибка и является нарушением стандарта POSIX. [Отчет об ошибках] [Стандарт]
Кроме того, это не удастся, если команда, которую вы ищете, была определена как псевдоним.
Я согласен с lhunath, чтобы препятствовать использованию which
, и его решение отлично подходит для пользователей BASH. Однако, чтобы быть более портативным, вместо этого следует использовать command -v
:
$ command -v foo >/dev/null 2>&1 || { echo "I require foo but it not installed. Aborting." >&2; exit 1; }
Команда command
совместима с POSIX, см. здесь для ее спецификации: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html
Примечание: type
совместим с POSIX, но type -P
нет.
У меня есть функция, определенная в моем .bashrc, что делает это проще.
command_exists () {
type "$1" &> /dev/null ;
}
Вот пример того, как он использовался (из моего .bash_profile
.)
if command_exists mvim ; then
export VISUAL="mvim --nofork"
fi
Это зависит от того, хотите ли вы знать, существует ли она в одном из каталогов в переменной $PATH
или знаете ли вы ее абсолютное местоположение. Если вы хотите узнать, находится ли она в переменной $PATH
, используйте
if which programname >/dev/null; then
echo exists
else
echo does not exist
fi
в противном случае используйте
if [ -x /path/to/programname ]; then
echo exists
else
echo does not exist
fi
Перенаправление в /dev/null/
в первом примере подавляет вывод программы which
.
Развернувшись на ответах @lhunath и @GregV, здесь приведен код для людей, которые хотят легко выполнить эту проверку внутри инструкции if
:
exists()
{
command -v "$1" >/dev/null 2>&1
}
Здесь, как его использовать:
if exists bash; then
echo 'Bash exists!'
else
echo 'Your system does not have Bash'
fi
Попробуйте использовать:
test -x filename
или
[ -x filename ]
На странице bash man Условные выражения:
-x file True if file exists and is executable.
Чтобы использовать hash
, как @lhunath предлагает, в bash script:
hash foo &> /dev/null
if [ $? -eq 1 ]; then
echo >&2 "foo not found."
fi
Этот script запускает hash
, а затем проверяет, является ли код выхода самой последней команды, значение, хранящееся в $?
, равно 1
. Если hash
не находит foo
, код выхода будет 1
. Если присутствует foo
, код выхода будет 0
.
&> /dev/null
перенаправляет стандартную ошибку и стандартный вывод из hash
, чтобы он не отображался на экране, а echo >&2
записывал сообщение в стандартную ошибку.
Я никогда не получал вышеуказанные решения для работы с коробкой, к которой у меня есть доступ. Например, тип был установлен (что делает больше). Поэтому необходима встроенная директива. Эта команда работает для меня:
if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi
Если вы проверяете существование программы, вы, вероятно, собираетесь запустить ее позже. Почему бы не попробовать запустить его в первую очередь?
if foo --version >/dev/null 2>&1; then
echo Found
else
echo Not found
fi
Это более надежная проверка, что программа работает, а не просто просмотр каталогов PATH и разрешений файлов.
Кроме того, вы можете получить полезный результат из своей программы, например, ее версию.
Конечно, недостатки в том, что некоторые программы могут быть тяжелыми для запуска, а некоторые не имеют опции --version
для немедленного (и успешного) выхода.
Проверьте наличие нескольких зависимостей и сообщите статус конечным пользователям.
for cmd in latex pandoc; do
printf '%-10s' "$cmd"
if hash "$cmd" 2>/dev/null; then
echo OK
else
echo missing
fi
done
Образец вывода:
latex OK
pandoc missing
Отрегулируйте 10
до максимальной длины команды. Не автоматический, потому что я не вижу не многословного способа POSIX сделать это: Как выровнять столбцы разделенной пробелами таблицы в Bash?
Почему бы не использовать встроенные функции Bash, если вы можете?
which programname
...
type -P programname
hash foo 2>/dev/null
: работает с zsh, bash, тире и золе.
type -p foo
: он работает с zsh, bash и ash (busybox), но не тире (он интерпретирует -p
как аргумент).
command -v foo
: работает с zsh, bash, тире, но не золой (busybox) (-ash: command: not found
).
Также обратите внимание, что builtin
недоступно с ash
и dash
.
Для тех, кто заинтересован, ни одна из вышеперечисленных методологий не работает, если вы хотите обнаружить установленную библиотеку. Я полагаю, что вам остается либо физически проверять путь (возможно, для файлов заголовков и т.д.), Либо что-то вроде этого (если вы используете дистрибутив на основе Debian):
dpkg --status libdb-dev | grep -q not-installed
if [ $? -eq 0 ]; then
apt-get install libdb-dev
fi
Как видно из вышесказанного, ответ "0" из запроса означает, что пакет не установлен. Это функция "grep" - "0" означает совпадение, "1" означает, что совпадение не найдено.
Команда which
может быть полезна. человек, который
Он возвращает 0, если исполняемый файл найден, 1, если он не найден или не выполним:
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
Хорошая вещь в том, что она выясняет, доступен ли исполняемый файл в среде, в которой выполняется, - сохраняет несколько проблем...
-Adam
Я бы сказал, что нет портативного и 100% надежного способа из-за зависания alias
es. Например:
alias john='ls --color'
alias paul='george -F'
alias george='ls -h'
alias ringo=/
Конечно, только последний является проблематичным (без обид на Ringo!) Но все они действительны alias
es с точки зрения command -v
.
Чтобы отклонить оборванные, такие как ringo
, мы должны проанализировать вывод встроенной команды alias
и recurse в оболочку оболочки (command -v
здесь не превосходит alias
). Там нет портативное решение для него, и даже Bash -специфическое решение довольно утомительно.
Обратите внимание, что такое решение безоговорочно отвергает alias ls='ls -F'
test() { command -v $1 | grep -qv alias }
Если внешняя команда type
недоступна (как принято в виде здесь), мы можем использовать POSIX-совместимый env -i sh -c 'type cmd 1>/dev/null 2>&1'
:
# portable version of Bash 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
По крайней мере, в Mac OS X 10.6.8 с использованием Bash 4.2.24 (2) command -v ls
не соответствует перемещенному /bin/ls-temp
.
Чтобы подражать Bash type -P cmd
, мы можем использовать 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; }
В хэш-варианте есть одна ошибка: в командной строке вы можете, например, ввести
one_folder/process
чтобы выполнить процесс. Для этого родительская папка one_folder должна находиться в $PATH. Но когда вы пытаетесь хешировать эту команду, она всегда будет успешной:
hash one_folder/process; echo $? # will always output '0'
Во-вторых, использование команды -v. Например. например:
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
моя настройка для сервера debian. У меня возникла проблема, когда несколько пакетов содержат одно и то же имя. например apache2. так это было моим решением.
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
}
Если вы не можете заставить вещи выше/ниже работать и вытаскивать волосы из своей спины, попробуйте выполнить ту же команду, используя bash -c
. Просто посмотрите на этот сомнительный бред, это то, что действительно происходит при запуске $(sub-command):
Во-первых. Это может дать вам совершенно другой выход.
$ command -v ls
alias ls='ls --color=auto'
$ bash -c "command -v ls"
/bin/ls
Во-вторых. Он не может дать вам никакого вывода.
$ command -v nvm
nvm
$ bash -c "command -v nvm"
$ bash -c "nvm --help"
bash: nvm: command not found
Если вы хотите проверить, существует ли программа и действительно программа, а не встроенная команда bash, то command
, type
и hash
не являются подходящие для тестирования, поскольку все они возвращают 0 статус выхода для встроенных команд.
Например, существует программа time, которая предлагает больше возможностей, чем встроенная команда time. Чтобы проверить, существует ли программа, я бы предложил использовать which
, как в следующем примере:
# 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
Здесь куча вариантов, но я не удивился, когда не было быстрых однострочных символов, это то, что я использовал в начале своих сценариев: [[ "$(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; }
это основано на выбранном здесь ответе и другом источнике (и я немного поигрался).
надеюсь, что это будет полезно для других.
Он сообщит в зависимости от местоположения, если программа существует или нет
if [ -x /usr/bin/yum ]; then
echo This is Centos
fi
Я использую это, потому что это очень просто:
if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then echo exists;else echo "not exists";fi
или
if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then
echo exists
else echo "not exists"
fi
Он использует shell builtin и статус программного эха для stdout и ничего для stderr с другой стороны, если команда не найдена, она имеет статус echos только для stderr.
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=$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"'
Результат
✘ [ABRT]: notacmd: command does not exist
hits command
0 /usr/bin/bash
Fin.
Команда -v работает нормально, если для параметра <command>
задана опция POSIX_BUILTINS, но может произойти сбой, если нет. (он работал у меня годами, но недавно столкнулся с тем, где он не работал).
Я считаю следующее более надежным:
test -x $(which <command>)
Так как он проверяет 3 вещи: путь, существование и разрешение на выполнение.
Привет:
if [[ 'command --help' ]]; then
echo "This command exists"
else
echo "This command does not exist";
fi
Поместите рабочий переключатель, например, "--help" или "-v" в проверку if: if [[ command --help
]]; затем
Мне пришлось проверить, был ли установлен git
как часть развертывания нашего CI-сервера. Мой последний bash script был следующим (сервер Ubuntu):
if ! builtin type -p git &>/dev/null; then
sudo apt-get -y install git-core
fi
Надеюсь, что это поможет кому-то еще!