如何在Bash中测试一个名称/字符串是否可执行

在Bash中,我们如何测试一个名称字符串是否可执行,因为尝试它不起作用?
$ [[ -x cp ]] &&echo YES
$
$ [[ -x ls ]] &&echo YES

努力尝试了也不会是正确的方式。
$ >/dev/null type ls && echo YES
YES

$ >/dev/null type -fat && echo YES
YES

这是非常彻底的失败 感谢之前的帮助


4个回答

问题不太清楚:
- 你想测试某个字符串是否对应可执行命令吗? 或者 - 你想测试一个文件是否可执行?
如果你想测试一个文件是否可执行,那么-x测试在第二种情况下非常有用,因为它会检查给定文件是否可执行,即是否设置了可执行位。
它需要你提供一个有效的文件名。除非你提供完整的文件路径,否则会在当前目录中进行搜索。因此,如果你要执行例如cp这样的命令,除非你先将可执行文件所在的目录设为当前目录,否则通常会失败,即cd /usr/bin
要获取可执行文件的完整文件路径,可以使用shell内置的type命令,并使用选项-P,这样只会检索到搜索PATH中匹配的可执行文件(忽略别名、哈希命令和函数)。请注意,默认情况下可能不是系统上执行的内容。
如果测试失败,你还可以添加一个执行操作。那么你的语句将变成:
[[ -x $(type -P cp) ]] && echo YES || echo NO

也可以写成
test -x $(type -P cp) && echo YES || echo NO

你想测试某个字符串是否对应一个可执行命令。
如果你的问题是想要知道如何在你的系统上测试某个东西是否是一个可执行命令,只需使用"type"命令即可。
type ls &>/dev/null && echo YES || echo NO

如果字符串与可执行内容不对应,Type将返回一个错误代码,因此第二个命令将不会被执行。

