часть - Как проверить, существует ли программа из сценария 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?







bash