The reasons why one may not want to use `which` have already been explained, but here are a few examples on a few systems where `which` actually fails.
On Bourne-like shells, we're comparing the output of `which` with the output of `type` (`type` being a shell builtin, it's meant to be the ground truth, as it's the shell telling us how it would invoke a command).
Many cases are _corner_ cases, but bear in mind that `which`/`type` are often used in corner cases (to find the answer to an unexpected behaviour like: _why on earth is that command behaving like that, which one am I calling?_).
## Most systems, most Bourne-like shells: functions
The most obvious case is for functions:
$ type ls
ls is a function
ls ()
{
[ -t 1 ] && set -- -F "$@";
command ls "$@"
}
$ which ls
/bin/ls
The reason being that `which` only reports about executables, and sometimes about aliases (though not always the ones of _your_ shell), not functions.
The GNU which man page has a broken (as they forgot to quote `$@`) example on how to use it to report functions as well, but just like for aliases, because it doesn't implement a shell syntax parser, it's easily fooled:
$ which() { (alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot "$@";}
$ f() { echo $'\n}\ng ()\n{ echo bar;\n}\n' >> ~/foo; }
$ type f
f is a function
f ()
{
echo '
}
g ()
{ echo bar;
}
' >> ~/foo
}
$ type g
bash: type: g: not found
$ which f
f ()
{
echo '
}
$ which g
g ()
{ echo bar;
}
## Most systems, most Bourne-like shells: builtins
Another obvious case is builtins or keywords, as `which` being an external command has no way to know which builtins your shell have (and some shells like `zsh`, `bash` or `ksh` can load builtins dynamically):
$ type echo . time
echo is a shell builtin
. is a shell builtin
time is a shell keyword
$ which echo . time
/bin/echo
which: no . in (/bin:/usr/bin)
/usr/bin/time
(that doesn't apply to `zsh` where `which` is builtin)
## Solaris 10, AIX 7.1, HP/UX 11i, Tru64 5.1 and many others:
$ csh
% which ls
ls: aliased to ls -F
% unalias ls
% which ls
ls: aliased to ls -F
% ksh
$ which ls
ls: aliased to ls -F
$ type ls
ls is a tracked alias for /usr/bin/ls
That is because on most commercial Unices, `which` (like in the original implementation on 3BSD) is a `csh` script that reads `~/.cshrc`.
The aliases it will report are the ones defined there regardless of the aliases you currently have defined and regardless of the shell you're actually using.
In HP/UX or Tru64:
% echo 'setenv PATH /bin:/usr/bin' >> ~/.cshrc
% setenv PATH ~/bin:/bin:/usr/bin
% ln -s /bin/ls ~/bin/
% which ls
/bin/ls
(the Solaris and AIX versions have fixed that issue by saving `$path` before reading the `~/.cshrc` and restoring it before looking up the command(s))
$ type 'a b'
a b is /home/stephane/bin/a b
$ which 'a b'
no a in /usr/sbin /usr/bin
no b in /usr/sbin /usr/bin
Or:
$ d="$HOME/my bin"
$ mkdir "$d"; PATH=$PATH:$d
$ ln -s /bin/ls "$d/myls"
$ type myls
myls is /home/stephane/my bin/myls
$ which myls
no myls in /usr/sbin /usr/bin /home/stephane/my bin
(of course, being a `csh` script you can't expect it to work with arguments containing spaces...)
## CentOS 6.4, bash
$ type which
which is aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
$ alias foo=': "|test|"'
$ which foo
alias foo=': "|test|"'
/usr/bin/test
$ alias $'foo=\nalias bar='
$ unalias bar
-bash: unalias: bar: not found
$ which bar
alias bar='
On that system, there's an alias defined system-wide that wraps the GNU `which` command.
The bogus output is because `which` reads the output of `bash`'s `alias` but doesn't know how to parse it properly and uses heuristics (one alias per line, looks for the first found command after a `|`, `;`, `&`...)
The worst thing on CentOS is that `zsh` has a perfectly fine `which` builtin command but CentOS managed to break it by replacing it with a non-working alias to GNU `which`.
## Debian 7.0, ksh93:
(though applies to most systems with many shells)
$ unset PATH
$ which which
/usr/local/bin/which
$ type which
which is a tracked alias for /bin/which
On Debian, `/bin/which` is a `/bin/sh` script. In my case, `sh` being `dash` but it's the same when it's `bash`.
An unset `PATH` is not to disable `PATH` lookup, but means using the system's _default PATH_ which unfortunately on Debian, nobody agrees on (`dash` and `bash` have `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`, `zsh` has `/bin:/usr/bin:/usr/ucb:/usr/local/bin`, `ksh93` has `/bin:/usr/bin`, `mksh` has `/usr/bin:/bin`, `execvp()` (like in `env`) has `:/bin:/usr/bin` (yes, looks in the current directory first!)).
Which is why `which` gets it wrong above since it's using `dash`'s default `PATH` which is different from `ksh93`'s
It's not better with GNU `which` which reports:
which: no which in ((null))
(interestingly, there is indeed a `/usr/local/bin/which` on my system which is actually an `akanga` script that came with `akanga` (an `rc` shell derivative where the default `PATH` is `/usr/ucb:/usr/bin:/bin:.`))
## bash, any system:
The one [Chris is referring to in his answer](http://unix.stackexchange.com/a/85280/22565):
$ PATH=$HOME/bin:/bin
$ ls /dev/null
/dev/null
$ cp /bin/ls bin
$ type ls
ls is hashed (/bin/ls)
$ command -v ls
/bin/ls
$ which ls
/home/chazelas/bin/ls
Also after calling `hash` manually:
$ type -a which
which is /usr/local/bin/which
which is /usr/bin/which
which is /bin/which
$ hash -p /bin/which which
$ which which
/usr/local/bin/which
$ type which
which is hashed (/bin/which)
## Now a case where `which` and sometimes `type` fail:
$ mkdir a b
$ echo '#!/bin/echo' > a/foo
$ echo '#!/' > b/foo
$ chmod +x a/foo b/foo
$ PATH=b:a:$PATH
$ which foo
b/foo
$ type foo
foo is b/foo
Now, with some shells:
$ foo
bash: ./b/foo: /: bad interpreter: Permission denied
With others:
$ foo
a/foo
Neither `which` nor `type` can know in advance that `b/foo` cannot be executed. Some shells like `bash`, `ksh` or `yash`, when invoking `foo` will indeed try to run `b/foo` and report an error, while others (like `zsh`, `ash`, `csh`, `Bourne`, `tcsh`) will run `a/foo` upon the failure of the `execve()` system call on `b/foo`.