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

我将如何以返回错误并退出或继续执行脚本的方式来验证程序是否存在?

看起来应该很容易,但是一直让我很沮丧。

答案

回答

兼容 POSIX:

command -v <the_command>

对于特定于bash环境:

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

说明

避免which 。它不仅是您为执行很少工作而启动的外部进程(这意味着诸如hashtypecommand type内建hash要便宜得多),您还可以依靠内建函数实际执行所需的操作,而外部命令的效果却可以随系统的不同而变化。

为什么要在意呢?

  • 许多操作系统有一个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 所说的内容。 typehash的退出代码不是由 POSIX 很好地定义的,并且当命令不存在时, hash可以成功退出(尚未在type看到)。 POSIX 很好地定义了command的退出状态,因此可能最安全地使用它。

如果您的脚本使用bash ,则 POSIX 规则不再重要, typehash变得非常安全。 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

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

还要注意,如果$PATH之前存在与可执行文件同名的不可执行文件,则 dash 会返回前者,即使后者将被执行。这是一个错误,并且违反了 POSIX 标准。 [ 错误报告 ] [ 标准 ]

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

我同意不鼓励使用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不兼容。

我在. bashrc 中定义了一个函数,可以使此操作更加容易。

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

这是一个用法示例(来自我的.bash_profile

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

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

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

条件表达式下的 bash 联机帮助页中:

-x file
          True if file exists and is executable.

要在 bash 脚本中使用@lhunath 建议的 hash ,请执行以下操作

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

我从未获得上述解决方案在我可以访问的盒子上工作。首先,已经安装了 type(执行其他操作)。因此需要内置指令。此命令对我有用:

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

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

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 方法: 如何在 Bash 中对齐以空格分隔的表的列?

检查dpkg -s是否安装了一些apt软件包,否则安装它们

请参阅: 检查是否已安装 apt-get 软件包,然后在 Linux 上未安装的情况下进行安装?

以前在以下位置提到过: https : //stackoverflow.com/a/2075386/895245