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


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

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


1582
2018-02-26 21:52


起源




答案:


回答

POSIX兼容:

command -v <the_command>

对于 bash 特定环境:

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

说明

避免 which。它不仅是一个外部过程,你只是做很少的事情(意味着内置的像 hashtype 要么 command 更便宜的方式),你也可以依靠内置实际做你想做的事情,而外部命令的效果很容易因系统而异。

为何关心?

  • 许多操作系统都有 which 那 甚至没有设置退出状态,意思是 if which foo 甚至不会在那里工作 总是 报告说 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所说的。 type 和 hashPOSIX的退出代码并没有很好地定义,并且 hash 当命令不存在时,可以看到成功退出(没有看到这个 type 然而)。 commandPOSIX很好地定义了退出状态,因此最安全的可能是最安全的。

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

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

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

2286
2018-03-24 12:45



@Geert:当'foo'不存在时,&> / dev / null部分会隐藏消息'type'。 echo上的>&2确保将错误消息发送到标准错误而不是标准输出;因为这是惯例。它们都出现在您的终端上,但标准错误肯定是错误消息和意外警告的首选输出。 - lhunath
例如,-P标志在'sh'中不起作用 stackoverflow.com/questions/2608688/... - momeara
对于那些不熟悉bash中“高级”i / o重定向的人:1) 2>&-  (“关闭输出文件描述符2”,这是stderr) 与...有相同的结果 2> /dev/null; 2) >&2 是一个快捷方式 1>&2,您可能会将其识别为“将stdout重定向到stderr”。见 高级Bash脚本编制指南i / o重定向页面 了解更多信息。 - mikewaters
此解决方案不适用于FreeBSD / sh。 FreeBSD上的hash总是以0的代码返回 - jyavenard
@mikewaters 2>&- 是 不 同样的 2>/dev/null。前者关闭文件描述符,而后者只是将其重定向到 /dev/null。您可能看不到错误,因为程序试图通过stderr通知您stderr已关闭。 - nyuszika7h


以下是检查命令是否存在的可移植方法 $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标准。 [错误报告] [[标准]

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


239
2017-11-05 14:33



我喜欢这个答案,因为测试很容易在脚本中使用。它不需要创建功能。 - Stephen Ostermiller
将 command -v 甚至为非可执行文件生成路径?也就是说,-x真的有必要吗? - einpoklum
@einpoklum -x 测试文件是可执行的,问题是什么。 - Ken Sharp
@KenSharp:但这似乎是多余的,因为 command 将自己测试它的可执行性 - 不是吗? - einpoklum
@einpoklum是的,这是必要的。实际上,即使这种解决方案也可能在一个边缘情况下破裂。谢谢你引起我的注意。 dash,bash和zsh都跳过了非可执行文件 $PATH 执行命令时但是,行为 command -v 是非常不一致的。在dash中,它返回第一个匹配的文件 $PATH,无论它是否可执行。在bash中,它返回第一个可执行匹配项 $PATH,但如果没有,它可以返回一个不可执行的文件。在zsh中,它永远不会返回非可执行文件。 - nyuszika7h


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


190
2018-01-24 18:16



与上述相同 - exit 1; 如果从那里调用,则杀死xterm。 - user unknown
这不适用于标准sh:you&>不是有效的重定向指令。 - jyavenard
@jyavenard:问题被标记了 庆典因此,更简洁的bash特定重定向符号 &>/dev/null。但是,我同意你的意见,真正重要的是可移植性,我已经相应地编辑了我的答案,现在使用标准的sh重定向 >/dev/null 2>&1。 - GregV
为了更好地改进这个答案我会做两件事:1:使用“&>”来简化它,就像Josh的回答一样。 2:为了便于阅读,将{}分成额外的一行,在回声之前放置一个标签 - knocte
如果有人想要它,我只是把这个衬里放入bash功能...... github.com/equant/my_bash_tools/blob/master/tarp.bash - equant


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

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

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

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

81
2017-10-14 09:24



什么是 &> 做? - Saad Malik
该 &>  重定向stdout和stderr 一起。 - Josh Strater
只适用于 type "$1" > /dev/null 2>&1 - Marcello de Sales
&> 可能在您的Bash版本中不可用。 Marcello的代码应该可以正常工作;它做同样的事情。 - Josh Strater


这取决于你是否想知道它是否存在于其中的一个目录中 $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 程序。


66
2018-02-26 22:01



由于我的评论中列出的原因,你真的不应该使用“which”。 - lhunath


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

27
2017-12-07 21:17



几乎尽可能笨拙。阅读 退出状态 man bash 并学习如何使用它。它将使您的代码更简单,更优雅。括号不是其中的一部分 if 语法,它们只是命令的简写 test。 if 检查命令是否成功(退出状态为0)。 - Palec
@Palec:你是对的,它 是 很笨拙。我把它清理了一下,我希望现在看起来更合适。 - Romário
必须奖励学习和提高的意愿。 +1这很干净简单。我唯一能补充的是 command 即使对于别名也能成功,这可能有些违反直觉。检查交互式shell中是否存在会在将其移动到脚本时产生不同的结果。 - Palec
我只是测试和使用 shopt -u expand_aliases 忽略/隐藏别名(比如 alias ls='ls -F' 在另一个答案中提到)和 shopt -s expand_aliases通过解决它们 command -v。因此,它可能应该在检查之前设置并在之后取消设置,但如果您没有捕获并显式返回命令调用的输出,它可能会影响函数返回值。 - dragon788


尝试使用:

test -x filename

要么

[ -x filename ]

从bash手册页下 条件表达式

 -x file
          True if file exists and is executable.

19
2018-02-26 21:57



这意味着您需要已经知道应用程序的完整路径。 - lhunath
OP没有说明是否要检查特定实例或任何可执行实例......我按照我的方式回答它。 - dmckee


使用 hash作为 @lhunath建议,在bash脚本中:

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


16
2018-06-24 17:01



为什么不呢 if hash foo &> /dev/null; then ... ? - Beni Cherniavsky-Paskin


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

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

7
2017-07-11 18:38



括号不是的一部分 if 语法,只需使用 if builtin type -p vim; then ...。反引号非常古老而且不赞成语法, $() 即使是支持 sh 在所有现代系统上。 - nyuszika7h