如何从Bash脚本检查程序是否存在?

时间:2009-02-26 21:52:49

标签: bash

如何验证程序是否存在,以某种方式返回错误并退出,或继续使用脚本?

看起来应该很容易,但它一直在困扰我。

39 个答案:

答案 0 :(得分:2624)

答案

POSIX兼容:

command -v <the_command>

对于bash特定环境:

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

说明

避免使用which。它不仅是一个外部过程,你只是做了很少的事情(意味着像hashtypecommand这样的内置更便宜),你也可以依靠内置来实现做你想做的事,而外部命令的影响很容易因系统而异。

为什么要关心?

  • 许多操作系统都有一个which 甚至没有设置退出状态,这意味着if which foo甚至无法在那里工作,并且总是< / strong>报告foo存在,即使它不存在(请注意,某些POSIX shell似乎也为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所说的内容。 POSIX没有很好地定义typehash的退出代码,并且当命令不存在时,hash被视为成功退出(没有看到{ {1}}。 type的退出状态由POSIX很好地定义,因此可能是最安全的。

如果您的脚本使用command,则POSIX规则不再重要,bashtype变得非常安全。 hash现在只有type来搜索-PPATH有副作用,该命令的位置将被哈希(以便下次使用时更快查找) ,这通常是一件好事,因为你可能会检查它的存在,以便实际使用它。

举一个简单的例子,这是一个运行hash的函数(如果存在),否则gdate

date

答案 1 :(得分:401)

以下是检查$PATH 中是否存在命令以及是否可执行的可移植方式:

[ -x "$(command -v foo)" ]

示例:

if ! [ -x "$(command -v git)" ]; then
  echo 'Error: git is not installed.' >&2
  exit 1
fi

需要执行可执行文件检查,因为如果在$PATH中找不到具有该名称的可执行文件,bash将返回非可执行文件。

另请注意,如果$PATH中先前存在与可执行文件同名的非可执行文件,则破折号将返回前者,即使后者将被执行。这是一个错误,违反了POSIX标准。 [Bug report] [Standard]

此外,如果您要查找的命令已被定义为别名,则此操作将失败。

答案 2 :(得分:196)

我同意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不符合。

答案 3 :(得分:84)

我的.bashrc中定义了一个函数,使这更容易。

command_exists () {
    type "$1" &> /dev/null ;
}

以下是一个如何使用它的示例(来自我的.bash_profile。)

if command_exists mvim ; then
    export VISUAL="mvim --nofork"
fi

答案 4 :(得分:73)

这取决于您是否想知道它是否存在于$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程序的输出。

答案 5 :(得分:31)

扩展@ lhunath&#39;和@ 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

答案 6 :(得分:21)

尝试使用:

test -x filename

[ -x filename ]

来自Conditional Expressions下的bash联机帮助页:

 -x file
          True if file exists and is executable.

答案 7 :(得分:15)

在bash脚本中使用hash@lhunath suggests

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会将消息写入标准错误。

答案 8 :(得分:10)

我从来没有让上述解决方案在我可以访问的盒子上工作。例如,已安装类型(执行更多操作)。所以需要内置指令。这个命令对我有用:

if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi

答案 9 :(得分:8)

如果检查程序是否存在,您可能会在以后运行它。为什么不尝试首先运行它?

if foo --version >/dev/null 2>&1; then
    echo Found
else
    echo Not found
fi

这是一个更值得信赖的检查程序运行,而不仅仅是查看PATH目录和文件权限。

另外,您可以从程序中获得一些有用的结果,例如它的版本。

当然缺点是某些程序可能很重要,有些程序没有--version选项可以立即(并成功)退出。

答案 10 :(得分:7)

检查多个依赖项并告知最终用户状态

for cmd in latex pandoc; do
  printf '%-10s' "$cmd"
  if hash "$cmd" 2>/dev/null; then
    echo OK
  else
    echo missing
  fi
done

示例输出:

latex     OK
pandoc    missing

10调整为最大命令长度。不自动,因为我没有看到非冗长的POSIX方式: How to align the columns of a space separated table in Bash?

答案 11 :(得分:6)

如果可以,为什么不使用Bash builtins?

which programname

...

type -P programname

答案 12 :(得分:6)

hash foo 2>/dev/null :适用于zsh,bash,dash和ash。

type -p foo:它似乎与zsh,bash和ash(busybox)一起使用,但不是破折号(它将-p解释为参数)。

command -v foo:适用于zsh,bash,dash,但不适用于ash(busybox)(-ash: command: not found)。

另请注意,builtinash无法使用dash

答案 13 :(得分:4)

对于那些感兴趣的人,如果您希望检测已安装的库,则上述方法都不起作用。我想你会留下物理检查路径(可能是头文件等),或类似的东西(如果你在基于Debian的发行版上):

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

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

从上面可以看出,查询中的“0”答案表示未安装包。这是“grep”的函数 - “0”表示找到匹配,“1”表示未找到匹配。

答案 14 :(得分:3)

which命令可能很有用。 man 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

关于它的好处是,它确定可执行文件是否在运行的环境中可用 - 节省了一些问题......

- 亚当

答案 15 :(得分:3)

我认为由于悬空alias而没有便携式和100%可靠的方式。例如:

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

当然只有最后一个是有问题的(对Ringo没有冒犯!)但从alias的角度来看,所有这些都是有效的command -v

为了拒绝像ringo这样的悬空,我们必须解析shell内置alias命令的输出并递归到它们中(command -v并不优于{{ 1}}这里。)没有便携式解决方案,即使是特定于Bash的解决方案也相当繁琐。

请注意,此类解决方案将无条件拒绝alias

alias ls='ls -F'

答案 16 :(得分:2)

如果没有可用的外部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不匹配。

答案 17 :(得分:1)

zsh,但对zsh 脚本编写非常有用(例如,在编写完成脚本时):

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

答案 18 :(得分:1)

哈希变体有一个缺陷:在命令行上,您可以输入

one_folder/process

执行流程。为此,one_folder的父文件夹必须位于 $ PATH 中。但是当你尝试散列这个命令时,它总会成功:

hash one_folder/process; echo $? # will always output '0'

答案 19 :(得分:1)

为了模仿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; }

答案 20 :(得分:1)

我的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
}

