There is a very useful command line tool called which
. This tool determines which file will be executed when you type it. Unfortunately, this command can sometimes provide the wrong answer.
Newest Update: I was using bash as my terminal shell when I wrote this article. Now, I’m using zsh and with this shell the problem no longer occurs (which
successfully detects aliases). Thanks to Aubrey Portwood for pointing this out.
Update: I’ve recently been introduced to the type
command, which seems to do what I want. You can jump to the section on using type if you want to skip the background information.
Background
The PATH environmental variable is a list of directories to check for an executable when it is entered. This means that an executable file, like dig
could be in any one of a number of directories. Using the which
command, we can determine exactly, which file is executed when a command is entered.
$ which dig
/usr/bin/dig
This tells me that when I execute the command dig
, the actual program that is executed is at /usr/bin/dig
. This can be particularly helpful when you have different executables in different places. For example I have two different versions of vim
on my machine, one at /usr/bin/vim
and one at /usr/local/bin/vim
. How do I know which version is run when I enter vim
? I can find out by running which vim
.
$ which vim
/usr/local/bin/vim
When “which” provides the wrong answer
While which
provides the first matching executable on the PATH
, it does not take into account aliases nor functions.
For these demonstrations, I’m going to use two built-in executables logname
(prints the user’s login name) and pwd
(prints the name of current/working directory).
Here is the default behavior for these commands on my machine.
$ logname
sal
$ pwd
/Users/sal
Alias
An alias is an abbreviation to execute a command sequence and it takes precedence over executable files on the PATH
.
For example, we can override the default behavior of logname
with something else (in this case, the pwd
command).
$ alias logname=pwd
$ logname
/Users/sal
As you can see above, logname
is actually giving us the output for pwd
but which
gives the wrong result.
$ which logname
/usr/bin/logname
If we want to check for an alias, we can use alias <command name>
.
$ alias logname
alias logname='pwd'
We can get a list of all aliases with alias
(with no command name).
We can undo an alias with unalias
.
$ unalias logname
$ logname
sal
Function
An function is a code block that implements a set of operations and it takes precedence over executable files on the PATH
.
For example, we can override the default behavior of logname
with our own functionality.
$ function logname() { echo 'There is no logname, only Zuul.'; }
$ logname
There is no logname, only Zuul.
As you can see above, logname
is actually giving us the output “There is no logname, only Zuul.” but which
gives the wrong result.
$ which logname
/usr/bin/logname
If we want to check for a function, we can use declare -f <function name>
.
$ declare -f logname
logname ()
{
echo 'There is no logname, only Zuul.'
}
We can get a list of all functions with declare -F
(with no command name).
We can delete a function with unset -f
.
$ unset -f logname
$ logname
sal
Alias vs function
When both an alias and a function are present, the alias takes precedence regardless of which one was assigned first.
$ alias logname=pwd
$ function logname() { echo 'There is no logname, only Zuul.'; }
$ logname
/Users/sal
$ function logname() { echo 'There is no logname, only Zuul.'; }
$ alias logname=pwd
$ logname
/Users/sal
How to Check What a Command Executes
- Check
alias <command name>
- Check
declare -f <command name>
- If neither of the above return a result, check
which <command name>
A More Powerful which
Looking at the documentation for which
it appears there are a number of options that can be included (e.g. --read-alias
and --read-functions
) unfortunately in the version of bash on my Mac, these flags are not supported (see documentation for which
on Mac).
Since these advanced options are not universally available, I’m going to focus on working with which
without using --read-alias
and --read-functions
.
Using type
Update
Another choice is the type
command. I’ve not had a chance to play around with it a great deal but it seems to do what I want.
$ alias logname=pwd
$ type logname
logname is aliased to `pwd'
$ unalias logname
$ type logname
logname is hashed (/usr/bin/logname)
$ function logname() { echo 'There is no logname, only Zuul.'; }
$ type logname
logname is a function
logname ()
{
echo 'There is no logname, only Zuul.'
}
$ alias logname=pwd
$ type logname
logname is aliased to `pwd'
You’ll notice that when both a function and an alias are set, type
returns the alias (which is consistent with the behavior we outlined earlier in this article).
Example
All of this came about because I have both Vim and Neovim (nvim
) installed on my machine. When I ran vim
, Neovim was executed. Before learning about aliases and functions, I was confused because which
was giving the wrong result.
$ vim
# opens an instance of Neovim
$ which vim
/usr/local/bin/vim
$ /usr/local/bin/vim
# opens an instance of Vim
This was confusing because based on the results of which
, I expected both vim
and /usr/local/bin/vim
to run the same executable, however the first would run Neovim and the second Vim.
It turns out I had an alias in place (that I had probably created some time ago and forgotten about).
$ alias vim
alias vim='nvim'
Using type
, it is easy to discover the alias.
$ type vim
vim is aliased to `nvim'
Once I remove the alias, I got the correct behavior.
$ unalias vim
$ vim
# opens an instance of Vim
Skipping Aliases and Functions
If you want to run a command, while ignoring aliases and functions you can prepend the command with command
.
In my examples with logname
, even if both an alias and function are setup, we can get the command to run with
$ command logname
Fantastic post. Thank you!