12

I need to find the path of a given program on the PATH using a shell script. The path must be the actual full path of the program, which can be passed later to one of the exec* functions, which does not search the PATH itself, e.g. execv.

There are programs like kill, which are available as an actual program and a shell built-in at the same time. If this is case, I need the full path to the actual program.

There are several utilities that can find a program on the PATH as specified in Section 2.9.1.1, Command Search and Execution of the POSIX standard.

There is which, which is not part of any standard. It can be a regular program on some systems, whereas some shells provide it is a builtin. It seems to be available on most systems and shells, but the shells with a builtin version, also just return the name of the built-in instead of the path to the executable. Also it is not standardized in any way and may return any output and take different options.

bash# which kill
/usr/bin/kill
dash# which kill
/usr/bin/kill
fish# which kill
/usr/bin/kill
mksh# which kill
/usr/bin/kill
tcsh# which kill
kill: shell built-in command.
zsh# which kill
kill: shell built-in command

There is whence, which is a built-in of a few shells. But not available on many shells. It will too return the name of the built-in instead of the path to program. A -p may be passed to whence to change this behavior.

bash# whence kill
bash: whence: command not found
dash# whence kill
dash: 1: whence: not found
fish# whence kill
fish: Unknown command 'whence'
mksh# whence kill
kill
mksh# whence -p kill
/usr/bin/kill
tcsh# whence kill
whence: Command not found.
zsh# whence kill
kill
zsh# whence -p kill
/usr/bin/kill

There is the command builtin specified by POSIX:2008. Unfortunately it also searches for regular commands and built-ins and will return the name of the built-in instead of the path to the program shadowed by a built-in of the same name. Some old shells haven't implemented it yet.

bash# command -v kill
kill
dash# command -v kill
kill
fish# command -v kill
/usr/bin/kill
mksh# command -v kill
kill
tcsh# command -v kill
command: Command not found.
zsh# command -v kill
kill
  • I can't figure out if enable is specified in POSIX or not, but if it is, you could use enable -n which to disable the shell built-in for which. – Muzer Nov 17 '16 at 15:04
  • and there is realpath – Ipor Sircer Nov 17 '16 at 15:10
  • @Muzer On the shells I have at my disposal, enable is only provided by bash and zsh – Sebastian Schrader Nov 17 '16 at 15:11
  • 1
    You need a realiable method for the specific shell which is running your script not for all shells. Scripts are not executed by a random shell but specifically by the shell specified in the shebang line. That being said, in bash that would be type -p. Both bash and dash let you say command command to run an actual executable even if there is a function or builtin with the same name. – AlexP Nov 17 '16 at 15:31
  • 1
    @AlexP command skips functions (and aliases) but NOT builtins, as the Q correctly says. And you can't always use a shebang because there is no path that gets any given shell, or even some POSIX shell, on all systems. – dave_thompson_085 Nov 17 '16 at 19:21
11

Just search for it yourself.

export IFS=":"
[ -z "${1}" ] && exit 1
for dir in $PATH
do
   if [ -x "${dir}/${1}" ];then
      echo "${dir}/${1}"
      exit 0
   fi
done
echo ${1} not found
exit 1

Tested in bash, dash, ksh, mksh, zsh

Update

The above is nice for a stand alone script however if you're planning on embedding this into a larger script you may want to use something more like the following.

function find_path() {
   IFS_SAVE="${IFS}"
   export IFS=":"
   [ -z "${1}" ] && exit 1
   for dir in $PATH
   do
      if [ -x "${dir}/${1}" ];then
         echo "${dir}/${1}"
         export IFS="${IFS_SAVE}"
         return 0
      fi
   done
   export IFS="${IFS_SAVE}"
   echo ${1} not found
   return 1
}

This is so that IFS is restored after finding the match, also swapped exit's with return's

  • 1
    Maybe -x instead of -f? – Jeff Schaller Nov 17 '16 at 16:58
  • @JeffSchaller good point, no reason to pick out non-executable files. – Zachary Brady Nov 17 '16 at 17:01
  • 3
    If you integrate this into a larger shell script (as opposed to making it into a script in its own right, as is presumably intended), you might want to restore the old value of IFS afterwards - otherwise that could have a lot of effects on the rest of the script... – psmears Nov 17 '16 at 17:34
  • Why export the IFS variable? Isn't it enough to have this set in the local shell? Speaking of local, would local IFS be portable? The above may interact poorly if something else is saving IFS the same way. Looking at this question on SE, local may work for most shells despite not being POSIX. Putting the original version in a (…) subshell might work as well. – MvG Nov 17 '16 at 20:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.