在当前的Ubuntu 22.04 LTS系统中,我遇到了Illegal option --的错误。(which命令不接受任何带有--的选项。) - sudodus
1@sudodus 我在 Fedora 上进行了测试,现在我会检查一下!-> 显然 Fedora 发行的 which 命令不同,由 Carlo Wood carlo@gnu.org 支持长的 GNU 选项,而 Ubuntu 版本是2016年的,几乎没有选项。然而,它只是搜索 PATH,所以这样做会更容易 -> 我会更新我的答案 - vanadium
1+1 ... 虽然首先使用 type 进行双重检查,然后再与 test, [ 进行检查 ... 但是当提供的参数为空字符串时,这种方法不会失败。 - Raffa
1@Raffa没错,我也注意到了。不过,-P测试似乎起到了作用。我猜这是因为现在,如果在路径中找不到文件,就会设置一个错误代码,导致测试失败。已更新答案。 - vanadium
无论如何,“type”总是比“which”更好的选择。请参阅为什么不使用“which”?应该使用什么? - terdon
1如果一个问题不够清晰明了,那就是关闭它的理由,而不是回答它。 - user3840170
1@user3840170 是的,但我后来才意识到,当我看到其他答案时,才发现这个问题实际上是含糊不清的。 - vanadium

您可以使用command -v命令来测试这个。
command -v cp >/dev/null 2>&1 && echo "YES"

将返回:

YES

当:

command -v fhdjskfsfs >/dev/null 2>&1 && echo "YES"

很可能什么都不会返回(除非您的路径中有一个名为fhdjskfsfs的可执行文件)。

+1 ... command将需要一个非空字符串参数,否则它将返回0,就像type一样... 所以你可能想要提到这一点。 - Raffa
2根据我的经验,对于交互式使用来说,command并不像type那样好(提供的信息不够详细,并且它无法识别一些type可以识别的情况),但是在脚本编写方面,command要更优秀,因为它在不同的shell中行为通常更加一致。 - Austin Hemmelgarn

在Bash中,我们如何测试一个名称字符串是否可执行?
你使用type shell内置命令的逻辑应该可以工作(排除你似乎想要的shell内置命令和函数),如果你像这样使用它:
&>/dev/null type -afPt -- ls && echo yes

...只要提供的字符串参数不为空,它就能正常工作;否则,它将在空字符串上返回0(成功)...因此,如果您在脚本中使用它并通过变量传递名称/字符串,例如var,那么您可能希望像这样使用:${var:-empty},如果var为空,则Bash将将该变量扩展为单词empty...双引号引用变量扩展也应该有效。
示例:
$ &>/dev/null type -afPt -- ls && echo yes || echo no
yes
$
$ &>/dev/null type -afPt -- notcommand && echo yes || echo no
no
$
$ &>/dev/null type -afPt -- set && echo yes || echo no
no
$
$ &>/dev/null type -afPt -- ./myscript.sh && echo yes || echo no
yes
$
$ &>/dev/null type -afPt -- echo && echo yes || echo no
yes

如果你要丢弃所有的输出,那么你可能不需要那些选项,只需要使用-P就足够了,因为它会强制对传递的字符串中的每个单词进行路径搜索,这样默认就会排除掉shell内置命令和函数。请注意。

如果你想测试一个命令不仅仅是可执行的,而且通常是什么类型的命令,你可以使用下面的shell脚本,你可以给它起名为what-about并使其可执行。
#!/bin/bash

LANG=C
inversvid="\0033[7m"
resetvid="\0033[0m"

if [ $# -ne 1 ]
then
 echo "Usage: ${0##*/} <program-name>"
 echo "Will try to find corresponding package"
 echo "and tell what kind of program it is"
 exit 1
fi
command="$1"

str=;for ((i=1;i<=$(tput cols);i++)) do str="-$str";done
tmp="$command"
first=true
curdir="$(pwd)"
tmq=$(which "$command")
tdr="${tmq%/*}"
tex="${tmq##*/}"
if test -d "$tdr"; then cd "$tdr"; fi
#echo "cwd='$(pwd)' ################# d"

while $first || [ "${tmp:0:1}" == "l" ]
do
 first=false
#echo "tmp=$tmp########################################## d 1.0"
 tmp=${tmp##*-\>\ }
 tmq="$tmp"
#echo "tmp=$tmp########################################## d 1.1"
 tmp="$(ls -l "$(which "$tmp")" 2>/dev/null)"
#echo "tmp=$tmp########################################## d 1.2"
 tdr="${tmq%/*}"
 tex="${tmq##*/}"
 if test -d "$tdr"; then cd "$tdr"; fi
# echo "cwd='$(pwd)' ################# d"
 if [ "$tmp" == "" ]
 then
  tmp=$(ls -l "$tex" 2>/dev/null)
#echo "tmp=$tmp########################################## d 2.1"
  tmp=${tmp##*\ }
#echo "tmp=$tmp########################################## d 2.2"
  if [ "$tmp" == "" ]
  then
   echo "$command is not in PATH"
#   package=$(bash -ic "$command -v 2>&1")
#   echo "package=$package XXXXX 0"
   bash -ic "alias '$command' > /dev/null 2>&1" > /dev/null 2>&1
   if [ $? -ne 0 ]
   then
    echo 'looking for package ...'
    package=$(bash -ic "$command -v 2>&1"| sed -e '0,/with:/d'| grep -v '^$')
   else
    echo 'alias, hence not looking for package'
   fi
#   echo "package=$package XXXXX 1"
   if [ "$package" != "" ]
   then
    echo "$str"
    echo "package: [to get command '$1']"
    echo -e "${inversvid}${package}${resetvid}"
   fi
   else
    echo "$tmp"
   fi
 else
  echo "$tmp"
 fi
done
tmp=${tmp##*\ }
if [ "$tmp" != "" ]
then
 echo "$str"
 program="$tex"
 program="$(pwd)/$tex"
 file "$program"
 if [ "$program" == "/usr/bin/snap" ]
 then
  echo "$str"
  echo "/usr/bin/snap run $command     # run $command "
  sprog=$(find /snap/"$command" -type f -iname "$command" \
   -exec file {} \; 2>/dev/null | sort | tail -n1)
  echo -e "${inversvid}file: $sprog$resetvid"
  echo "/usr/bin/snap list $command    # list $command"
  slist="$(/usr/bin/snap list "$command")"
  echo -e "${inversvid}$slist$resetvid"
 else
  package=$(dpkg -S "$program")
  if [ "$package" == "" ]
  then
   package=$(dpkg -S "$tex" | grep -e " /bin/$tex$" -e " /sbin/$tex$")
   if [ "$package" != "" ]
   then
    ls -l /bin /sbin
   fi
  fi
  if [ "$package" != "" ]
  then
   echo "$str"
   echo " package: /path/program  [for command '$1']"
   echo -e "${inversvid} $package ${resetvid}"
  fi
 fi
fi
echo "$str"
alias=$(bash -ic "alias '$command' 2>/dev/null"| grep "$command")
if [ "$alias" != "" ]
then
 echo "$alias"
else
 type=$(bash -ic "type \"$command\" 2>/dev/null")
 if [ "$type" != "" ]
 then
  echo "type: $type"
 elif [ "$alias" == "" ]
 then
  echo "type: $command: not found"
 fi
fi
cd "$curdir"

内置的type命令可能已经足够了,例如type ls - vanadium
@vanadium,是的,通常type就足够了,但是运行脚本时针对不同的命令进行测试,看看它的效果如何,比如使用echotimerename(如果已安装)和ls命令。 - sudodus
+1 ... 我测试了你的脚本,运行得非常好。 - Raffa