Score:5

How in Bash to test a name/string is an executable

cn flag

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

hr flag
... 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)
Score:7
cn flag

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.

sudodus avatar
jp flag
I get `Illegal option --` in a current Ubuntu 22.04 LTS system. (`which` does not accept any options with `--`.)
vanadium avatar
cn flag
@sudodus I tested this, however in Fedora. Will check now! -> clearly Fedora ships a different `which` command, by Carlo Wood <[email protected]> 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
Raffa avatar
jp flag
+1 ... Although double checking with first `type` then with `test, [` ... This however will not fail when the supplied argument is an empty string.
vanadium avatar
cn flag
@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.
terdon avatar
cn flag
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)
user3840170 avatar
in flag
If a question is not quite clear, that’s a reason to vote to close it, not answer.
vanadium avatar
cn flag
@user3840170 right, but I only realized later, when I saw other answers, that the question actually is ambigious
Score:6
vn flag

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).

Raffa avatar
jp flag
+1 ... `command` will need a none-empty string argument or it will return `0` like `type` ... So you might want to mention this.
in flag
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.
Score:4
jp flag

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.

Score:2
jp flag

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"
vanadium avatar
cn flag
The shell build-in `type` command may be enough, e.g. `type ls`.
sudodus avatar
jp flag
@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`.
Raffa avatar
jp flag
+1 ... I tested your script and it runs very well.
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.