часть - Как проверить, существует ли программа из сценария Bash?
полезные bash скрипты (20)
Как я могу проверить, существует ли программа, которая будет либо возвращать ошибку, либо выходить из нее, либо продолжать ее?
Похоже, это должно быть легко, но это меня колотило.
Ответ
Совместимость с POSIX:
command -v <the_command>
Для конкретных условий:
hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords
объяснение
Избегайте. Мало того, что это внешний процесс, который вы запускаете для выполнения очень мало (это означает, что встроенные hash
, такие как hash
, type
или command
, намного дешевле), вы также можете полагаться на встроенные функции, чтобы фактически делать то, что вы хотите, в то время как эффекты внешних команд могут легко меняются от системы к системе.
Зачем заботиться?
- Многие операционные системы имеют тот, который даже не устанавливает статус выхода , а это означает, что
if which foo
даже не работает и всегда будет сообщать о том, чтоfoo
существует, даже если это не так (обратите внимание, что некоторые оболочки POSIX это дляhash
тоже). - Многие операционные системы делают,
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
). статус выхода команды хорошо определен POSIX, так что один из них, вероятно, самый безопасный в использовании.
Если ваш скрипт использует 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
}
Во-вторых, использование команды -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 -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
Если вы проверяете существование программы, вы, вероятно, собираетесь запустить ее позже. Почему бы не попытаться запустить его в первую очередь?
if foo --version >/dev/null 2>&1; then
echo Found
else
echo Not found
fi
Это более надежная проверка, что программа работает, а не просто просмотр каталогов PATH и разрешений файлов.
Кроме того, вы можете получить полезный результат из своей программы, например, ее версию.
Конечно, недостатки в том, что некоторые программы могут быть тяжелыми для запуска, а некоторые не имеют опции --version
для немедленного (и успешного) выхода.
Если не существует команды внешнего type
(как принято here ), мы можем использовать 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
.
Здесь много вариантов, но я не удивлялся быстрым однострочным установкам, это то, что я использовал в начале моих скриптов: [[ "$(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; }
это основано на выбранном ответе здесь и на другом источнике (и я немного играю вокруг).
надеюсь, что это будет полезно для других.
Команда -v работает нормально, если для параметра <command>
задана опция POSIX_BUILTINS, но может быть неудачной, если нет. (он работал для меня в течение многих лет, но недавно столкнулся с тем, где он не работал).
Я считаю следующее более стойким к отказу:
test -x $(which <command>)
Поскольку он проверяет на 3 вещи: путь, исполнение и разрешение.
Ниже приведен переносимый способ проверить, существует ли команда в $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. [ Отчет об ошибке ] [ Standard ]
Кроме того, это не сработает, если команда, которую вы ищете, была определена как псевдоним.
Почему бы не использовать встроенные Bash, если можно?
which programname
...
type -P programname
Развернувшись на ответах @ 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
Удивительный ответ и объяснение от @lhunath. Спас мой день. Я немного расширил его. Не мог контролировать себя, разделяя его - надеясь, что это может быть полезно кому-то. Если кому-то нужно проверить (массив) несколько программ, вот быстрый фрагмент.
Что он делает? (1) Чтение массива программ. (2) Показать сообщение для неудавшейся программы. (3) Запросить пользователя для продолжения (форсирование цикла) параметров y / n для проверки остальной части программ.
#!/bin/bash
proginstalldir=/full/dir/path/of/installation
progsbindir=$proginstalldir/bin
echo -e "\nMy install directory - $proginstalldir"
echo -e "My binaries directory - $progsbindir"
VerifyInstall () {
clear
myprogs=( program1 program2 program3 program4 program5 programn );
echo -e "\nValidation of my programs started...."
for ((i=0; i<${#myprogs[@]}; i++)) ; do
command -v $progsbindir/${myprogs[i]} >/dev/null && echo -e "Validating....\t${myprogs[i]}\tSUCCESSFUL" || { echo -e "Validating.... \t${myprogs[i]}\tFAILED" >&2;
while true; do
printf "%s: " "ERROR.... Validation FAILED for ${myprogs[i]} !!!! Continue?"; read yn;
case $yn in [Yy] ) echo -e "Please wait..." ; break;;
[Nn]) echo -e "\n\n#################################\n## Validation Failed .. !! ##\n#################################\n\n" ; exit 1; break;;
*) echo -e "\nPlease answer y or n then press Enter\n"; esac; done; >&2; }; done
sleep 2
}
VerifyInstall
Хэш-вариант имеет одну ошибку: в командной строке вы можете, например, ввести
one_folder/process
для выполнения процесса. Для этого родительская папка one_folder должна находиться в $ PATH . Но когда вы пытаетесь хешировать эту команду, она всегда будет успешной:
hash one_folder/process; echo $? # will always output '0'
Чтобы подражать type -P cmd
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; }
Это зависит от того, хотите ли вы знать, существует ли она в одной из каталогов $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
программы.
Я использую очень удобную и короткую версию:
dpkg -s curl 2>/dev/null >/dev/null || apt-get -y install curl
Так просто, если нужно проверить только одну программу.
Я использую это, потому что это очень просто:
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.
Я согласен с lhunath, чтобы препятствовать их использованию, и его решение отлично подходит для пользователей 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
- нет.
моя настройка для сервера 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
}
hash foo 2>/dev/null
: работает с zsh, bash, тире и золой.
type -p foo
: он работает с zsh, bash и ash (busybox), но не тире (он интерпретирует -p
как аргумент).
command -v foo
: работает с zsh, bash, тире, но не ash (busybox) ( -ash: command: not found
).
Также обратите внимание, что builtin
не доступен с ash
и dash
.
Проверьте наличие нескольких зависимостей и сообщите статус конечным пользователям.
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?