Differences between revisions 6 and 7
Revision 6 as of 2007-10-09 19:39:03
Size: 1871
Editor: JariAalto
Comment: which : change to use verb "may". E.g. under Solaris, which does set proper exit code and write message to sderr. New Which() function
Revision 7 as of 2007-10-11 14:23:06
Size: 3796
Editor: GreyCat
Comment: replace that horribly wrong example
Deletions are marked like this. Additions are marked like this.
Line 7: Line 7:
# This one works in bash
Line 14: Line 15:
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 no set a useful ''exit'' ''code'' on systems other than GNU/Linux -- and it may not even write errors to stderr. In those cases, one must parse its output. Or, if you prefer {{{type}}}:

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

Korn shell has {{{whence}}} instead:

{{{
# Here's a ksh solution
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.
Line 18: Line 41:
# Backticks + unset (no "local" used for variable); assume a legacy Bourne shell.
function Which()
{
    [ "$1" ] || return 1 # Require ARG
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."
Line 23: Line 48:
    tmpval=`LC_ALL=C which $1 2>&1`
else
Line 26: Line 50:
      *no\ *\ in\ *) tmpval="" ;;
      *not\ found*) tmpval="" ;;
      '') tmpval="" ;;
      *no\ *\ in\ *|*not\ found*|'')
        echo "qwerty is not installed. Please install it."
 ;;
      *)
        echo "Congratulations -- it seems you have qwerty (in $tmpval)."
 ;;
Line 30: Line 57:

    if [ "$tmpval" ]; then
        echo $tmpval
        unset tmpval # prevent variable leak from function
        return 0
    else
        unset tmpval
        return 1
    fi
}

Which which
}}}

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.}}}; on Debian and SuSE, it prints nothing at all; and on Gentoo, it actually prints something to stderr.

{{{
# Another easy way that works only on gnu:
if ! which qwerty >/dev/null 2>&1; then
  echo "$0: install qwerty first"
  exit 1
Line 54: Line 60:
(Although, on a GNU system, one would generally prefer to use one of the Bash builtins instead.) 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.

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. That should tell you how unreliable `which(1)` is. Here's a ''simplified'' version of such a test:

{{{
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.

Anchor(faq81)

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

In BASH, there are a couple builtins that are suitable for this purpose: hash and type. Here's an example using hash:

# This one works in bash
if hash qwerty 2>/dev/null; then
  echo qwerty exists
else
  echo qwerty does not exist
fi

Or, if you prefer type:

# Also a bash solution
if type -P qwerty >/dev/null; then
  echo qwerty exists
else
  echo qwerty does not exist
fi

Korn shell has whence instead:

# Here's a ksh solution
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.

# 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.

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. That should tell you how unreliable which(1) is. Here's a simplified version of such a test:

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.

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