找出 POSIX 系统上是否存在命令

问题描述 投票:0回答:6

我希望能够从 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
6个回答
37
投票

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

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


3
投票

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

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


1
投票

一个

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

command -v
本身是有问题的,因为它可能会输出 shell 函数名称、别名定义、关键字、内置或不可执行的文件路径。另一方面,如果您直接运行相应的参数或作为
which
的参数运行,则
command
输出的某些路径将不会被 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"; command -v -- "$1") \
        && [ -z "${cmd##/*}" ] && [ -x "$cmd" ]; then
        printf '%s\n' "$cmd"
    else
        return 1
    fi
}

免责声明:请注意,上面使用

command -v
的脚本找不到名称等于特殊内置实用程序、与 PATH 搜索不相关的常规内置实用程序或 shell 保留字的名称的可执行文件。如果 PATH 搜索中存在不可执行文件和可执行文件,它也可能找不到可执行文件。


0
投票

要在 zsh 或 bash 中实现此功能,以便在不考虑别名和函数的同时仍符合 POSIX 标准,只需 shell 即可:

xdg_open_path="$(sh -c "command -v xdg-open")"
if [ "$?" -eq 0 ]; then
    echo "xdg-open: is ${xdg_open_path}"
else
    echo 'xdg-open: not found' 1>&2
fi

-1
投票

A

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

请注意,即使使用了将脚本变成

set -eu
选项的
-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

运行

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>

-1
投票

以下内容适用于

bash
zsh
,并且避免使用函数和别名。

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

bin_path () {
        if [[ -n ${ZSH_VERSION:-} ]]; then
                builtin whence -cp "$1" 2> /dev/null
        else
                builtin type -P "$1"
        fi
}
© www.soinside.com 2019 - 2024. All rights reserved.