Size: 1392
Comment:
|
← Revision 29 as of 2025-03-11 22:37:01 ⇥
Size: 4353
Comment: mention exit codes of command vs type in POSIX
|
Deletions are marked like this. | Additions are marked like this. |
Line 1: | Line 1: |
[[Anchor(faq81)]] | <<Anchor(faq81)>> |
Line 3: | Line 3: |
In BASH, there are a couple builtins that are suitable for this purpose: {{{hash}}} and {{{type}}}. Here's an example using {{{hash}}}: |
POSIX specifies a shell builtins called `command` and `type` which can be used for this purpose. Note that type's exit codes isn't well defined by POSIX whereas command's exit status is well defined by POSIX, so that one is probably the safest to use. |
Line 7: | Line 6: |
if hash qwerty 2>/dev/null; then | # POSIX if command -v qwerty > /dev/null; then echo qwerty exists else echo qwerty does not exist fi # POSIX (no options in `type`) if type qwerty > /dev/null; then echo qwerty exists else echo qwerty does not exist fi }}} In BASH, there is `hash` builtin and `type` with non-POSIX options. Here's are examples: {{{ # Bash if hash qwerty 2> /dev/null; then echo qwerty exists else echo qwerty does not exist fi # Bash # type -P forces a PATH search # skipping builtins and so on if type -P qwerty >/dev/null; then |
Line 13: | Line 41: |
If these builtins are not available (because you're in a Bourne shell, or whatever), then you may have to rely on the external command {{{which}}} (which is often a csh script, although sometimes a compiled binary). Unfortunately, {{{which}}} does ''not'' set a useful exit code -- and it doesn't even write errors to stderr! Therefore, one must parse its output. |
KornShell and zsh have `whence` instead: |
Line 17: | Line 44: |
# Last resort -- using which(1) x=$(LC_ALL=C which qwerty 2>&1) case "$x" in no\ *\ in\ *) echo qwerty does not exist;; *Command\ not\ found.) echo qwerty does not exist;; '') echo qwerty does not exist;; *) echo qwerty exists;; esac |
# ksh/zsh if whence -p qwerty > /dev/null; then echo qwerty exists else echo qwerty does not exist fi |
Line 26: | Line 51: |
The `command` builtin also returns true for shell builtins (unlike `type -P`). If you absolutely must check only PATH, the only POSIX way is to iterate over it: | |
Line 28: | Line 54: |
# Another easy way in gnu: if [ -z "$(which qwerty)" ]; then echo "$0: install qwerty first" exit 1 |
# POSIX IsInPath () ( [ "$1" ] || exit 2 set -f; IFS=: for dir in $PATH$IFS; do [ -x "${dir:-.}/$1" ] && exit 0 done exit 1 ) if IsInPath qwerty; then echo qwerty exists else echo qwerty does not exist |
Line 34: | Line 71: |
Note that the function defined above uses parentheses around the body rather than the normal curly braces. This makes the body run in a subshell, and is the reason we don't need to undo `set -f` or [[IFS]]. | |
Line 35: | Line 73: |
(Also note that its output is ''not'' consistent across platforms. On HP-UX, for example, it prints {{{no qwerty in /path /path /path ...}}}; on OpenBSD, it prints {{{qwerty: Command not found.}}}; and on GNU/Linux, it prints nothing at all.) | The iterative approach is also used in `configure` scripts. Here's a ''simplified'' version of such a test: {{{ # Bourne save_IFS=$IFS IFS=: found=no for dir in $PATH; do if test -x "$dir/qwerty"; then echo "qwerty is installed (in $dir)" found=yes break fi done IFS=$save_IFS if test "$found" = no; then echo "qwerty is not installed" fi }}} Real `configure` scripts are generally much more complicated than this, since they may deal with systems where `$PATH` is not delimited by colons; or systems where executable programs may have optional extensions like `.EXE`; or `$PATH` variables that have the current working directory included in them as an empty string; etc. If you're interested in such things, I suggest reading an actual GNU autoconf-generated `configure` script. They're far too large and complicated to include in this FAQ. The command `which` (which is often a csh script, although sometimes a compiled binary) is '''not reliable''' for this purpose. `which` 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). {{{ # Bourne. Last resort -- using which(1) tmpval=`LC_ALL=C which qwerty 2>&1` if test "$?" -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 }}} 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. Use one of the builtins or the iterative approaches is recommended instead of `which`. |
How can I determine whether a command exists anywhere in my PATH?
POSIX specifies a shell builtins called command and type which can be used for this purpose. Note that type's exit codes isn't well defined by POSIX whereas command's exit status is well defined by POSIX, so that one is probably the safest to use.
# POSIX if command -v qwerty > /dev/null; then echo qwerty exists else echo qwerty does not exist fi # POSIX (no options in `type`) if type qwerty > /dev/null; then echo qwerty exists else echo qwerty does not exist fi
In BASH, there is hash builtin and type with non-POSIX options. Here's are examples:
# Bash if hash qwerty 2> /dev/null; then echo qwerty exists else echo qwerty does not exist fi # Bash # 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
KornShell and zsh have whence instead:
# ksh/zsh if whence -p qwerty > /dev/null; then echo qwerty exists else echo qwerty does not exist fi
The command builtin also returns true for shell builtins (unlike type -P). If you absolutely must check only PATH, the only POSIX way is to iterate over it:
# POSIX IsInPath () ( [ "$1" ] || exit 2 set -f; IFS=: for dir in $PATH$IFS; do [ -x "${dir:-.}/$1" ] && exit 0 done exit 1 ) if IsInPath qwerty; then echo qwerty exists else echo qwerty does not exist fi
Note that the function defined above uses parentheses around the body rather than the normal curly braces. This makes the body run in a subshell, and is the reason we don't need to undo set -f or IFS.
The iterative approach is also used in configure scripts. Here's a simplified version of such a test:
# Bourne save_IFS=$IFS IFS=: found=no for dir in $PATH; do if test -x "$dir/qwerty"; then echo "qwerty is installed (in $dir)" found=yes break fi done IFS=$save_IFS if test "$found" = no; then echo "qwerty is not installed" fi
Real configure scripts are generally much more complicated than this, since they may deal with systems where $PATH is not delimited by colons; or systems where executable programs may have optional extensions like .EXE; or $PATH variables that have the current working directory included in them as an empty string; etc. If you're interested in such things, I suggest reading an actual GNU autoconf-generated configure script. They're far too large and complicated to include in this FAQ.
The command which (which is often a csh script, although sometimes a compiled binary) is not reliable for this purpose. which 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).
# Bourne. Last resort -- using which(1) tmpval=`LC_ALL=C which qwerty 2>&1` if test "$?" -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
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.
Use one of the builtins or the iterative approaches is recommended instead of which.