Как проверить, существует ли программа из сценария Bash?

Как я могу проверить, что программа существует, таким образом, чтобы либо вернуть ошибку и выйти, либо продолжить сценарий?

кажется, это должно быть легко, но это ставило меня в тупик.

30 ответов


ответ

совместимость с POSIX:

command -v <the_command>

на bash особых условиях:

hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords

объяснение

избежать which. Мало того, что это внешний процесс, который вы запускаете, делая очень мало (что означает builtins, такие как hash, type или command путь дешевле), вы можете также положиться на builtins фактически для того чтобы сделать чего вы хотите, пока влияния внешних команд могут легко поменять от системы к система.

зачем?

  • многие операционные системы имеют 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. [сообщить об ошибке] [стандартный]

кроме того, это не удастся, если команда, которую вы ищете, была определена в качестве псевдонима.


Я согласен с лхунатхом, чтобы препятствовать использованию 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 нет.


у меня есть функция, определенная в мой .bashrc, что делает это проще.

command_exists () {
    type "" &> /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/ в первом примере подавляет вывод


расширяя ответы @lhunath и @GregV, вот код для людей, которые хотят легко поместить эту проверку внутри if о себе:

exists()
{
  command -v "" >/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 manpage под Условные Выражения:

 -x file
          True if file exists and is executable.

использовать hash, as @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 записывает сообщение в стандартную ошибку.


Я никогда не получал выше решения для работы на поле у меня есть доступ. Во-первых, тип был установлен (что делает больше). Поэтому необходима директива builtin. Эта команда работает для меня:

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

это более надежная проверка, что программа работает, чем просто глядя на каталоги пути и разрешения файлов.

плюс вы можете получить полезный результат от вашей программы, такие как версия.

конечно, недостатки в том, что некоторые программы могут быть тяжелыми для запуска, а некоторые не имеют для немедленно (и успешно) выйти.


почему бы не использовать bash builtins, если вы можете?

which programname

...

type -P programname

проверьте наличие нескольких зависимостей и сообщите статус конечным пользователям

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?


для тех, кто заинтересован, ни одна из вышеперечисленных методологий не работает, если вы хотите обнаружить установленную библиотеку. Я предполагаю, что вам остается либо физически проверить путь (потенциально для заголовочных файлов и тому подобного), либо что-то вроде этого (если вы находитесь в дистрибутиве на основе Debian):

dpkg --status libdb-dev | grep -q not-installed

if [ $? -eq 0 ]; then
    apt-get install libdb-dev
fi

Как вы можете видеть из вышеизложенного, ответ "0" из запроса означает, что пакет не установлен. Это функция "grep" - " 0 "означает, что совпадение было найдено," 1 " означает, что совпадение не было найдено.


hash foo 2>/dev/null: работы с ЗШ, Баш, черточкой и золой.

type -p foo: похоже, он работает с zsh, bash и ash (busybox), но не dash (он интерпретирует -p в качестве аргумента).

command -v foo: работает с zsh, bash, dash, но не ash (busybox) (-ash: command: not found).

также обратите внимание, что builtin не работает с ash и dash.


на 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

хорошая вещь о том, что он выясняет, доступен ли исполняемый файл в среде, в которой он запущен, - экономит несколько проблем...

Адам


Я бы сказал, что нет портативного и 100% надежного способа из-за болтающихся aliases. Например:

alias john='ls --color'
alias paul='george -F'
alias george='ls -h'
alias ringo=/

конечно, только последний из них проблематичен (не обижайтесь на Ринго!) Но все они действительны aliasES с точки зрения command -v.

для того, чтобы отклонить болтающиеся, как ringo, мы должны проанализировать выход встроенной оболочки и рекурсия в них (command -v не превосходит alias здесь.) Там нет портативных решение для него, и даже Bash-специфическое решение довольно утомительно.

обратите внимание, что такое решение будет безоговорочно отклонять alias ls='ls -F'

test() { command -v  | 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="" 
      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
   ' _ "" || 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'

Я поддерживаю использование "command-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  > /dev/null
}

function _apt_install_norecommends() {
    apt-get install -y --no-install-recommends  > /dev/null
}
function _apt_available() {
    if [ `apt-cache search  | grep -o "" | uniq | wc -l` = "1" ]; then
        echo "Package is available : "
        PACKAGE_INSTALL="1"
    else
        echo "Package  is NOT available for install"
        echo  "We can not continue without this package..."
        echo  "Exitting now.."
        exit 0
    fi
}
function _package_install {
    _apt_available 
    if [ "${PACKAGE_INSTALL}" = "1" ]; then
        if [ "$(dpkg-query -l  | tail -n1 | cut -c1-2)" = "ii" ]; then
             echo  "package is already_installed: "
        else
            echo  "installing package : , please wait.."
            _apt_install 
            sleep 0.5
        fi
    fi
}

function _package_install_no_recommends {
    _apt_available 
    if [ "${PACKAGE_INSTALL}" = "1" ]; then
        if [ "$(dpkg-query -l  | tail -n1 | cut -c1-2)" = "ii" ]; then
             echo  "package is already_installed: "
        else
            echo  "installing package : , please wait.."
            _apt_install_norecommends 
            sleep 0.5
        fi
    fi
}

Если вы, ребята, не можете заставить вещи выше / ниже работать и вытаскивать волосы из спины, попробуйте выполнить ту же команду, используя bash -c. Просто посмотрите на этот somnambular бред, это то, что на самом деле происходит, когда вы запустите $(подкоманды):

первый. Это может дать вам совершенно другой выход.

$ 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 статус выхода для встроенных команд.

например, есть времени программа, которая предлагает больше возможностей, чем времени встроенная команда. Чтобы проверить, существует ли программа, Я бы предложил использовать 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; }

это основано на выбранном ответе здесь и другом источнике (и я немного играю).

надеюсь, что это будет удобно для других.


checkexists() {
    while [ -n "" ]; do
        [ -n "$(which "")" ] || echo "": command not found
        shift
    done
}

Я использую это, потому что это очень легко:

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 с другой стороны, если команда не найдена, она повторяет статус только для stderr.


скрипт

#!/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=; 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.

command-v отлично работает, если параметр POSIX_BUILTINS установлен для <command> для проверки, но может потерпеть неудачу, если нет. (он работал для меня в течение многих лет, но недавно столкнулся с тем, где он не работал).

Я считаю, что следующее является более отказоустойчивым:

test -x $(which <command>)

поскольку он проверяет 3 вещи: путь, выполнение и разрешение.


Я использую очень удобную и короткую версию:

dpkg -s curl 2>/dev/null >/dev/null || apt-get -y install curl

так легко, если нужно проверить только одну программу.


удивительный ответ и объяснение @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