Differences between revisions 12 and 23 (spanning 11 versions)
Revision 12 as of 2011-01-08 16:00:10
Size: 4440
Editor: p4FD93882
Comment: 'command' also looks for builtins
Revision 23 as of 2023-05-18 20:36:51
Size: 4150
Editor: emanuele6
Comment: in POSIX solution, make sure to iterate once with dir='' if PATH is empty
Deletions are marked like this. Additions are marked like this.
Line 3: Line 3:
The POSIX shell specifies a builtin called {{{command}}} which can be used for this purpose: POSIX specifies a shell builtin called `command` which can be used for this purpose:
Line 13: Line 13:
In BASH, there are a couple more builtins that may also be used: `hash` and `type`. Here's an example using `hash`:
Line 14: Line 15:
However, {{{command}}} also returns true for shell builtins. If you want to absolutely check PATH, the only POSIX way is to iterate over it: {{{
# Bash
if hash qwerty 2>/dev/null; then
  echo qwerty exists
else
  echo qwerty does not exist
fi
}}}
Or, if you prefer `type`:

{{{
# 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:
Line 20: Line 50:
  [ $# -eq 1 ] && [ "$1" ] || return   [ "$#" = 1 ] && [ "$1" ] || return 2
Line 22: Line 52:
  for dir in $PATH; do
    [ -x "$dir/$1" ] && return
  for dir in ''$PATH; do
    [ -x "${dir:-.}/$1" ] && return
Line 34: Line 64:
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 66:
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
  echo qwerty exists
else
  echo qwerty does not exist
fi
}}}

Or, if you prefer {{{type}}}:

{{{
# Bash
if type -P qwerty >/dev/null; then
  echo qwerty exists
else
  echo qwerty does not exist
fi
}}}

KornShell has {{{whence}}} instead:

{{{
# ksh
if whence -p qwerty >/dev/null; then
  echo qwerty exists
else
  echo qwerty does not exist
fi
}}}

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}}} may not set a useful exit code, and it may not even write errors to stderr. In those cases, one must parse its output.

{{{
# 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
    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 and 4.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.

So our best portable solution is to match the words `no` and `in`, or the phrase `not found`, in the combined stdout + stderr and pray.

Note to the person who tried to put a function in this FAQ as a legacy Bourne shell example: legacy Bourne shells ''don't have functions'', at all. POSIX shells have them, but the syntax is:

{{{
foo() {
  commands
}
}}}

You may ''not'' use the word `function`, and you may ''especially not'' use the combination of the word `function` and the `()` symbols. No shell but bash accepts that. Also see BashPitfalls.

The approach used in `configure` scripts is usually to iterate over the components of `$PATH` and explicitly test for the presence of the command in each directory. The fact that this is ''preferred'' over `which` should tell you how unreliable `which` is. Here's a ''simplified'' version of such a test:
The iterative approach is also used in `configure` scripts. Here's a ''simplified'' version of such a test:
Line 120: Line 81:
if test $found = no; then if test "$found" = no; then
Line 124: Line 85:
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.
Line 125: Line 87:
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.

We '''strongly recommend''' not using `which`. Use one of the builtins or the iterative approaches instead.

How can I determine whether a command exists anywhere in my PATH?

POSIX specifies a shell builtin called command which can be used for this purpose:

# POSIX
if command -v qwerty >/dev/null; then
  echo qwerty exists
else
  echo qwerty does not exist
fi

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
  echo qwerty exists
else
  echo qwerty does not exist
fi

Or, if you prefer type:

# 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 ] && [ "$1" ] || return 2
  set -f; IFS=:
  for dir in ''$PATH; do
    [ -x "${dir:-.}/$1" ] && return
  done
  return 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.

We strongly recommend not using which. Use one of the builtins or the iterative approaches instead.

BashFAQ/081 (last edited 2023-05-22 10:02:19 by emanuele6)