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

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


当前回答

答复

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."

其他回答

它可能更简单,只是:

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

假设您已经遵循了安全壳做法:

set -eu -o pipefail
shopt -s failglob

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

这假设命令可以以这样的方式调用,即它(几乎)什么都不做,比如报告其版本或显示帮助。

如果找不到伪命令,Bash将退出并返回以下错误。。。

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

这比其他命令-v(和类似的)回答更有用,也更不冗长,因为错误消息是自动生成的,并且还包含相关的行号。

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 executable

       2      if an invalid option is specified

这样做的好处是,它可以确定可执行文件在运行环境中是否可用-这节省了一些问题。。。

要像@lhunath建议的那样在Bash脚本中使用哈希:

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

此脚本运行哈希,然后检查最近命令的退出代码(存储在$?中的值)?,等于1。如果hash没有找到foo,则退出代码将为1。如果存在foo,则退出代码将为0。

&>/dev/null重定向哈希的标准错误和标准输出,以便它不会出现在屏幕上,echo>&2将消息写入标准错误。

如果你想检查一个程序是否存在,是否真的是一个程序,而不是Bash内置命令,那么命令、类型和散列不适合测试,因为它们都会返回内置命令的0退出状态。

例如,时间程序提供了比时间内置命令更多的功能。要检查程序是否存在,我建议使用以下示例中的哪个:

# 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