Differences between revisions 14 and 29 (spanning 15 versions)
Revision 14 as of 2011-01-10 19:42:37
Size: 4174
Editor: p4FD97FAB
Comment: POSIX calls it a "legacy feature" and says "a strictly conforming application shall use an actual pathname (such as .)"
Revision 29 as of 2025-03-11 22:37:01
Size: 4353
Editor: 81
Comment: mention exit codes of command vs type in POSIX
Deletions are marked like this. Additions are marked like this.
Line 3: Line 3:
POSIX specifies a shell builtin called `command` which can be used for this purpose: 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 7:
if command -v qwerty >/dev/null; then if command -v qwerty > /dev/null; then
Line 12: Line 12:
}}}
Line 14: Line 13:
In BASH, there are a couple more builtins that may also be used: `hash` and `type`. Here's an example using `hash`:

{{{
# Bash
if hash qwerty 2>/dev/null; then
# POSIX (no options in `type`)
if type qwerty > /dev/null; then
Line 23: Line 19:

Line 24: Line 22:

Or, if you prefer `type`:
In BASH, there is `hash` builtin and `type` with non-POSIX options. Here's are examples:
Line 29: Line 26:
# type -P forces a PATH search, skipping builtins and so on 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
Line 36: Line 41:

KornShell has `whence` instead:
KornShell and zsh have `whence` instead:
Line 40: Line 44:
# ksh
if whence -p qwerty >/dev/null; then
# ksh/zsh
if whence -p qwerty > /dev/null; then
Line 47: Line 51:
Line 54: Line 57:
  [ $# -eq 1 ] && [ "$1" ] || return 2   [ "$1" ] || exit 2
Line 56: Line 59:
  for dir in $PATH; do
    [ -z "$dir" ] && dir=. # Legacy behaviour
    [ -
x "$dir/$1" ] && return
  for dir in $PATH$IFS; do
    [ -x "${dir:-.}/$1" ] && exit 0
Line 60: Line 62:
  return 1   exit 1
Line 69: Line 71:
Line 87: Line 88:
if test $found = no; then if test "$found" = no; then
Line 91: Line 92:
Line 99: Line 99:
if test $rc -ne 0; then if test "$?" -ne 0; then
Line 110: Line 110:
 ;;         ;;
Line 113: Line 113:
 ;;         ;;
Line 117: Line 117:
Line 120: Line 119:
We '''strongly recommend''' not using `which`. Use one of the builtins or the iterative approaches instead. 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.

BashFAQ/081 (last edited 2025-03-11 22:37:01 by 81)