1. Overview

which is a common tool on Linux systems for locating the executable for a given command. We can use which to check if there’s a command on our system and its configured paths. However, many experienced users don’t recommend the use of which.

In this tutorial, we’ll learn what which does, why it can cause problems, and some alternatives we can use instead.

Also, we ran this code on Ubuntu 20.04.3 with root permission to execute the commands.

2. which Command

which is a simple tool that allows us to find out where a certain command or program is located on our system. It takes one or more arguments. The arguments are the names of the commands that we want to locate. Then, which searches for each argument in each directory from the list of colon-separated paths specified in the $PATH environment variable. Finally, the output contains the full path of the first, if any, match found for each argument.

For instance, let’s find out where the ls command is located:

$ which ls
/usr/bin/ls

This shows that the ls command is located in /usr/bin/ls.

If we pass multiple arguments to which, it outputs the found path for each one in the same order.

Let’s see where the grepawk, and sed executables are:

$ which grep awk sed
/usr/bin/grep
/usr/bin/awk
/usr/bin/sed

In the result, the location of each command is displayed in the same order we passed them to which.

Notably, which has a single option, -a, for printing all matches for each argument, and not just the first one. In fact, this can be useful if there’re multiple versions of the same program in different directories.

For instance, we can find out all the matches for the python3 interpreter:

$ which -a python3
/usr/bin/python3
/bin/python3

The result shows both matches for python3.

3. Drawbacks of which

Although which may seem like a handy tool for finding commands on Linux systems, it has some limitations and drawbacks that can make it unreliable.

3.1. which and Shell Differences

One of the problems with using which is that it behaves differently depending on the shell that’s used. Notably, different shells may have different implementations of which, or may not have it at all.

For instance, zsh has a built-in which command that can show aliases, functions, and built-ins, while Bash doesn’t. Some shells may use an external which command that’s a script or a binary file, while others may use an internal which command that’s part of the shell itself.

So, this means that using which can give inconsistent results depending on the shell that’s used.

For instance, let’s see the behavior of running which in Bash if we have an alias called ls that runs the command ls –color=auto:

$ which ls
/usr/bin/ls

The output from Bash shows the path of the original ls command, but it doesn’t show the alias that we’ve defined.

However, the behavior is quite different in zsh:

$ which ls
ls: aliased to ls --color=auto

This shows the alias that we’ve defined, but it doesn’t show the path of the original ls command.

3.2. which and Environment Variables

Besides, another problem is that which relies on the $PATH environment variable to search for commands. Thus, $PATH can affect the accuracy and reliability of the output.

The $PATH environment variable is a list of directories that the shell searches in when looking for commands to run. The order of the directories in the $PATH variable matters. In fact, the shell stops searching once it finds a match for a given command name.

However, the $PATH variable can be modified, corrupted, or overridden by other variables or scripts. Obviously, this can affect how which works.

For instance, let’s say we have two versions of Python installed on our system:

  • /usr/bin/python3
  • /usr/local/bin/python3

The latter one is newer and has more features than the former one. If we want to use the newer version of Python by default, we can add /usr/local/bin to the beginning of our $PATH variable. This makes its priority higher than that in /usr/bin.

3.3. Caching

Yet, if we run which python3, we may get /usr/bin/python3 as the output even if a python3 instance exists in a preceding $PATH directory. This is because which may be using a cached or outdated version of the $PATH variable that doesn’t reflect our changes.

Alternatively, if we run a script that changes or overrides our $PATH variable temporarily, running which python3 may give us a different output than before or no output at all.

Consequently, using which can be inaccurate and unreliable when searching for commands based on the $PATH environment variable alone.

3.4. which and Aliases

In shells other than tcsh and zsh, the behavior of which with aliases depends on how the aliases are defined and how the $PATH environment variable is set. In a shell like Bash, which doesn’t recognize or report aliases correctly. Ultimately, this can lead to inaccurate results, especially if we declared the aliases interactively.

However, if which is a shell built-in as is the case in zsh, it will try to reconstruct the aliases from the shell’s startup files.

For instance, let’s create an alias for grep and run which with the alias as an argument:

$ alias grep='grep -E'
$ which grep
/usr/bin/grep

Here, we only see the location of the grep command. However, this is not what we want if we need to know the exact command that’ll be executed when we type grep in our shell.

Additionally, if we create an alias called gr that runs the command grep -Ewhich won’t return any location.

4. Alternatives to which

Of course, there’re some other commands that can help us find or verify executable locations on Linux systems.

4.1. The type Command

The type command is a shell built-in that displays information about how a given command name would be interpreted by the shell. It can show whether a name is an alias, a function, or a built-in command:

$ type ls
ls is aliased to `ls --color=auto'

This output shows that ls is an alias that runs the command ls –color=auto.

Furthermore, we can use type to see if a command is a shell built-in:

$ type echo
echo is a shell builtin

In this case, echo is a built-in command that’s part of the shell itself.

Additionally, type can show the path of an external command:

$ type python3
python3 is /usr/bin/python3

Here, we see that python3 is an external command and its path is /usr/bin/python3.

Also, we can use type to show the path to a command only if the command is an executable file using the -p option:

$ type -p grep sed awk
/usr/bin/grep
/usr/bin/sed
/usr/bin/awk

The type command can also show all the matches for a given name by using the -a option:

$ type -a python3
python3 is /usr/bin/python3
python3 is /bin/python3

The type command is often more reliable and informative than which. However, the type command may not be available in all shells or systems.

4.2. The command Command

The command command is similar to the type command. For instance, the -V option of command is equivalent to type without any option. However, command has some differences in syntax and functionality.

Of course, command can show the path of an external command or an executable by using the -v option:

$ command -v grep sed awk
/usr/bin/grep
/usr/bin/sed
/usr/bin/awk

In particular, we can employ command to execute a program without invoking any shell function, built-in, or alias:

$ command ls
bin   dev  home  lib32  libx32  mnt  proc  run   srv  tmp  var
boot  etc  lib   lib64  media   opt  root  sbin  sys  usr

Notably, this executes the ls command without any interference from shell functions that may have been defined with the same name.

4.3. The whereis Command

The whereis command is an external program that shows the path of a command file. Additionally, whereis shows the manual page file of the command passed to it as an argument.

For instance, let’s find the path of the ls command file as well as its manual page file:

$ whereis ls
ls: /usr/bin/ls /usr/share/man/man1/ls.1.gz /usr/share/man/man1/ls.1posix.gz

This shows that the path of the ls command file is /usr/bin/ls. Furthermore, it shows that the path of its manual page file is /usr/share/man/man1/ls.1.gz. Finally, we can see an additional manual page file for the ls command that follows the POSIX standard.

Furthermore, we can use the -b option to see only the executable path.

5. Conclusion

In this article, we’ve discussed the which command and why its use isn’t generally recommended.

which is a common tool for system administrators to use when looking for the location of a command executable. However, it’s sometimes better to avoid which because it may have unexpected behavior in different shell environments.

Additionally, we’ve seen the behavior of which with aliases and how the environment variables affect it. Finally, we discussed some alternatives to which that we can use to find command executables on Linux systems.