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