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

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


当前回答

如果您可以:

which programname

...

type -P programname

其他回答

如果没有任何可用的外部类型命令(在这里是理所当然的),我们可以使用符合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温度不匹配。

答复

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

我使用这个,因为它非常简单:

if [ $(LANG=C type example 2>/dev/null | wc -l) = 1 ]; then 
    echo exists; 
else 
    echo "not exists"; 
fi

or

if [ $(LANG=C type example 2>/dev/null | wc -l) = 1 ]; then
    echo exists
else
    echo "not exists"
fi

它使用shell内置程序和程序的回声状态进行标准输出,而不使用标准错误。另一方面,如果找不到命令,它只会将状态返回到标准错误。

迟到的回答,但这就是我最后做的。

我只是检查我执行的命令是否返回错误代码。如果返回0,则表示程序已安装。此外,您还可以使用它检查脚本的输出。以这个脚本为例。

foo.sh

#!/bin/bash
echo "hello world"
exit 1 # throw some error code

示例:

# outputs something bad... and exits
bash foo.sh $? -eq 0 || echo "something bad happened. not installed" ; exit 1

# does NOT outputs nothing nor exits because dotnet is installed on my machine
dotnet --version $? -eq 0 || echo "something bad happened. not installed" ; exit 1

基本上,所有这些都是检查命令运行的退出代码。即使命令退出代码不是0,这个问题上最被接受的答案也将返回true。

哈希变量有一个陷阱:例如,可以在命令行中键入

one_folder/process

以执行进程。为此,one_folder的父文件夹必须位于$PATH中。但当您尝试散列此命令时,它总是会成功:

hash one_folder/process; echo $? # will always output '0'