我希望能够从 shell 脚本判断任何 POSIX 系统上是否存在命令。
在 Linux 上,我可以执行以下操作:
if which <command>; then
...snip...
fi
但是,Solaris 和 MacOS
which
当命令不存在时不会给出退出失败代码,它们只是将错误消息打印到 STDOUT。
另外,我最近发现
which
命令本身不是 POSIX(参见 http://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html)
有什么想法吗?
command -v
是 POSIX 指定的命令,它执行的操作。
定义为当未找到命令或发生错误时返回>0。
您可以将“which”的 stdout/stderr 读入变量或数组(使用反引号),而不是检查退出代码。
如果系统没有“which”或“where”命令,您还可以获取 $PATH 变量的内容,然后循环遍历所有目录并搜索给定的可执行文件。 这本质上就是它的作用(尽管它可能使用 $PATH 结果的一些缓存/优化)。
一个
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 搜索中存在不可执行文件和可执行文件,它也可能找不到可执行文件。
要在 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
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>
以下内容适用于
bash
和 zsh
,并且避免使用函数和别名。
如果未找到二进制文件,则返回 1。
bin_path () {
if [[ -n ${ZSH_VERSION:-} ]]; then
builtin whence -cp "$1" 2> /dev/null
else
builtin type -P "$1"
fi
}