如何验证程序是否存在,以返回错误并退出,或继续执行脚本?

看起来应该很容易,但这让我很为难。


当前回答

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

zsh/parameter模块可以访问内部命令哈希表等。来自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不与--simulate一起使用,它似乎是默认加载的。

例子:

martin@martin ~ % echo $commands[zsh]
/usr/bin/zsh

要快速检查某个命令是否可用,只需检查哈希中是否存在密钥:

if (( ${+commands[zsh]} ))
then
  echo "zsh is available"
fi

请注意,散列将包含$PATH文件夹中的任何文件,无论它们是否可执行。为了绝对确定,您必须对此进行统计:

if (( ${+commands[zsh]} )) && [[ -x $commands[zsh] ]]
then
  echo "zsh is available"
fi

其他回答

hash foo 2>/dev/null:适用于Zshell(Zsh)、Bash、Dash和ash。

type-p foo:它似乎可以使用Z shell、Bash和ash(BusyBox),但不能使用Dash(它将-p解释为一个参数)。

命令-vfoo:适用于Z shell、Bash、Dash,但不适用于ash(BusyBox)(-ash:command:not found)。

还要注意,内置的ash和Dash不可用。

如果没有任何可用的外部类型命令(在这里是理所当然的),我们可以使用符合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 v10.6.8(雪豹)上,使用Bash 4.2.24(2)命令-vls与移动的/bin/ls温度不匹配。

扩展@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

这里有很多选择,但我很惊讶没有快速的一句话。这是我在脚本开始时使用的方法:

[[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; }
[[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; }

这是基于此处选择的答案和另一个来源。

答复

POSIX兼容:

command -v <the_command>

示例用法:

if ! command -v <the_command> &> /dev/null
then
    echo "<the_command> could not be found"
    exit
fi

对于Bash特定环境:

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

解释

避免使用。它不仅是一个外部进程,你启动它的目的很小(这意味着像哈希、类型或命令这样的内置程序要便宜得多),你还可以依赖内置程序来实际执行你想要的操作,而外部命令的效果很容易因系统而异。

为什么要在意?

许多操作系统都有一个甚至不设置退出状态的函数,这意味着if-which foo在那里甚至不起作用,并且总是报告foo存在,即使它不存在(注意,有些POSIX shell似乎也会为hash设置)。许多操作系统都会做一些自定义和邪恶的事情,比如更改输出,甚至挂接到包管理器。

所以,不要使用哪个。请使用以下选项之一:

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时导致错误,这与成功写入stderr并丢弃输出非常不同(而且很危险!))

如果你的hash bang是/bin/sh,那么你应该关心POSIX怎么说。POSIX并没有很好地定义类型和哈希的退出代码,当命令不存在时,可以看到哈希成功退出(还没有看到这种类型)。POSIX很好地定义了命令的退出状态,所以使用它可能是最安全的。

如果您的脚本使用bash,POSIX规则就不再重要了,类型和散列都变得非常安全。type现在有一个-P来搜索PATH,而hash的副作用是命令的位置将被散列(以便下次使用时更快地查找),这通常是一件好事,因为为了实际使用它,您可能会检查它的存在。

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

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

具有完整功能集的替代方案

您可以使用常见的脚本来满足您的需要。

要检查是否安装了某些东西,可以执行以下操作:

checkBin <the_command> || errorMessage "This tool requires <the_command>. Install it please, and then run this tool again."