• Skip to primary navigation
  • Skip to main content
Sal Ferrarello
  • About Sal Ferrarello
  • Speaking
  • Connect
    Mastodon GitHub Twitter (inactive)
You are here: Home / Dev Tips / When command line “which” gives the wrong result
The 'which' command and a pair of pants on fire (a reference to being a liar)

When command line “which” gives the wrong result

Last updated on January 29, 2021 by Sal Ferrarello

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
Sal Ferrarello
Sal Ferrarello (@salcode)
Sal is a PHP developer with a focus on the WordPress platform. He is a conference speaker with a background including Piano Player, Radio DJ, Magician/Juggler, Beach Photographer, and High School Math Teacher. Sal can be found professionally at WebDevStudios, where he works as a senior backend engineer.

Share this post:

Share on TwitterShare on FacebookShare on LinkedInShare on EmailShare on Reddit

Filed Under: Computing, Dev Tips, Solution Tagged With: command line

Reader Interactions

Comments

  1. Joshua Richardson says

    November 20, 2018 at 6:16 pm

    Fantastic post. Thank you!

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Copyright © 2023 · Bootstrap4 Genesis on Genesis Framework · WordPress · Log in