【问题标题】:Find out if a command exists on POSIX system查看 POSIX 系统上是否存在命令
【发布时间】:2010-10-20 05:50:26
【问题描述】:

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

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

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

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

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

有什么想法吗?

【问题讨论】:

标签: unix shell posix


【解决方案1】:

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

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

【讨论】:

  • 问题是,即使在有 的系统上,我也不保证会有任何输出......我想我可以检查它是否“看起来像一条路径”...
【解决方案2】:

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

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

【讨论】:

  • 我 100% 同意使用 command -v,我一直使用它来检查命令的可用性。例如,command -v CMD &gt; /dev/null || echo 'CMD not found' 不需要愚蠢的ifs!!
  • if 并不是因为它是if 而愚蠢,在这个用例中它是愚蠢的,因为它是无关的。没有分组,因此没有额外的语义价值,这只是不必要的重新评估。无论如何,这是否合理,绝对!任何实践的狂热者几乎可以为任何事情辩护,在这种情况下,后果是微不足道的……但这并不意味着它们是正确的,或者所有事情都是平等的。通常我会这样做,一个带有单个错误代码的命令(包括测试),没有 if 语句。一旦你得到更多,那么if 可能会更合理。
  • POSIX shell 不需要实现command -v 选项。见stackoverflow.com/q/34572700/1175080
  • git-lfs command -v git-lfs &gt;/dev/null 2&gt;&amp;1 || { echo &gt;&amp;2 "git-lfs command not found"} 设置的 git hook 中发现了这个用途(错误信息更冗长,但这不是重点)
【解决方案3】:

function_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

请注意,即使使用了将 -e 选项转换为脚本的 set -eu,脚本也会执行到最后一行“脚本结束”

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

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

command -v &lt;command-to-check&gt;的解决方案

#!/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 在第一个示例中被发现。

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

#!/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>

【讨论】:

    【解决方案4】:

    以下内容在bashzsh 中都有效,并且避免了函数和别名。

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

    bin_path () {
            if [[ -n ${ZSH_VERSION:-} ]]; then
                    builtin whence -cp "$1" 2> /dev/null
            else
                    builtin type -P "$1"
            fi
    }
    

    【讨论】:

    • 这不是 POSIX 方式。
    【解决方案5】:

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

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

    #!/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 搜索中存在不可执行文件和可执行文件,它也可能找不到可执行文件。

    【讨论】:

    • 这是众多实现之一。
    • @singpolyma 你知道另一个 POSIX shell 实现吗?
    猜你喜欢
    • 1970-01-01
    • 2017-11-17
    • 2021-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-18
    • 1970-01-01
    • 2018-02-15
    相关资源
    最近更新 更多