29

I want to be able to tell if a command exists on any POSIX system from a shell script.

On Linux, I can do the following:

if which <command>; then
   ...snip...
fi

However, Solaris and MacOS which do not give an exit failure code when the command does not exist, they just print an error message to STDOUT.

Also, I recently discovered that the which command itself is not POSIX (see http://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html)

Any ideas?

tripleee
  • 175,061
  • 34
  • 275
  • 318
singpolyma
  • 10,999
  • 5
  • 47
  • 71
  • 2
    related: [shell - Check if a program exists from a bash script](http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script) – mrak Jun 02 '13 at 06:25
  • Thanks, wasn't so clear from the below, this helped me @mrak `if command -v dropbox; then dropbox running`... – lsl Jan 23 '14 at 03:05
  • 1
    See also https://unix.stackexchange.com/questions/85249/why-not-use-which-what-to-use-then for the overwhelming but probably necessary background. – tripleee Aug 03 '19 at 09:19
  • Another related question: [How can I check if a program exists in a shell script?](https://stackoverflow.com/q/7522712/2989289) – artu-hnrq Aug 06 '21 at 10:18

5 Answers5

35

command -v is a POSIX specified command that does what which does.

It is defined to to return >0 when the command is not found or an error occurs.

singpolyma
  • 10,999
  • 5
  • 47
  • 71
  • 4
    I 100% agree with using `command -v`, I use it all the time, to check for command availability. For example, `command -v CMD > /dev/null || echo 'CMD not found'` No stupid `if`s required!! – J. M. Becker Aug 13 '12 at 18:37
  • 3
    @TechZilla: why are 'if' statements stupid? – dokaspar Mar 09 '15 at 16:36
  • `if` isn't stupid just because it's an `if`, it was stupid in this use case as it's extraneous. There was no grouping, and thus no additional semantic value, it would just be an unnecessary re-evaluation. Can it be justified anyway, absolutely! A zealot of any practice can justify almost anything, and in this case the consequence is insignificant... but it doesn't mean they are correct, or that all things are equal. Generally I do the practice like this, one command (test included) with a single error code, no if statement. Once you get more, then `if` could be more than reasonable. – J. M. Becker Mar 11 '15 at 16:59
  • 3
    POSIX shell is not required to implement `command -v` option. See http://stackoverflow.com/q/34572700/1175080. – Lone Learner Jan 03 '16 at 02:53
  • Found this very use in a git hook set up by `git-lfs` `command -v git-lfs >/dev/null 2>&1 || { echo >&2 "git-lfs command not found"}` (the error message is more verbose but that's not the point here) – Marcello Romani Apr 12 '17 at 08:14
  • @LoneLearner in the latest version of the specification it is required. – jarno Apr 28 '21 at 21:12
  • 2
    `command -v` does not do exactly what `which` does: The latter exits with non-zero code, if the argument is an alias or a shell function. – jarno Apr 28 '21 at 21:21
  • 1
    In `zsh`, `command -v` will happily print out an alias. See my answer for a solution which works in both `zsh` and `bash`. – Tom Hale Jan 13 '22 at 07:13
  • @J.M.Becker Well, the check might fail for CMD `if`, for example. In [my answer](https://stackoverflow.com/a/70715425/4414935) I give better alternatives. – jarno Jan 18 '22 at 13:41
3

You could read the stdout/stderr of "which" into a variable or an array (using backticks) rather than checking for an exit code.

If the system does not have a "which" or "where" command, you could also grab the contents of the $PATH variable, then loop over all the directories and search for the given executable. That's essentially what which does (although it might use some caching/optimization of $PATH results).

Andy White
  • 86,444
  • 48
  • 176
  • 211
  • The problem is, that even on systems that *have* which, I am not guarenteed there will even *be* any output... I guess I could just check to see that it doesn't "look like a path"... – singpolyma Apr 18 '09 at 00:35
1

One which utility is available as shell script in the Git repository of debianutils package of Debian Linux. The script seems to be POSIX compatible and you could use it, if you take into account copyright and license. Note that there have been some controversy whether or not and how the which utility should be deprecated; (at time of writing) current version in Git shows deprecation message whereas an earlier version added later removed -s option to enable silent operation.

command -v as such is problematic as it may output a shell function name, an alias definition, a keyword, a builtin or a non-executable file path. On the other hand some path(s) output by which would not be executed by shell if you run the respective argument as such or as an argument for command. As an alternative for using the which script, a POSIX shell function using command -v could be something like

#!/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
}

Disclaimer: Note that the script using command -v above does not find an executable whose name equals a name of a special built-in utility, a regular built-in utility not associated with a PATH search, or a shell reserved word. It might not find an executable either in case if there is non-executable file and executable file available in PATH search.

jarno
  • 787
  • 10
  • 21
-1

A function_command_exists for checking if a 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"

output:

example 1
Command: 'ls' exists
Command: 'notpresent' does not exist
example 2
Command: 'ls' exists
End of the script

Note that even the set -eu that turns -e option for the script was used the script was executed to the last line "End of the script"

There is no Command: 'notpresent' does not exist in the example 2 because of the && operator so the execution of echo "Command: "\'$command\'" does not exist" is skipped but the execution of the script continues till the end.

Note that the function_command_exists does not check if you have a right to execute the command. This needs to be done separately.

Solution with 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

produces:

<handle not found 2>

because echo was found at the first example.

Solution with running a command and handling errors including command not found.

#!/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

produces:

<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>
Jimmix
  • 5,644
  • 6
  • 44
  • 71
-1

The following works in both bash and zsh and avoids both functions and aliases.

It returns 1 if the binary is not found.

bin_path () {
        if [[ -n ${ZSH_VERSION:-} ]]; then
                builtin whence -cp "$1" 2> /dev/null
        else
                builtin type -P "$1"
        fi
}
Tom Hale
  • 40,825
  • 36
  • 187
  • 242