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

我将如何validation程序是否存在,以何种方式返回错误并退出,还是继续执行脚本?

这似乎应该很容易,但它一直困在我身上。

回答

兼容POSIX:

command -v <the_command> 

对于bash特定的环境:

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

说明

避免which 。 它不仅是一个外部过程,你只需要做很less的事情(意思就是像hashtype或者command那样的内buildhash是便宜的),你也可以依靠内build函数实际做你想做的事,而外部命令的影响可以从系统到系统很容易变化。

为什么要关心

  • 许多操作系统有一个甚至没有设置退出状态 ,这意味着if which foo甚至不会在那里工作,并且总是会报告foo存在,即使它不存在(注意一些POSIX shell似乎做这也是hash )。
  • 许多操作系统使得自定义和恶意的东西,如改变输出,甚至挂钩到包pipe理器。

所以,不要使用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; } 

(小调 – 注意:有些人会build议2>&-是相同的2>/dev/null但是更短 – 这是不真实的 2>&-closuresFD 2,当它试图写入stderr时, ,这与成功写入并丢弃输出(和危险的!)非常不同

如果你的hash bang是/bin/sh那么你应该关心POSIX所说的。 typehash的退出代码不是由POSIX很好地定义的,并且在命令不存在的情况下hash可以成功退出(还没有看到这种type )。 command的退出状态是由POSIX定义的,所以一个可能是最安全的使用。

如果您的脚本使用bash ,POSIX规则不再是真正的问题,并且typehash变得非常安全。 type现在有一个-P来searchPATHhash有副作用,命令的位置将被散列(为了下一次使用它,更快的查找),这通常是一件好事,因为你可能检查它的存在为了实际使用它。

作为一个简单的例子,如果它存在,这是一个运行gdate的函数,否则为date

 gnudate() { if hash gdate 2>/dev/null; then gdate "$@" else date "$@" fi } 

我同意lhunath劝阻使用,而且他的解决scheme对于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 并且是可执行的:

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

例:

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

可执行检查是必需的,因为如果在$PATH找不到具有该名称的可执行文件,则bash将返回一个不可执行的文件。

还要注意,如果在$PATH早些时候存在与可执行文件同名的不可执行文件,则即使后者被执行,破折号也会返回前者。 这是一个错误,违反了POSIX标准。 [ 错误报告 ] [ 标准 ]

另外,如果您正在查找的命令已被定义为别名,则这将失败。

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

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

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

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

这取决于您是否想知道它是否存在于$PATHvariables中的某个目录中,或者您是否知道它的绝对位置。 如果你想知道它是否在$PATHvariables,使用

 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 

在第一个例子中,redirect到/dev/null/将抑制which程序的输出。

扩展@ lhunath's和@ 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 ] 

根据条件expression式的bash manpage:

  -x file True if file exists and is executable. 

如@lhunath所示 ,在一个bash脚本中使用hash

 hash foo &> /dev/null if [ $? -eq 1 ]; then echo >&2 "foo not found." fi 

这个脚本运行hash ,然后检查最近的命令的退出代码,存储在$?的值 ,等于1 。 如果hash没有findfoo ,则退出代码将是1 。 如果foo存在,则退出码将为0

&> /dev/nullredirect标准错误和标准输出,使其不会出现在屏幕上, echo >&2将消息写入标准错误。

我从来没有得到上述解决scheme在我有权访问的框中工作。 首先,types已经安装(做更多的事情)。 所以内build指令是需要的。 这个命令适用于我:

 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 

程序运行比单纯查看PATH目录和文件权限更值得信赖。

另外你可以从你的程序中得到一些有用的结果,比如它的版本。

当然,缺点是一些程序可能很繁重,有些程序不能立即(并成功)退出。

如果可以的话,为什么不使用Bash buildins?

 which programname 

 type -P programname 

对于那些感兴趣的,如果你想检测一个已安装的库,上面的方法都没有工作。 我想你不是在物理上检查path(可能是用于头文件等),或者是类似的东西(如果你是在一个基于Debian的发行版):

 dpkg --status libdb-dev | grep -q not-installed if [ $? -eq 0 ]; then apt-get install libdb-dev fi 

从上面可以看出,查询中的“0”答案表示软件包未安装。 这是“grep”的function – “0”表示find匹配,“1”表示找不到匹配。

检查多个依赖关系并将状态通知最终用户

 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到最大的命令长度。 不是自动的,因为我没有看到一个非详细的方式来做到这一点。

which命令可能是有用的。 男人哪

如果find可执行文件,则返回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%可靠的方式由于悬挂alias 。 例如:

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

当然,只有最后一个是有问题的(Ringo没有冒犯!)但是从command -v的angular度来看,它们都是有效的alias

为了拒绝像ringo这样的悬挂类,我们必须parsingshell内build的alias命令的输出并recursion到它们中( command -v在此没有优于alias )。没有可移植的解决scheme,甚至是Bash具体的解决scheme相当繁琐。

注意像这样的解决scheme将无条件地拒绝alias ls='ls -F'

 test() { command -v $1 | grep -qv alias } 

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

哈希variables有一个缺陷:在命令行上,你可以input例如

 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 

hash foo 2>/dev/null :与zsh,bash,dash和ash一起使用。

type -p foo :它似乎与zsh,bash和ash(busybox)一起工作,但不是短划线(它将-p解释为参数)。

command -v foo :使用zsh,bash,破折号,但不是灰(busybox)(- -ash: command: not found )。

另外请注意, builtin ashdash不可用。

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

至less在Mac OS X 10.6.8上使用Bash 4.2.24(2) command -v ls与移动的/bin/ls-temp不匹配。

如果你们不能把上面/下面的东西拿来工作,把头发拉出来,试着用bash -c来运行相同的命令。 看看这个梦幻般的deli妄,当你运行$(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 
 checkexists() { while [ -n "$1" ]; do [ -n "$(which "$1")" ] || echo "$1": command not found shift done } 

我的设置为debian服务器。 我有一个问题,当多个包包含相同的名称。 例如apache2。 所以这是我的解决scheme。

 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 } 

我使用这个,因为它很容易:

 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内置和程序回显状态标准输出,而另一方面,如果没有发现一个命令stderr,它只响应stderr状态。

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

例如,有时间程序比时间内置命令提供更多的function。 要检查程序是否存在,我会build议使用如下例所示:

 # 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 
 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 

脚本

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

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

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

希望这可以帮助别人!

我不能得到一个解决scheme的工作,但编辑一点后,我想出了这个。 哪些适合我:

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