Shell – How to reliably find the full path of a program on the PATH

pathshell

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

Best Answer

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}" ] && return 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