24

我希望能够通过 shell 脚本判断任何 POSIX 系统上是否存在命令。

在 Linux 上,我可以执行以下操作:

if which <command>; then
   ...snip...
fi

which但是,当命令不存在时,Solaris 和 MacOS不会给出退出失败代码,它们只是将错误消息打印到 STDOUT。

另外,我最近发现which命令本身不是 POSIX(参见http://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html

有任何想法吗?

4

5 回答 5

31

command -v是一个 POSIX 指定的命令,它执行什么操作。

定义为在找不到命令或发生错误时返回>0。

于 2009-04-18T00:55:47.790 回答
3

您可以将“which”的 stdout/stderr 读入变量或数组(使用反引号),而不是检查退出代码。

如果系统没有“which”或“where”命令,您也可以获取 $PATH 变量的内容,然后遍历所有目录并搜索给定的可执行文件。本质上就是这样做的(尽管它可能会使用 $PATH 结果的一些缓存/优化)。

于 2009-04-18T00:34:43.740 回答
0

Afunction_command_exists用于检查命令是否存在:

#!/bin/sh

set -eu

function_command_exists() {
    local command="$1"
    local IFS=":" # paths are delimited with a colon in $PATH

    # iterate over dir paths having executables
    for search_dir in $PATH
    do
        # seek only in dir (excluding subdirs) for a file with an exact (case sensitive) name
        found_path="$(find "$search_dir" -maxdepth 1 -name "$command" -type f 2>/dev/null)"

        # (positive) if a path to a command was found and it was executable
        test -n "$found_path" && \
        test -x "$found_path" && \
            return 0
    done
    
    # (negative) if a path to an executable of a command was not found
    return 1
}

# example usage
echo "example 1";

command="ls"
if function_command_exists "$command"; then
    echo "Command: "\'$command\'" exists"
else
    echo "Command: "\'$command\'" does not exist"
fi

command="notpresent"
if function_command_exists "$command"; then
    echo "Command: "\'$command\'" exists"
else
    echo "Command: "\'$command\'" does not exist"
fi

echo "example 2";

command="ls"
function_command_exists "$command" && echo "Command: "\'$command\'" exists"

command="notpresent"
function_command_exists "$command" && echo "Command: "\'$command\'" does not exist"

echo "End of the script"

输出:

example 1
Command: 'ls' exists
Command: 'notpresent' does not exist
example 2
Command: 'ls' exists
End of the script

请注意,即使使用了脚本的set -euthat turn-e选项,脚本也被执行到最后一行“脚本结束”

因为运算符中没有Command: 'notpresent' does not exist,所以跳过了执行,但脚本的执行一直持续到最后。example 2&&echo "Command: "\'$command\'" does not exist"

请注意,function_command_exists不检查您是否有权执行该命令。这需要单独进行。

解决方案command -v <command-to-check>

#!/bin/sh
set -eu;

# check if a command exists (Yes)
command -v echo > /dev/null && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
   echo "<handle not found 1>"
fi

# check if a command exists (No)
command -v command-that-does-not-exists > /dev/null && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
   echo "<handle not found 2>"
fi

产生:

<handle not found 2>

因为echo在第一个例子中发现。

运行 acommand和处理错误(包括找不到命令)的解决方案。

#!/bin/sh

set -eu;

# check if a command exists (No)
command -v command-that-does-not-exist > /dev/null && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
   echo "<handle not found 2>"
fi

# run command and handle errors (no problem expected, echo exist)
echo "three" && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
   echo "<handle not found 3>"

elif [ "${status}" -ne 0 ]; then
   echo "<handle other error 3>"
fi

# run command and handle errors (<handle not found 4> expected)
command-that-does-not-exist && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
   echo "<handle not found 4>"

elif [ "${status}" -ne 0 ]; then
   echo "<handle other error 4>"
fi

# run command and handle errors (command exists but <handle other error 5> expected)
ls non-existing-path && status="$?" || status="$?"
if [ "${status}" = 127 ]; then
   echo "<handle not found 5>"

elif [ "${status}" -ne 0 ]; then
   echo "<handle other error 5>"
fi

产生:

<handle not found 2>
three
./function_command_exists.sh: 34: ./function_command_exists.sh: command-that-does-not-exist: not found
<handle not found 4>
ls: cannot access 'non-existing-path': No such file or directory
<handle other error 5>
于 2021-01-10T00:49:12.550 回答
0

一个实用程序在 Debian Linux 的 debianutils 包的Git 存储库which中作为 shell 脚本提供。该脚本似乎与 POSIX 兼容,如果您考虑版权和许可,您可以使用它。请注意,是否以及如何弃用该实用程序存在一些争议;(在撰写本文时)Git 中的当前版本显示弃用消息,而早期版本添加了稍后删除的选项以启用静默操作。which-s

command -v这样是有问题的,因为它可能会输出 shell 函数名称、别名定义、关键字、内置或不可执行的文件路径。另一方面which,如果您运行相应的参数或作为command. 作为使用which脚本的替代方法,使用的 POSIX shell 函数command -v可能类似于

#!/bin/sh
# Argument $1 should be the basename of the command to be searched for.
# Outputs the absolute path of the command with that name found first in
# a directory listed in PATH environment variable, if the name is not
# shadowed by a special built-in utility, a regular built-in utility not
# associated with a PATH search, or a shell reserved word; otherwise
# outputs nothing and returns 1. If this function prints something for
# an argument, it is the path of the same executable as what 'command'
# would execute for the same argument.
executable() {
    if cmd=$(unset -f -- "$1"; unalias -a; command -v -- "$1") \
        && [ -z "${cmd##/*}" ] && [ -x "$cmd" ]; then
        printf '%s\n' "$cmd"
    else
        return 1
    fi
}

免责声明:请注意,使用command -v上面的脚本找不到名称等于特殊内置实用程序名称、与 PATH 搜索无关的常规内置实用程序或 shell 保留字的可执行文件。如果 PATH 搜索中存在不可执行文件和可执行文件,它也可能找不到可执行文件。

于 2022-01-14T19:05:49.310 回答
-1

以下适用于bashandzsh并避免了函数和别名。

如果找不到二进制文件,则返回 1。

bin_path () {
        if [[ -n ${ZSH_VERSION:-} ]]; then
                builtin whence -cp "$1" 2> /dev/null
        else
                builtin type -P "$1"
        fi
}
于 2022-01-13T07:13:38.617 回答