Как проверить, существует ли программа из скрипта Bash?
Как бы я проверил, что программа существует, таким образом, что либо вернет ошибку и выйдет, либо продолжит работу со сценарием?
Кажется, это должно быть легко, но это меня озадачило.
43 ответа
Ответ
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'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; }
(Незначительное примечание: некоторые предложат 2>&-
та же 2>/dev/null
но короче - это неправда. 2>&-
закрывает FD 2, который вызывает ошибку в программе, когда она пытается записать в stderr, что сильно отличается от успешной записи в нее и отбрасывания вывода (и опасно!))
Если ваш хэш взрыва /bin/sh
тогда вы должны заботиться о том, что говорит POSIX. type
а также hash
Коды выхода не очень хорошо определены в POSIX, и hash
видно, что успешно завершается, когда команда не существует (не видел это с type
еще). command
Состояние выхода хорошо определено POSIX, так что, вероятно, наиболее безопасным для использования.
Если ваш скрипт использует bash
Тем не менее, правила POSIX больше не имеют значения, и оба type
а также hash
стать совершенно безопасным в использовании. type
теперь имеет -P
искать только PATH
а также hash
имеет побочный эффект, что расположение команды будет хэшировано (для более быстрого поиска в следующий раз, когда вы ее используете), что, как правило, хорошо, так как вы, вероятно, проверяете ее существование для фактического использования.
В качестве простого примера, вот функция, которая запускается gdate
если он существует, в противном случае date
:
gnudate() {
if hash gdate 2>/dev/null; then
gdate "$@"
else
date "$@"
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
, dash возвращает первое, даже если последнее будет выполнено. Это ошибка, которая нарушает стандарт POSIX. [ Отчет об ошибке] [ Стандарт]
Кроме того, произойдет сбой, если искомая команда была определена как псевдоним.
Я согласен с lhunath, чтобы препятствовать использованию which
и его решение идеально подходит для пользователей BASH. Тем не менее, чтобы быть более портативным, command -v
должен использоваться вместо:
$ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed. Aborting." >&2; exit 1; }
команда command
является POSIX-совместимым, см. здесь для его спецификации: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html
Замечания: type
соответствует POSIX, но type -P
не является.
Это зависит от того, хотите ли вы знать, существует ли он в одном из каталогов в $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
программа.
У меня есть функция, определенная в моем.bashrc, которая делает это проще.
command_exists () {
type "$1" &> /dev/null ;
}
Вот пример того, как он используется (из моего .bash_profile
.)
if command_exists mvim ; then
export VISUAL="mvim --nofork"
fi
Расширяя ответы @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 ]
Из man-страницы bash в разделе Условные выражения:
-x file True if file exists and is executable.
Использовать hash
, как предполагает @lhunath, в скрипте bash:
hash foo &> /dev/null
if [ $? -eq 1 ]; then
echo >&2 "foo not found."
fi
Этот скрипт запускается hash
и затем проверяет, является ли код завершения самой последней команды значением, сохраненным в $?
, равно 1
, Если hash
не находит foo
код выхода будет 1
, Если foo
присутствует, код выхода будет 0
,
&> /dev/null
перенаправляет стандартную ошибку и стандартный вывод с hash
так что это не появляется на экране и echo >&2
пишет сообщение к стандартной ошибке.
Команда -v работает нормально, если для POSIX_BUILTINS установлена опция <command>
проверить, но может потерпеть неудачу, если нет. (он работал у меня годами, но недавно столкнулся с тем, где он не работал).
Я считаю следующее более надежным:
test -x $(which <command>)
Так как он проверяет 3 вещи: путь, выполнение и разрешение.
Здесь есть множество вариантов, но я не удивился, когда не было быстрых однострочников, вот что я использовал в начале своих сценариев:
[[ "$(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 foo --version >/dev/null 2>&1; then
echo Found
else
echo Not found
fi
Это более надежная проверка того, что программа запускается, чем просто просмотр каталогов PATH и прав доступа к файлам.
Кроме того, вы можете получить полезный результат от вашей программы, например, ее версию.
Конечно, недостатки заключаются в том, что некоторые программы могут быть тяжелыми для запуска, а некоторые не имеют --version
возможность немедленно (и успешно) выйти.
Это могло быть проще, просто:
#!/usr/bin/env bash
set -x
# if local program 'foo' returns 1 (doesn't exist) then...
if ! type -P foo; then
echo 'crap, no foo'
else
echo 'sweet, we have foo!'
fi
+ Изменить foo
к vi
чтобы запустить другое условие.
Проверьте наличие нескольких зависимостей и сообщите статус конечным пользователям.
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
Образец вывода:
latex OK
pandoc missing
Настроить 10
до максимальной длины команды. Не автоматический, потому что я не вижу не многословного POSIX способа сделать это: Как выровнять столбцы разделенной пробелами таблицы в Bash?
Я никогда не заставлял вышеупомянутые решения работать на коробке, к которой у меня есть доступ. Для одного, тип был установлен (делает то, что делает больше). Так что встроенная директива необходима. Эта команда работает для меня:
if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi
hash foo 2>/dev/null
: работает с Zsh, Bash, Dash и Ash.
type -p foo
: он работает с zsh, bash и ash (busybox), но не с d ash (интерпретирует -p
в качестве аргумента).
command -v foo
: работает с zsh, bash, dash, но не с ash (busybox) (-ash: command: not found
).
Также обратите внимание, что builtin
недоступно с ash
а также dash
,
Я хотел получить ответ на тот же вопрос, но для запуска в Makefile.
install:
@if [[ ! -x "$(shell command -v ghead)" ]]; then \
echo 'ghead does not exist. Please install it.'; \
exit -1; \
fi
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
Хорошая вещь в том, что он выясняет, доступен ли исполняемый файл в среде, в которой он запущен, - избавляет от нескольких проблем...
-Адам
Почему бы не использовать встроенные функции Bash, если можете?
which programname
...
type -P programname
только , но очень полезно для написания сценариев (например, при написании сценариев завершения):
В
zsh/parameter
модуль предоставляет доступ, помимо прочего, к внутренним
commands
хеш-таблица. Из
man zshmodules
:
THE ZSH/PARAMETER MODULE
The zsh/parameter module gives access to some of the internal hash ta‐
bles used by the shell by defining some special parameters.
[...]
commands
This array gives access to the command hash table. The keys are
the names of external commands, the values are the pathnames of
the files that would be executed when the command would be in‐
voked. Setting a key in this array defines a new entry in this
table in the same way as with the hash builtin. Unsetting a key
as in `unset "commands[foo]"' removes the entry for the given
key from the command hash table.
Хотя это загружаемый модуль, кажется, что он загружается по умолчанию, если
zsh
не используется с
--emulate
.
пример:
martin@martin ~ % echo $commands[zsh]
/usr/bin/zsh
Чтобы быстро проверить, доступна ли определенная команда, просто проверьте, существует ли ключ в хеше:
if (( ${+commands[zsh]} ))
then
echo "zsh is available"
fi
Обратите внимание, что хеш будет содержать любые файлы в
$PATH
папки, независимо от того, исполняемые они или нет. Чтобы быть абсолютно уверенным, вы должны потратить
stat
позвонить по этому поводу:
if (( ${+commands[zsh]} )) && [[ -x $commands[zsh] ]]
then
echo "zsh is available"
fi
Для тех, кто заинтересован, ни одна из вышеуказанных методологий не работает, если вы хотите обнаружить установленную библиотеку. Я полагаю, что вам остается либо физически проверить путь (возможно, для заголовочных файлов и тому подобное), либо что-то вроде этого (если вы находитесь в дистрибутиве на основе Debian):
dpkg --status libdb-dev | grep -q not-installed
if [ $? -eq 0 ]; then
apt-get install libdb-dev
fi
Как видно из приведенного выше, ответ на запрос "0" означает, что пакет не установлен. Это функция "grep" - "0" означает, что совпадение найдено, "1" означает, что совпадение не найдено.
Он сообщит в зависимости от местоположения, если программа существует или нет
if [ -x /usr/bin/yum ]; then
echo This is Centos
fi
Я бы сказал, что нет портативного и 100% надежного способа из-за висячего alias
эс. Например:
alias john='ls --color'
alias paul='george -F'
alias george='ls -h'
alias ringo=/
Конечно, проблематично только последнее (без обид на Ринго!), Но все они действительны alias
с точки зрения command -v
,
Для того, чтобы отказаться от таких как ringo
надо разобрать вывод встроенной оболочки alias
командуй и вербуйся в них (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'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
По крайней мере, в Mac OS X 10.6.8 с использованием Bash 4.2.24(2) command -v ls
не соответствует переехал /bin/ls-temp
,
Подражать Башу 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'
#!/bin/bash
a=${apt-cache show program}
if [[ $a == 0 ]]
then
echo "the program doesn't exist"
else
echo "the program exists"
fi
#program не является буквальным, вы можете изменить его на имя программы, которую хотите проверить
Я второй использование "команда -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
Если вы хотите проверить, существует ли программа и является ли она программой, а не встроенной командой bash, тогда command
, type
а также hash
не подходят для тестирования, так как все они возвращают 0 выход для встроенных команд.
Например, есть программа времени, которая предлагает больше функций, чем встроенная команда времени. Чтобы проверить, существует ли программа, я бы предложил использовать 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
Мои настройки для сервера 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
, Просто посмотрите на этот бред сомнамбула, вот что действительно происходит, когда вы запускаете $(подкоманда):
Первый. Это может дать вам совершенно другой вывод.
$ 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