5

How, on Bash, do we test a name string is an executable, as tried it won't work:

$ [[ -x cp ]] &&echo YES
$
$ [[ -x ls ]] &&echo YES

as tried it hard won't be correct way:

$ >/dev/null type ls && echo YES
YES

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

This the very falling failure thanks for help before

Raffa
  • 24,905
  • 3
  • 35
  • 79
itil nonok
  • 59
  • 2
  • 1
    ... see [What is the bash command: `command`?](https://askubuntu.com/a/1005292/178692) and [What is the best method to test if a program exists for a shell script?](https://unix.stackexchange.com/questions/404146/what-is-the-best-method-to-test-if-a-program-exists-for-a-shell-script) – steeldriver Aug 31 '23 at 00:44

4 Answers4

7

The question is not quite clear:

  • Do you want to test if some string corresponds to an executable command? or
  • Do you want to test if a file is executable

You want to test if a file is executable

The -x test is useful in the second case because it tests whether a given file is executable, i.e., if the executable bit is set.

It requires you to provide a valid file name. Unless you provide a full file path, the current directory is searched. Thus your command will typically fail for e.g. cp, unless you first make the directory of the executable current, i.e. cd /usr/bin.

To retrieve the full file path of an executable, you can use the shell build-in type with the options -P so only a matching executable in the search PATH is retrieved (ignoring aliases, hashed commands and functions). Note that that may not be what is executed on the system by default.

You could also add an action to execute if the test fails. Then your statement becomes:

[[ -x $(type -P cp) ]] && echo YES || echo NO

which can also be written as

test -x $(type -P cp) && echo YES || echo NO

You want to test if some string corresponds to an executable command

If your question aimed to ask how to test if something is an executable command on your system, just use type:

type ls &>/dev/null && echo YES || echo NO

Type will return an error code if the string does not correspond with something executable, so the second command will not be executed.

Barmar
  • 241
  • 1
  • 7
vanadium
  • 82,909
  • 6
  • 116
  • 186
  • I get `Illegal option --` in a current Ubuntu 22.04 LTS system. (`which` does not accept any options with `--`.) – sudodus Aug 31 '23 at 07:49
  • 1
    @sudodus I tested this, however in Fedora. Will check now! -> clearly Fedora ships a different `which` command, by Carlo Wood supporting long gnu options, whereas the version in Ubuntu is from 2016 and has almost no options. However, it does no more than search the PATH, so it makes things easier -> will update my answer – vanadium Aug 31 '23 at 08:48
  • 1
    +1 ... Although double checking with first `type` then with `test, [` ... This however will not fail when the supplied argument is an empty string. – Raffa Aug 31 '23 at 10:25
  • 1
    @Raffa Correct, I also noticed in the mean time. However, the -P test does seem to do the trick. I guess that is because now, an error code is set if no file is found in the path, which causes the test to fail. Updated the answer. – vanadium Aug 31 '23 at 10:29
  • In any case, `type` is always a better choice than `which`. See [Why not use "which"? What to use then?](https://unix.stackexchange.com/q/85249) – terdon Aug 31 '23 at 12:49
  • 1
    If a question is not quite clear, that’s a reason to vote to close it, not answer. – user3840170 Aug 31 '23 at 16:56
  • 1
    @user3840170 right, but I only realized later, when I saw other answers, that the question actually is ambigious – vanadium Aug 31 '23 at 19:54
6

You can use the command -v command to test this:

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

Will return:

YES

While:

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

Will most likely return nothing (unless you have an executable in path named fhdjskfsfs).

Artur Meinild
  • 21,605
  • 21
  • 56
  • 89
  • +1 ... `command` will need a none-empty string argument or it will return `0` like `type` ... So you might want to mention this. – Raffa Aug 31 '23 at 10:17
  • 2
    In my experience, `command` is not quite as good as `type` for interactive usage (not quite as much info, and it does not recognize some cases that `type` does), but it’s far superior for scripting since it’s generally more consistent in behavior across shells. – Austin Hemmelgarn Aug 31 '23 at 12:01
4

How, on Bash, do we test a name string is an executable?

Your logic with the type shell builtin command should work (excluding shell builtin commands and functions as you seem to want that), if you use it like so:

&>/dev/null type -afPt -- ls && echo yes

... and that would work as long as the provided string argument is not empty or otherwise it will return 0(success) on an empty string ... Therefore, if you are using this in a script and passing names/strings in a variable e.g. var, then you might want to use it like this: ${var:-empty} which if var is empty, then Bash will expand that variable to the word empty ... Double quoting the variable expansion should work as well.

Examples:

$ &>/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

Notice: if you are going to discard off all the output, then you might not need all those options as only -P should be enough to exclude both shell builtins and functions because it forces a path search for each word in the passed string and this will exclude those two by default.

Raffa
  • 24,905
  • 3
  • 35
  • 79
2

If you want to test not only if a command is an executable, but generally what kind of command it is, you can use the following shellscript, which you might give the name what-about and make it executable.

#!/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"
sudodus
  • 45,126
  • 5
  • 87
  • 151
  • The shell build-in `type` command may be enough, e.g. `type ls`. – vanadium Aug 31 '23 at 09:04
  • @vanadium, Yes, often `type` is enough, but run the script targeting various commands and see what is does, for example with `echo`, `time`, `rename` (if installed), `ls`. – sudodus Aug 31 '23 at 09:23
  • +1 ... I tested your script and it runs very well. – Raffa Aug 31 '23 at 10:13