When looking for the path to an executable or checking what would happen if you enter a command name in a Unix shell, there's a plethora of different utilities (which
, type
, command
, whence
, where
, whereis
, whatis
, hash
, etc).
We often hear that which
should be avoided. Why? What should we use instead?
6 Answers 6
Here is all you never thought you would ever not want to know about it:
Summary
To get the pathname of an executable in a Bourne-like shell script (there are a few caveats; see below):
ls_path=$(command -v ls)
To find out if a given command exists:
if command -v given-command > /dev/null; then
echo given-command is available
else
echo given-command is not available
fi
At the prompt of an interactive Bourne-like shell:
type ls
The which
command is a broken heritage from the C-Shell and is better left alone in Bourne-like shells.
Use Cases
There's a distinction between looking for that information as part of a script or interactively at the shell prompt.
At the shell prompt, the typical use case is: this command behaves weirdly, am I using the right one? What exactly happened when I typed mycmd
? Can I look further at what it is?
In that case, you want to know what your shell does when you invoke the command without actually invoking the command.
In shell scripts, it tends to be quite different. In a shell script there's no reason why you'd want to know where or what a command is if all you want to do is run it. Generally, what you want to know is the path of the executable, so you can get more information out of it (like the path to another file relative to that, or read information from the content of the executable file at that path).
Interactively, you may want to know about all the my-cmd
commands available on the system, in scripts, rarely so.
Most of the available tools (as is often the case) have been designed to be used interactively.
History
A bit of history first.
The early Unix shells until the late 70s had no functions or aliases. Only the traditional looking up of executables in $PATH
. csh
introduced aliases around 1978 (though csh
was first released in 2BSD
, in May 1979), and also the processing of a .cshrc
for users to customize the shell (every shell, as csh
, reads .cshrc
even when not interactive like in scripts).
While the Bourne shell was first released in Unix V7 earlier in 1979, function support was only added much later (1984 in SVR2), and anyway, it never had some rc
file (the .profile
is to configure your environment, not the shell per se).
csh
got a lot more popular than the Bourne shell as (though it had an awfully worse syntax than the Bourne shell) it was adding a lot of more convenient and nice features for interactive use.
In 3BSD
(1980), a which
csh script was added for the csh
users to help identify an executable, and it's a hardly different script you can find as which
on many commercial Unices nowadays (like Solaris, HP/UX, AIX or Tru64).
That script reads the user's ~/.cshrc
(like all csh
scripts do unless invoked with csh -f
), and looks up the provided command name(s) in the list of aliases and in $path
(the array that csh
maintains based on $PATH
).
Here you go: which
came first for the most popular shell at the time (and csh
was still popular until the mid-90s), which is the main reason why it got documented in books and is still widely used.
Note that, even for a csh
user, that which
csh script does not necessarily give you the right information. It gets the aliases defined in ~/.cshrc
, not the ones you may have defined later at the prompt or for instance by source
ing another csh
file, and (though that would not be a good idea), PATH
might be redefined in ~/.cshrc
.
Running that which
command from a Bourne shell would still lookup aliases defined in your ~/.cshrc
, but if you don't have one because you don't use csh
, that would still probably get you the right answer.
A similar functionality was not added to the Bourne shell until 1984 in SVR2 with the type
builtin command. The fact that it is builtin (as opposed to an external script) means that it can give you the right information (to some extent) as it has access to the internals of the shell.
The initial type
command suffered from a similar issue as the which
script in that it didn't return a failure exit status if the command was not found. Also, for executables, contrary to which
, it output something like ls is /bin/ls
instead of just /bin/ls
which made it less easy to use in scripts.
Unix Version 8's (not released in the wild) Bourne shell had its type
builtin renamed to whatis
and extended to also report about parameters and print function definitions. It also fixed type
issue of not returning failure when failing to find a name.
rc
, the shell of Plan9 (the once-to-be successor of Unix) (and its derivatives like akanga
and es
) have whatis
as well.
The Korn shell (a subset of which the POSIX sh
definition is based on), developed in the mid-80s but not widely available before 1988, added many of the csh
features (line editor, aliases...) on top of the Bourne shell. It added its own whence
builtin (in addition to type
) which took several options (-v
to provide with the type
-like verbose output, and -p
to look only for executables (not aliases/functions...)).
Coincidental to the turmoil with regards to the copyright issues between AT&T and Berkeley, a few free software shell implementations came out in the late 80s early 90s. All of the Almquist shell (ash
, to be replacement of the Bourne shell in BSDs), the public domain implementation of ksh
(pdksh
), bash
(sponsored by the FSF), zsh
came out in-between 1989 and 1991.
Ash, though meant to be a replacement for the Bourne shell, didn't have a type
builtin until much later (in NetBSD 1.3 and FreeBSD 2.3), though it had hash -v
. OSF/1 /bin/sh
had a type
builtin which always returned 0 up to OSF/1 v3.x. bash
didn't add a whence
but added a -p
option to type
to print the path (type -p
would be like whence -p
) and -a
to report all the matching commands. tcsh
made which
builtin and added a where
command acting like bash
's type -a
. zsh
has them all.
The fish
shell (2005) has a type
command implemented as a function.
The which
csh script meanwhile was removed from NetBSD (as it was builtin in tcsh and of not much use in other shells), and the functionality added to whereis
(when invoked as which
, whereis
behaves like which
except that it only looks up executables in $PATH
). In OpenBSD and FreeBSD, which
was also changed to one written in C that looks up commands in $PATH
only.
Implementations
There are dozens of implementations of a which
command on various Unices with different syntax and behaviour.
On Linux (beside the builtin ones in tcsh
and zsh
) we find several implementations. On recent Debian systems for instance, it's a simple POSIX shell script that looks for commands in $PATH
.
busybox
also has a which
command.
There is a GNU
which
which is probably the most extravagant one. It tries to extend what the which
csh script did to other shells: you can tell it what your aliases and functions are so that it can give you a better answer (and I believe some Linux distributions set some global aliases around that for bash
to do that).
zsh
has a couple of operators to expand to the path of executables: the =
filename expansion operator and the :c
history expansion modifier (here applied to parameter expansion):
$ print -r -- =ls
/bin/ls
$ cmd=ls; print -r -- $cmd:c
/bin/ls
zsh
, in the zsh/parameters
module also makes the command hash table as the commands
associative array:
$ print -r -- $commands[ls]
/bin/ls
The whatis
utility (except for the one in Unix V8 Bourne shell or Plan 9 rc
/es
) is not really related as it's for documentation only (greps the whatis database, that is the man page synopsis').
whereis
was also added in 3BSD
at the same time as which
though it was written in C
, not csh
and is used to lookup at the same time, the executable, man page and source but not based on the current environment. So again, that answers a different need.
Now, on the standard front, POSIX specifies the command -v
and -V
commands (which used to be optional until POSIX.2008). UNIX specifies the type
command (no option). That's all (where
, which
, whence
are not specified in any standard).
Up to some version, type
and command -v
were optional in the Linux Standard Base specification which explains why for instance some old versions of posh
(though based on pdksh
which had both) didn't have either. command -v
was also added to some Bourne shell implementations (like on Solaris).
Status Today
The status nowadays is that type
and command -v
are ubiquitous in all the Bourne-like shells (though, as noted by @jarno, note the caveat/bug in bash
when not in POSIX mode or some descendants of the Almquist shell below in comments). tcsh
is the only shell where you would want to use which
(as there's no type
there and which
is builtin).
In the shells other than tcsh
and zsh
, which
may tell you the path of the given executable as long as there's no alias or function by that same name in any of our ~/.cshrc
, ~/.bashrc
or any shell startup file and you don't define $PATH
in your ~/.cshrc
. If you have an alias or function defined for it, it may or may not tell you about it, or tell you the wrong thing.
If you want to know about all the commands by a given name, there's nothing portable. You'd use where
in tcsh
or zsh
, type -a
in bash
or zsh
, whence -a
in ksh93 and in other shells, you can use type
in combination with which -a
which may work.
Recommendations
Getting the pathname to an executable
Now, to get the pathname of an executable in a script, there are a few caveats:
ls_path=$(command -v ls)
would be the standard way to do it.
There are a few issues though:
- It is not possible to know the path of the executable without executing it. All the
type
,which
,command -v
... all use heuristics to find out the path. They loop through the$PATH
components and find the first non-directory file for which you have execute permission. However, depending on the shell, when it comes to executing the command, many of them (Bourne, AT&T ksh, zsh, ash...) will just execute them in the order of$PATH
until theexecve
system call doesn't return with an error. For instance if$PATH
contains/foo:/bar
and you want to executels
, they will first try to execute/foo/ls
or if that fails/bar/ls
. Now execution of/foo/ls
may fail because you don't have execution permission but also for many other reasons, like it's not a valid executable.command -v ls
would report/foo/ls
if you have execution permission for/foo/ls
, but runningls
might actually run/bar/ls
if/foo/ls
is not a valid executable. - if
foo
is a builtin or function or alias,command -v foo
returnsfoo
. With some shells likeash
,pdksh
orzsh
, it may also returnfoo
if$PATH
includes the empty string and there's an executablefoo
file in the current directory. There are some circumstances where you may need to take that into account. Keep in mind for instance that the list of builtins varies with the shell implementation (for instance,mount
is sometimes builtin for busyboxsh
), and for instancebash
can get functions from the environment. - if
$PATH
contains relative path components (typically.
or the empty string which both refer to the current directory but could be anything), depending on the shell,command -v cmd
might not output an absolute path. So the path you obtain at the time you runcommand -v
will no longer be valid after youcd
somewhere else. - Anecdotal: with the ksh93 shell, if
/opt/ast/bin
(though that exact path can vary on different systems I believe) is in your$PATH
, ksh93 will make available a few extra builtins (chmod
,cmp
,cat
...), butcommand -v chmod
will return/opt/ast/bin/chmod
even if that path doesn't exist.
Determining whether a command exists
To find out if a given command exists standardly, you can do:
if command -v given-command > /dev/null 2>&1; then
echo given-command is available
else
echo given-command is not available
fi
Where one might want to use which
(t)csh
In csh
and tcsh
, you don't have much choice. In tcsh
, that's fine as which
is builtin. In csh
, that will be the system which
command, which may not do what you want in a few cases.
Find commands only in some shells
A case where it might make sense to use which
is if you want to know the path of a command, ignoring potential shell builtins or functions in bash
, csh
(not tcsh
), dash
, or Bourne
shell scripts, that is shells that don't have whence -p
(like ksh
or zsh
), command -ev
(like yash
), whatis -p
(rc
, akanga
) or a builtin which
(like tcsh
or zsh
) on systems where which
is available and is not the csh
script.
If those conditions are met, then:
echo_path=$(which echo)
would give you the path of the first echo
in $PATH
(except in corner cases), regardless of whether echo
also happens to be a shell builtin/alias/function or not.
In other shells, you'd prefer:
- zsh:
echo_path==echo
orecho_path=$commands[echo]
orecho_path=${${:-echo}:c}
- ksh, zsh:
echo_path=$(whence -p echo)
- yash:
echo_path=$(command -ev echo)
- rc, akanga:
echo_path = `{whatis -p echo}
(beware of paths containing whitespace) - fish:
set echo_path (type -fp echo)
Note that if all you want to do is run that echo
command, you don't have to get its path, you can just do:
env echo this is not echoed by the builtin echo
For instance, with tcsh
, to prevent the builtin which
from being used:
set echo_path = "`env which echo`"
When you do need an external command
Another case where you may want to use which
is when you actually need an external command. POSIX requires that all shell builtins (like command
) be also available as external commands, but unfortunately, that's not the case for command
on many systems. For instance, it's rare to find a command
command on Linux based operating systems while most of them have a which
command (though different ones with different options and behaviours).
Cases where you may want an external command would be wherever you would execute a command without invoking a POSIX shell.
The system("some command line")
, popen()
... functions of C or various languages do invoke a shell to parse that command line, so system("command -v my-cmd")
do work in them. An exception to that would be perl
which optimises out the shell if it doesn't see any shell special character (other than space). That also applies to its backtick operator:
$ perl -le 'print system "command -v emacs"'
-1
$ perl -le 'print system ":;command -v emacs"'
/usr/bin/emacs
0
$ perl -e 'print `command -v emacs`'
$ perl -e 'print `:;command -v emacs`'
/usr/bin/emacs
The addition of that :;
above forces perl
to invoke a shell there. By using which
, you wouldn't have to use that trick.
-
27@Joe,
which
is acsh
script on many commercial Unices. The reason is historical, that's why I gave the history, so people understand where it came from, why people got used to using it and why actually there's no reason you should be using it. And yes, some people use (t)csh. Not everybody uses Linux yetStéphane Chazelas– Stéphane Chazelas2013年08月03日 07:39:52 +00:00Commented Aug 3, 2013 at 7:39 -
27After reading this post, I have found a lot of context for the answer, but not the answer itself. Where in this post does it actually say why not to use
which
, as opposed to things you might be trying to usewhich
to do, the history ofwhich
, implementations ofwhich
, other commands to do related tasks, or reasons to actually usewhich
? Why are the other commands better? What do they do differently fromwhich
? How do they avoid its pitfalls? This answer actually spends more words on the problems with the alternatives than the problems withwhich
.user2357112– user23571122014年03月08日 12:24:26 +00:00Commented Mar 8, 2014 at 12:24 -
3
-
4@StéphaneChazelas If I create new file by
touch /usr/bin/mytestfile
and thereafter runcommand -v mytestfile
, it will give the path (whereaswhich mytestfile
does not).jarno– jarno2017年07月02日 20:30:10 +00:00Commented Jul 2, 2017 at 20:30 -
4@jarno, oh yes, you're right.
bash
will settle on a non-executable file if it can't find an executable one, so it's "OK" (though in practice one would rathercommand -v
/type
return an error) as that's the command it would try to execute when you runmytestfile
, but thedash
behaviour is buggy, as if there's a non-executablecmd
ahead of an executable one,command -v
returns the non-executable one while executingcmd
would execute the executable one (the wrong one is also hashed). FreeBSDsh
(also based onash
) has the same bug. zsh, yash, ksh, mksh, bash as sh are OK.Stéphane Chazelas– Stéphane Chazelas2017年07月03日 05:59:30 +00:00Commented Jul 3, 2017 at 5:59
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
($(getconf PATH)
), 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:
$ 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
.
-
mksh
actually uses something different for the default$PATH
: first, the operating system’s compile-time constant_PATH_DEFPATH
is used (most commonly on the BSDs), then,confstr(_CS_PATH, ...)
is used (POSIX), and if both don’t exist or fail,/bin:/usr/bin:/sbin:/usr/sbin
is used.mirabilos– mirabilos2014年02月27日 14:26:55 +00:00Commented Feb 27, 2014 at 14:26 -
1In your 1st example, even if
ls
is a function it is usingls
from PATH. Andwhich
is fine to tell you which one is used/usr/bin/ls
or/usr/local/bin/ls
. I don't see "Why not use which"....rudimeier– rudimeier2017年05月03日 13:12:12 +00:00Commented May 3, 2017 at 13:12 -
@rudimeier, That
which ls
will give me/bin/ls
regardless of whether thels
function calls/bin/ls
or/opt/gnu/bin/ls
ordir
or nothing at all. IOW,which
(that which implementations, IMMV) is giving something irrelevantStéphane Chazelas– Stéphane Chazelas2017年05月03日 13:43:12 +00:00Commented May 3, 2017 at 13:43 -
1@StéphaneChazelas. No, no, no. I know already that my
ls
is a function. I know that myls
function is callingls
fromPATH
. Nowwhich
tells me where the file is. You only see one single use case: "What would my shell do with this command." For this use casewhich
is wrong, correct. But there are other use cases where (GNU)which
is exactly the right thing.rudimeier– rudimeier2017年05月03日 14:18:33 +00:00Commented May 3, 2017 at 14:18 -
@rudimeter, depends on the
which
implementation. Some will tell you it's an alias (if you have an alias configured, or if there is a~/.cshrc
in your home that has such an alias), some will give you a path but the wrong one under some conditions.sh -c 'command -v ls'
, though not perfect is still more likely to give you the right answer to that different requirement (and is also standard).Stéphane Chazelas– Stéphane Chazelas2017年05月03日 15:06:19 +00:00Commented May 3, 2017 at 15:06
One thing which (from my quick skim) it seems that Stephane didn't mention is that which
has no idea about your shell's path hash table. This has the effect that it might return a result which is not representative of what actually is run, which makes it ineffective in debugging.
-
Which means - after trying out the code which Stéphane added in response to Chris' answer - that the shell remembers (hashes) where it found something, and if you later add another version of the same command at a place earlier in the path, the shell will continue to use the version it found originally (until, e.g., you reset the PATH variable).
command -v
knows which version the shell is using.which
doesn't.MikeBeaton– MikeBeaton2021年06月06日 07:04:21 +00:00Commented Jun 6, 2021 at 7:04
I use which
quite often to quickly dive into executable like this: vim $(which my_executable)
. Recently I found this post and was quite surprised -- I have been doing this wrong all the time? But then I checked all the answers and didn't find any reason to drop which
entirely. Moreover, no one answer provided me with drop-in replacement for vim $(which my_executable)
: (output is left as is)
$ type -a pacman
pacman is aliased to `pacman'
pacman is /usr/bin/pacman
$ command -v pacman
alias pacman='pacman'
$ which pacman
/usr/bin/pacman
$ bash --version
GNU bash, version 5.1.8(1)-release (x86_64-pc-linux-gnu)
The which command is a broken heritage from the C-Shell and is better left alone in Bourne-like shells.
I think that you should use any tool as long as it covers all your needs. I use which
to find executable path in $PATH
, and of course it is not apropriate tool to investigate what actually stands behind your command and then type
and command
would fit better.
-
1The problem is that
vim $(which command)
could make you open a file that is completely irrelevant and not actually what is executed when you runcommand
.2021年08月19日 12:21:11 +00:00Commented Aug 19, 2021 at 12:21 -
It's a bit clunky, but
with_path() { "${@:1:$#-1}" "$( unset -f "${@:$#}" ; unalias "${@:$#}" 2>/dev/null ; command -v "${@:$#}" )" ; } ; with_path vim pacman
should edit the actualpacman
executable script, ignoring any aliases and functions.Martin Kealey– Martin Kealey2023年03月22日 12:12:48 +00:00Commented Mar 22, 2023 at 12:12 -
At minimum,
vim "$( which someprog )"
with the double quotes will prevent (most) weirdness with wildcard and whitespace in the resulting filename.Martin Kealey– Martin Kealey2023年03月22日 12:16:05 +00:00Commented Mar 22, 2023 at 12:16
(Since this question is tagged with "portability", various interpretations of the question are excluded, including "interactive use" and "I only care about the system I'm using". So this answer only considers Why shouldn't I do this in a shell script?)
Stéphane has presented a comprehensive analysis of the options, with reasons why each are good or bad. There's no one reason why which
is always the wrong answer. Rather there are a multitude of smaller contributing factors. While you might be tolerant of one or a few of them, taken together they might change your mind.
One reason not yet mentioned for not using which
is that it begs the question:
Why do you want the output of which
in the first place?
By which I mean, sometimes the goal shouldn't be to find a drop-in replacement for which
, but rather to refactor the code in a way that doesn't need it in the first place.
A common pattern is:
CMD=$( which cmd )
# some time later...
$CMD --some --args
Leaving aside the fact that $CMD
is almost always unquoted, I would argue that this entire pattern is broken.
It's one thing to hard-code CMD=/my/path/to/cmd
when the point is to avoid depending on $PATH
.
But if you're going to search in $PATH
anyway, then there's almost always no point. Get rid of which
and $CMD
entirely, and just write:
cmd --some --args
If you think you need the path because you want to embed it in a function or alias, then that's where command
comes in:
function cmd {
command cmd --extra-arg "$@"
}
There are of course exceptions where you need a program based on a different PATH
. In that case consider using:
function cmd {
PATH=$OTHERPATH command cmd "$@"
}
The point, overall, is to avoid having a mental shortcut for "how do I get the path to a command", and instead consider on a case-by-case basis: do I really need the path and why?
In some cases which
may be acceptable, but at least some of the time there will be better solutions. If you're writing a tutorial for other people, please try harder to think of other options.
Even if you think that CMD=$( which cmd ) ; ... $CMD args
is warranted, there are other reasons to avoid which
It's not universal
The list of commands required by POSIX includes command
and type
but not which
.
Its output format is unspecified
which
is only obliged to indicate the path to the named command, not to provide it verbatim and free of commentary.
It's perfectly legal for which foo
to output something like:
foo might be /opt/foobar/bin/foo (unverified)
; orfoo is in /bin
; or~foo/bin/foo
(where~foo
denote's foo's home dir); or/proc/1234/root/bin/foo
(where process 1234 has since terminated)
(Most versions of which
will normally output an unadorned absolute path to the executable, but "most" and "normally" are not portable.)
The answer can get out of date
(This basically excludes all forms of CMD=$( something ) ; ... $CMD ...
and is not specific to which
.)
For various reasons, the location of the executable for a given command name can change.
Sometimes you want the old location, and sometimes you don't.
Occasionally the path to a command is relative (starting at .
rather than /
). This is rare because it's generally frowned upon as a security risk, but might still apply in special cases, in which case simply going cd
can invalidated the path to a program.
I also have a write-up at https://burnthewhich.github.io/
-
Do you feel that
whereis
is any "better" thanwhich
?Seamus– Seamus2024年03月02日 23:26:53 +00:00Commented Mar 2, 2024 at 23:26 -
@Seamus that depends; how and why are you using
which
? Is your use-case covered by any of the scenarios that I've mentioned above?Martin Kealey– Martin Kealey2024年03月04日 01:55:51 +00:00Commented Mar 4, 2024 at 1:55 -
1@Seamus I reiterate this point: before one asks "how do I get the path to a command", it is more appropriate to step back, and ask yourself why do I need the path in this case, and is there a simpler design that means I don't need to know the path at all?Martin Kealey– Martin Kealey2024年03月04日 02:03:26 +00:00Commented Mar 4, 2024 at 2:03
-
PATH=$OTHERPATH command something ...
doesn't change the lookup of something in shell's pre-existing PATH; it only changes the environment passed down to whatever is found standardly (if anything). (command
is a builtin but not a special builtin.)dave_thompson_085– dave_thompson_0852024年12月08日 03:31:39 +00:00Commented Dec 8, 2024 at 3:31 -
@dave_thompson_085 I would have expected that a prefix assignment would be in the environment seen by
command
, so when the inner command is invoked, it's used to search for it. It seems to work that way in all the shells I've tested. However you do raise a valid concern aboutcommand
not being a special built-in; this leaves it implementation defined as to whether the assignment is unwound when the command finishes. That said, I've not encountered an implementation that didn't unwind prefix assignments since about 1990. I will adjust the answer soon.Martin Kealey– Martin Kealey2024年12月09日 09:56:36 +00:00Commented Dec 9, 2024 at 9:56
To be honest which is which. It did work for me across, years, OSs, and platforms.
My suggestion is: use it.
I use it all the time on the command line.
Indeed I will never use it in a script. The point is that I should not need it in a script.
I would not use which to discover a specific version of anything.
There is a little exception. In modern time, not Sys V, coders, did put system commands, "somewhere". Here "which", might, should, defines, the command base.
which
are assuming an interactive shell context. This question is tagged /portability. So i interpret the question in this context as "what to use instead ofwhich
to find the first executable of a given name in the$PATH
". Most answers and reasons againstwhich
deal with aliases, builtins and functions, which in most real-world portable shell scripts are just of academic interest. Locally defined aliases aren't inherited when running a shell script (unless you source it with.
).csh
(andwhich
is still acsh
script on most commercial Unices) does read~/.cshrc
when non-interactive. That's why you'll notice csh scripts usually start with#! /bin/csh -f
.which
does not because it aims to give you the aliases, because it's meant as a tool for (interactive) users ofcsh
. POSIX shells users havecommand -v
.stat $(which ls)
is wrong for several reasons (missing--
, missing quotes), not only the usage ofwhich
). You'd usestat -- "$(command -v ls)"
. That assumesls
is indeed a command found on the file system (not a builtin of your shell, or function of alias).which
might give you the wrong path (not the path that your shell would execute if you enteredls
) or give you an alias as defined in the configuration of some other shells...ls
is a function on my system is the best reason why to usewhich
in this case. Instead ofstat
my real usage is often this onerpm -q --whatprovides $(which ls)
. And yes, I don't use quotes interactively when I know that I don't need any. My PATH never contains whitespaces otherwise I consider it a bug in my setup and accept undefined behavior.which
implementations would not give you even thels
that would be found by a look-up of$PATH
(regardless of whatls
may invoke in your shell).sh -c 'command -v ls'
, orzsh -c 'rpm -q --whatprovides =ls'
are more likely to give you the correct answer. The point here is thatwhich
is a broken heritage fromcsh
.