答案 21 :(得分:1)

这可能更简单,只是:

#!/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,以激发其他条件。

答案 22 :(得分:1)

我希望回答相同的问题,但要在Makefile中运行。

install:
    @if [[ ! -x "$(shell command -v ghead)" ]]; then \
        echo 'ghead does not exist. Please install it.'; \
        exit -1; \
    fi

答案 23 :(得分:1)

如果你想检查程序是否存在并且确实是程序,而不是bash内置命令,那么commandtype和{{1不适合测试,因为它们都为内置命令返回0退出状态。

例如,时间程序提供的功能多于时间内置命令。要检查程序是否存在,我建议使用hash,如下例所示:

which

答案 24 :(得分:1)

我第二次使用“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

答案 25 :(得分: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; }

这是基于此处选择的答案和另一个来源(和我一起玩的情况)。

希望这对其他人会很方便。

答案 26 :(得分:1)

它将根据位置告诉程序是否存在

if [ -x /usr/bin/yum ]; then
    echo This is Centos
fi

答案 27 :(得分:0)

脚本

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

答案 28 :(得分:0)

最新答案,但这就是我最终要做的事情。

我只是检查我执行的命令是否返回错误代码。如果返回0,则表示已安装程序。此外,您可以使用它来检查脚本的输出。以这个脚本为例。

foo.sh

#!/bin/bash
echo "hello world"
exit 1 # throw some error code

示例:

# outputs something bad... and exits
bash foo.sh $? -eq 0 || echo "something bad happened. not installed" ; exit 1

# does NOT outputs nothing nor exits because dotnet is installed on my machine
dotnet --version $? -eq 0 || echo "something bad happened. not installed" ; exit 1

基本上,所有这些操作都是检查命令运行的退出代码。 即使命令退出代码不为0,此问题上最被接受的答案也将返回true。

答案 29 :(得分:0)

假设您已经遵循安全的shell做法:https://sipb.mit.edu/doc/safe-shell/

set -eu -o pipefail
shopt -s failglob

./dummy --version 2>&1 >/dev/null

这假定可以以几乎不执行任何操作的方式调用该命令,例如报告其版本或显示帮助。

如果未找到dummy命令,bash将退出,并显示以下错误...

./my-script: line 8: dummy: command not found

这是比其他command -v(和类似的答案)更有用且更简洁的方法,因为错误消息是自动生成的,并且还包含相关的行号。

答案 30 :(得分:0)

嘿:

if [[ `command --help` ]]; then
  echo "This command exists"
else
  echo "This command does not exist";
fi

在if检查中放置一个工作开关,例如“-help” “-v” if [[{command --help]] ;然后

答案 31 :(得分:0)

如果为<command>设置了POSIX_BUILTINS选项以进行测试,则

命令-v可以正常工作,但是如果没有,则该命令可能会失败。 (它对我有用了很多年,但最近遇到了一个不起作用的地方)。

我发现以下内容可以更好地防止故障:

test -x $(which <command>)

因为它测试了三件事:路径,存在和执行权限。

答案 32 :(得分:0)

我使用它是因为它很容易:

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内置程序并将程序回显状态编程到stdout而对stderr没有任何内容,它只对stderr发回状态。

答案 33 :(得分:-1)

GIT=/usr/bin/git                     # STORE THE RELATIVE PATH
# GIT=$(which git)                   # USE THIS COMMAND TO SEARCH FOR THE RELATIVE PATH

if [[ ! -e $GIT ]]; then             # CHECK IF THE FILE EXISTS
    echo "PROGRAM DOES NOT EXIST."
    exit 1                           # EXIT THE PROGRAM IF IT DOES NOT
fi

# DO SOMETHING ...

exit 0                               # EXIT THE PROGRAM IF IT DOES

答案 34 :(得分:-1)

我必须检查是否已安装git作为部署CI服务器的一部分。我的最终bash脚本如下(Ubuntu服务器):

if ! builtin type -p git &>/dev/null; then
  sudo apt-get -y install git-core
fi

希望这有助于其他人!

答案 35 :(得分:-1)

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

答案 36 :(得分:-2)

@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

答案 37 :(得分:-2)

我只想用--version--helpcheck if the command succeeded or failed来调用程序

如果找不到程序,则与set -e脚本一起使用的脚本将退出,并且您将收到一条有意义的错误消息:

#!/bin/bash
set -e
git --version >> /dev/null

答案 38 :(得分:-2)

我无法让其中一个解决方案起作用,但经过一段时间的编辑后,我想出了这个。哪个适合我:

dpkg --get-selections | grep -q linux-headers-$(uname -r)

if [ $? -eq 1 ]; then
        apt-get install linux-headers-$(uname -r)
fi