Image of the glider from the Game of Life by John Conway
Skip to content

Avoid Using which(1)

This post comes from BashFAQ/081 on Greg's Wiki. He argues why you should not be using which(1) to determine if a command is in your $PATH at the end of the page. I'll put that argument at the front:

The command which(1) (which is often a csh script, although sometimes a compiled binary) is not reliable for this purpose. which(1) may not set a useful exit code, and it may not even write errors to stderr. Therefore, in order to have a prayer of successfully using it, one must parse its output (wherever that output may be written).

Note that which(1)'s output when a command is not found is not consistent across platforms. On HP-UX 10.20, for example, it prints "no qwerty in /path /path /path ..."; on OpenBSD 4.1, it prints "qwerty: Command not found."; on Debian (3.1 through 5.0 at least) and SuSE, it prints nothing at all; on Red Hat 5.2, it prints "which: no qwerty in (/path:/path:...)"; on Red Hat 6.2, it writes the same message, but on standard error instead of standard output; and on Gentoo, it writes something on stderr.

(Quotation and manpage reference additions mine). So, if which(1) is bad news, then what is the "proper" way to determine if a command is in your $PATH? Well POSIX has an answer, and not surprisingly, the command to use is "command":

1
2
3
4
5
6
# POSIX
if command -v qwerty >/dev/null; then
  echo qwerty exists
else
  echo qwerty does not exist
fi

The "command" built-in also returns true for shell built-ins. If you absolutely must check only PATH, the only POSIX way is to iterate over it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# POSIX
IsInPath ()
(
  [ $# -eq 1 ] && [ "$1" ] || return 2
  set -f; IFS=:
  for dir in $PATH; do
    [ -z "$dir" ] && dir=. # Legacy behaviour
    [ -x "$dir/$1" ] && return
  done
  return 1
)

if IsInPath qwerty; then
  echo qwerty exists
else
  echo qwerty does not exist
fi

There are also Bash built-ins that can be used, should you have Bash installed on your system:

1
2
3
4
5
6
# Bash using the 'hash' built-in
if hash qwerty 2>/dev/null; then
  echo qwerty exists
else
  echo qwerty does not exist
fi

Or:

1
2
3
4
5
6
7
# Bash using the 'type' built-in
# type -P forces a PATH search, skipping builtins and so on
if type -P qwerty >/dev/null; then
  echo qwerty exists
else
  echo qwerty does not exist
fi

If you prefer the ZSH (my addition not present in the wiki), as I do, then you can look in the $commands associative array:

1
2
3
4
5
6
# ZSH using the $commands associative array
if [[ $commands[qwerty] >/dev/null ]]; then
    echo qwerty exists
else
    echo qwerty does not exist
fi

I like that at the end of the FAQ, he gives a shell script for using which(1) should it be absolutely necessary. Not only do you have to test for exit code, but you also have to test for common strings in the output, seeing as though which(1) doesn't always use exit codes properly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Bourne.  Last resort -- using which(1)
tmpval=`LC_ALL=C which qwerty 2>&1`
if test $rc -ne 0; then
  # FOR NOW, we'll assume that if this machine's which(1) sets a nonzero
  # exit status, that it actually failed.  I've yet to see any case where
  # which(1) sets an erroneous failure -- just erroneous "successes".
  echo "qwerty is not installed.  Please install it."

else
    # which returned 0, but that doesn't mean it succeeded.  Look for known error strings.
    case "$tmpval" in
      *no\ *\ in\ *|*not\ found*|'')
        echo "qwerty is not installed.  Please install it."
        ;;
      *)
        echo "Congratulations -- it seems you have qwerty (in $tmpval)."
        ;;
    esac
fi

CONCLUSION:
You have many options to find whether or not a command exists in your $PATH, some POSIX, some proper built-ins. Regardless, you should be able to build platform-independent scripts using the proper tools, and using which(1) is not the right tool for the job. Hopefully, this has convinced you of that.

{ 3 } Comments