Как проверить, существует ли программа из скрипта 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
Другие вопросы по тегам