45

I think most are familiar with the which command, and I use it frequently. I just ran into a situation where I'm curious not just which command is first in my path, but how many and where all the commands in all my paths are. I tried the which man page (typing man which made me laugh), but didn't see anything.

kenny
  • 1,453

4 Answers4

46

On some systems, which -a shows all matches. If your shell is bash or zsh¹, you can use type instead: type foo shows the first match and type -a foo shows all matches. The three commands type, which and whence do mostly the same thing; they differ between shells and operating systems in availability, options, and what exactly they report. type is always available and shows all possible command-like names (aliases, keywords, shell built-ins, functions, and external commands).

The only fully portable way to display all matches is to parse $PATH yourself. Here's a shell script that does this. If you make it a shell function, make sure to enclose the function body in parentheses (so that the change to IFS and set -f don't escape the function), and change exit to return.

#!/bin/sh
set -f       # disable globbing
IFS=:        # break words at : only
not_found=1
for d in $PATH; do
  if [ -f "$d/$x" ] && [ -x "$d/$x" ]; then
    printf '%s\n' "$d/$x"
    not_found=0
  fi
done
exit $not_found

¹ Or ksh 93, according to the documentation, though ksh 93s+ 2008-01-31 only prints the first match when I try.

6

The --all or -a flag will show you all matches in your path, and aliases (at least on Fedora, Ubuntu and CentOS):

which -a which

On AIX and Solaris, this will get you close:

echo "$PATH" | sed -e 's/:/ /g' | \
while read -r p; do find "$p" -type f -name "which"; done
Eli Heady
  • 1,234
  • You need double quotes around parameter substitutions, otherwise the script won't work if $PATH contains whitespace or shell globbing characters. read -r is necessary to cope with backslashes. This is not a good method because find will take a long time and may return spurious matches if a directory in $PATH contains subdirectories. Fortunately, find is not useful here; see my answer. – Gilles 'SO- stop being evil' Mar 20 '11 at 00:30
  • I knew find felt wrong since it would certainly be slow in nested directories. Globbing and spaces in $PATH? eww. But you're right (though you were nice enough not to say as much): my one liner was poorly written. – Eli Heady Mar 20 '11 at 01:25
2

If you don't have a which supporting -a, or whence available, roll your own:

#!/bin/sh -f

IFS=":"
for PART in $PATH
do
  if test -x "$PART/$1"
  then
    echo $PART/$1
  fi
done
MattBianco
  • 3,704
  • You're missing set -f to turn off globbing on the unprotected $PATH. test -f isn't sufficient since only executable files are wanted here; you need test -x. Hmm, I realize I forgot the regular file test in my script. – Gilles 'SO- stop being evil' Mar 21 '11 at 21:03
  • @Gilles: edited according to your tips. I'm all for correctness, but I find whence README.txt as unlikely as whence "file* wi?h we!rd name". Just trying to show how easy it is to traverse $PATH. – MattBianco Mar 22 '11 at 08:58
  • This was the only script that worked for me in OmniOS r151030 (OpenSolaris fork). #!/bin/sh -x helpful for troubleshooting - is there a way to omit the last echo? It's a repeat. – Avery Freeman Jan 19 '21 at 21:22
0

ksh and zsh have "whence" as a shell built-in. whence -a does what you want under zsh:

 7:27AM 7 % whence -a cat
/usr/bin/cat
/bin/cat
/usr/bin/cat
/bin/cat

I have to clean up PATH in zsh, I have lots of duplicates in it. whence -a works differently under ksh:

$ whence -a cat
cat is a tracked alias for /usr/bin/cat

I have to say, that seems like a potentially useful behavior, too.