关于bash:检测可执行文件是否在用户路径上

Detect if executable file is on user's PATH

本问题已经有最佳答案,请猛点这里访问。

(P)在一个基本的脚本中,我需要确定一个可执行的字母名称是什么。(p)


您也可以使用bash内置的type -P

1
2
3
4
5
help type

cmd=ls
[[ $(type -P"$cmd") ]] && echo"$cmd is in PATH"  ||
    { echo"$cmd is NOT in PATH" 1>&2; exit 1; }


您可以使用which

1
2
3
4
 path_to_executable=$(which name_of_executable)
 if [ -x"$path_to_executable" ] ; then
    echo"It's here: $path_to_executable"
 fi


您可以使用与posix兼容的command内置:

1
2
3
if [ -x"$(command -v"$cmd")" ]; then
    echo"$cmd is in \$PATH"
fi

由于command -v检测函数、别名以及可执行文件,因此需要执行检查。

在bash中,您还可以使用type-P选项,这将强制执行PATH搜索:

1
2
3
if type -P"$cmd" &>/dev/null; then
    echo"$cmd is in \$PATH"
fi

正如注释中已经提到的,避免使用which,因为它需要启动一个外部进程,在某些情况下可能会给您不正确的输出。


DR:

bash中:

1
2
3
function is_bin_in_path {
  builtin type -P"$1" &> /dev/null
}

is_bin_in_path的示例用法:

1
2
% is_bin_in_path ls && echo"in path" || echo"not in path"
in path

无需避免的解决方案

这不是一个简短的答案,因为解决方案必须正确处理:

  • 功能
  • 别名
  • 内置命令
  • 保留词

以纯type失败的例子(注意type更改后的标记):

1
2
3
4
5
6
7
8
9
10
11
12
$ alias foo=ls
$ type foo && echo"in path" || echo"not in path"
foo is aliased to `ls'
in path

$ type type && echo"in path" || echo"not in path"
type is a shell builtin
in path

$ type if && echo"in path" || echo"not in path"
if is a shell keyword
in path

注意,是bashwhich不是壳式内置(它在zsh中):

1
2
3
$ PATH=/bin
$ builtin type which
which is /bin/which

这个答案说明了为什么要避免使用which

Avoid which. Not only is it an external process you're launching for doing very little (meaning builtins like hash, type or command are way cheaper), you can also rely on the builtins to actually do what you want, while the effects of external commands can easily vary from system to system.

Why care?

  • Many operating systems have a which that doesn't even set an exit status, meaning the if which foo won't even work there and will always report that foo exists, even if it doesn't (note that some POSIX shells appear to do this for hash too).
  • Many operating systems make which do custom and evil stuff like change the output or even hook into the package manager.

在这种情况下,也要避免command -v

我刚才引用的答案建议使用command -v,但这不适用于当前的"$PATH中是否有可执行文件?"场景:它将以我上面用普通type所描述的方式失败。

正确的解决方案

bash中,我们需要使用type -P

1
2
3
  -P        force a PATH search for each NAME, even if it is an alias,
            builtin, or function, and returns the name of the disk file
            that would be executed

zsh中,我们需要使用whence -p

1
2
   -p     Do  a  path  search  for  name  even  if  it is an alias,
          reserved word, shell function or builtin.

对于同时在{ba,z}sh中工作的版本:

1
2
3
4
5
6
7
8
9
# True if $1 is an executable in $PATH
# Works in both {ba,z}sh
function is_bin_in_path {
  if [[ -n $ZSH_VERSION ]]; then
    builtin whence -p"$1" &> /dev/null
  else  # bash:
    builtin type -P"$1" &> /dev/null
  fi
}


如果命令-v foo;那么foo;否则echo"foo不可用";fi


我们可以使用which定义一个函数来检查是否存在可执行文件:

1
2
3
function is_executable() {
    which"$@" &> /dev/null
}

调用函数就像调用可执行文件一样。"$@"确保which得到与函数完全相同的参数。

&> /dev/null确保由which写入stdout或stderr的任何内容都被重定向到空设备(这是一个特殊的设备,丢弃写入它的信息),而不是由函数写入stdout或stderr。

由于函数没有用返回代码显式返回,因此当函数返回时,最新执行的可执行文件(在本例中为which)的退出代码将是函数的返回代码。如果which能够找到由函数的参数指定的可执行文件,则它将退出并返回一个指示成功的代码,否则将返回一个指示失败的退出代码。此行为将由is_executable自动复制。

然后我们可以使用这个函数有条件地做一些事情:

1
2
3
4
5
if is_executable name_of_executable; then
    echo"name_of_executable was found"
else
    echo"name_of_executable was NOT found"
fi

在这里,if执行它和then之间编写的命令(在我们的例子中是is_executable name_of_executable),并根据命令的返回代码选择要执行的分支。

或者,我们可以跳过定义函数,直接在if语句中使用which

1
2
3
4
5
if which name_of_executable &> /dev/null; then
    echo"name_of_executable was found"
else
    echo"name_of_executable was NOT found"
fi

但是,我认为这会使代码的可读性稍差。


使用which

1
$ which myprogram