Home » Why not use “which”? What to use then?

Why not use “which”? What to use then?


Here is all you never thought you would ever not want to know about it:


To get the pathname of an executable in a Bourne-like shell script (there are a few caveats; see below):

ls=$(command -v ls)

To find out if a given command exists:

if command -v given-command > /dev/null 2>&1; then
  echo given-command is available
  echo given-command is not available

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.


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 sourceing 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.


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
$ cmd=ls; print -r -- $cmd:c

zsh, in the zsh/parameters module also makes the command hash table as the commands associative array:

$ print -r -- $commands[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.


Getting the pathname to an executable

Now, to get the pathname of an executable in a script, there are a few caveats:

ls=$(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 the execve system call doesn’t return with an error. For instance if $PATH contains /foo:/bar and you want to execute ls, 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 running ls 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 returns foo. With some shells like ash, pdksh or zsh, it may also return foo if $PATH includes the empty string and there’s an executable foo 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 busybox sh), and for instance bash 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 run command -v will no longer be valid after you cd somewhere else.
  • Anecdotal: with the ksh93 shell, if /opt/ast/bin (though that exact path can vary on different systems I believe) is in you $PATH, ksh93 will make available a few extra builtins (chmod, cmp, cat…), but command -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
  echo given-command is not available

Where one might want to use which


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=$(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==echo or echo=$commands[echo] or echo=${${:-echo}:c}
  • ksh, zsh: echo=$(whence -p echo)
  • yash: echo=$(command -ev echo)
  • rc, akanga: echo=`whatis -p echo` (beware of paths with spaces)
  • fish: set echo (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 = "`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"'
$ perl -le 'print system ":;command -v emacs"'

$ perl -e 'print `command -v emacs`'
$ perl -e 'print `:;command -v emacs`'

The addition of that :; above forces perl to invoke a shell there. By using which, you wouldn’t have to use that trick.

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

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
which: no . in (/bin:/usr/bin)

(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

(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


$ 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|"'
$ 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
$ 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
$ cp /bin/ls bin
$ type ls
ls is hashed (/bin/ls)
$ command -v ls
$ which 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
$ 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
$ type foo
foo is b/foo

Now, with some shells:

$ foo
bash: ./b/foo: /: bad interpreter: Permission denied

With others:

$ 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.

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.

Related Solutions

AddTransient, AddScoped and AddSingleton Services Differences

TL;DR Transient objects are always different; a new instance is provided to every controller and every service. Scoped objects are the same within a request, but different across different requests. Singleton objects are the same for every object and every...

How to download package not install it with apt-get command?

Use --download-only: sudo apt-get install --download-only pppoe This will download pppoe and any dependencies you need, and place them in /var/cache/apt/archives. That way a subsequent apt-get install pppoe will be able to complete without any extra downloads....

What defines the maximum size for a command single argument?

Answers Definitely not a bug. The parameter which defines the maximum size for one argument is MAX_ARG_STRLEN. There is no documentation for this parameter other than the comments in binfmts.h: /* * These are the maximum length and maximum number of strings...

Bulk rename, change prefix

I'd say the simplest it to just use the rename command which is common on many Linux distributions. There are two common versions of this command so check its man page to find which one you have: ## rename from Perl (common in Debian systems -- Ubuntu, Mint,...

Output from ls has newlines but displays on a single line. Why?

When you pipe the output, ls acts differently. This fact is hidden away in the info documentation: If standard output is a terminal, the output is in columns (sorted vertically) and control characters are output as question marks; otherwise, the output is...

mv: Move file only if destination does not exist

mv -vn file1 file2. This command will do what you want. You can skip -v if you want. -v makes it verbose - mv will tell you that it moved file if it moves it(useful, since there is possibility that file will not be moved) -n moves only if file2 does not exist....

Is it possible to store and query JSON in SQLite?

SQLite 3.9 introduced a new extension (JSON1) that allows you to easily work with JSON data . Also, it introduced support for indexes on expressions, which (in my understanding) should allow you to define indexes on your JSON data as well. PostgreSQL has some...

Combining tail && journalctl

You could use: journalctl -u service-name -f -f, --follow Show only the most recent journal entries, and continuously print new entries as they are appended to the journal. Here I've added "service-name" to distinguish this answer from others; you substitute...

how can shellshock be exploited over SSH?

One example where this can be exploited is on servers with an authorized_keys forced command. When adding an entry to ~/.ssh/authorized_keys, you can prefix the line with command="foo" to force foo to be run any time that ssh public key is used. With this...

Why doesn’t the tilde (~) expand inside double quotes?

The reason, because inside double quotes, tilde ~ has no special meaning, it's treated as literal. POSIX defines Double-Quotes as: Enclosing characters in double-quotes ( "" ) shall preserve the literal value of all characters within the double-quotes, with the...

What is GNU Info for?

GNU Info was designed to offer documentation that was comprehensive, hyperlinked, and possible to output to multiple formats. Man pages were available, and they were great at providing printed output. However, they were designed such that each man page had a...

Set systemd service to execute after fstab mount

a CIFS network location is mounted via /etc/fstab to /mnt/ on boot-up. No, it is not. Get this right, and the rest falls into place naturally. The mount is handled by a (generated) systemd mount unit that will be named something like mnt-wibble.mount. You can...

Merge two video clips into one, placing them next to each other

To be honest, using the accepted answer resulted in a lot of dropped frames for me. However, using the hstack filter_complex produced perfectly fluid output: ffmpeg -i left.mp4 -i right.mp4 -filter_complex hstack output.mp4 ffmpeg -i input1.mp4 -i input2.mp4...

How portable are /dev/stdin, /dev/stdout and /dev/stderr?

It's been available on Linux back into its prehistory. It is not POSIX, although many actual shells (including AT&T ksh and bash) will simulate it if it's not present in the OS; note that this simulation only works at the shell level (i.e. redirection or...

How can I increase the number of inodes in an ext4 filesystem?

It seems that you have a lot more files than normal expectation. I don't know whether there is a solution to change the inode table size dynamically. I'm afraid that you need to back-up your data, and create new filesystem, and restore your data. To create new...

Why doesn’t cp have a progress bar like wget?

The tradition in unix tools is to display messages only if something goes wrong. I think this is both for design and practical reasons. The design is intended to make it obvious when something goes wrong: you get an error message, and it's not drowned in...

OpenSSH: How to end a match block

To end up a match block with openssh 6.5p1 or above, use the line: Match all Here is a piece of code, taken from my /etc/ssh/sshd_config file: # Change to no to disable tunnelled clear text passwords PasswordAuthentication no Match host