open atlas
↑ Back to track
Command line CLI · 04 · 04

Locating commands with which and type

which, type, and command -v locate commands by searching $PATH and the shell's own lookup table. type also reveals whether a name is a builtin, function, or alias — which only finds external executables.

CLI Junior ◷ 16 min
Level
FoundationsJuniorMiddleSenior

You install a new version of Python, run python3 --version, and it still reports the old version. Why? Because the shell is finding a different python3 earlier in $PATH. Understanding how the shell locates commands — and the tools that expose that lookup — is essential for debugging environment problems, managing multiple tool versions, and writing reliable scripts.

By the end of this lesson you will know how $PATH drives command lookup and how which, type, and command -v each illuminate it differently.

Goal

After this lesson you can explain how the shell uses $PATH to find executables, use which to locate an external command’s path, use type to distinguish builtins, functions, aliases, and external commands, and use command -v as the portable scripting alternative to which.

1

$PATH is a colon-separated list of directories. When you type a command name without a path, the shell searches each directory in $PATH left to right and runs the first match it finds:

echo $PATH

Typical output:

/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

The shell checks /usr/local/bin first. If a matching executable exists there, it is used — everything later in the list is ignored. This is why pyenv and nvm prepend their shim directories to $PATH: they intercept the lookup before the system default.

2

which finds the path of an external executable. It searches $PATH in order and prints the first match:

which python3

Output:

/usr/bin/python3
which node
/home/alice/.nvm/versions/node/v20.0.0/bin/node

which only looks at the filesystem — it does not know about shell builtins, functions, or aliases. If you type which cd, it will either print nothing or an incorrect result, because cd is a shell builtin and has no path on disk.

3

type reveals the full truth about a name. type is a bash builtin that consults the shell’s own lookup table — builtins, functions, aliases, and then $PATH:

type cd
cd is a shell builtin
type ls
ls is aliased to 'ls --color=auto'
type python3
python3 is /usr/bin/python3
type ll
ll is a function
ll ()
{
    ls -alF
}

Use type -a name to see all matches across the lookup order, not just the first:

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

This reveals that /usr/local/bin/python3 shadows /usr/bin/python3.

4

command -v is the POSIX-portable alternative for scripts. which is not specified by POSIX and behaves differently across systems. In shell scripts, use command -v instead:

command -v git
/usr/bin/git

In a script, the idiomatic pattern to check whether a command is available:

if ! command -v docker &>/dev/null; then
  echo "docker is not installed" >&2
  exit 1
fi

command -v returns exit code 0 if the command is found, non-zero otherwise. It also recognises builtins and functions. It does not print aliases — that is intentional for scripting purposes.

5

Inspect and modify $PATH. To add a directory to $PATH for the current session:

export PATH="/opt/myapp/bin:$PATH"

The new directory is prepended, so it is searched first. To make this permanent, add the same line to ~/.bashrc (bash) or ~/.zshrc (zsh).

To see where a command is being loaded from and diagnose version conflicts:

type -a node          # all node executables in PATH order
which -a node         # GNU which -a: same, for external files only
Worked example

Diagnosing a wrong Python version.

After installing Python 3.12 via pyenv, python3 --version still reports 3.10. Investigate:

which python3
/usr/bin/python3

The system Python is winning. Check what pyenv put in $PATH:

echo $PATH | tr ':' '\n' | head -5
/home/alice/.pyenv/shims
/home/alice/.pyenv/bin
/usr/local/bin
/usr/bin
/bin

The pyenv shims are there. But which found /usr/bin/python3 — that means pyenv’s shim for python3 is not in place:

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

Only one entry. The shim is missing because pyenv needs to be told which version to use:

pyenv global 3.12.0
type -a python3
python3 is /home/alice/.pyenv/shims/python3
python3 is /usr/bin/python3

Now the shim is first. python3 --version reports 3.12.0.

Why this works

which on macOS vs Linux. On macOS, which is /usr/bin/which and does not support -a by default (some versions do, some don’t). On Linux with GNU which, -a lists all matches. In zsh (the default macOS shell), which is actually a builtin that does show aliases and functions — making it behave like type. This inconsistency is why command -v is preferred in portable scripts: it is defined by POSIX, available in every POSIX shell, and behaves consistently.

Common mistake

Relying on which to check if a command exists in scripts. which exits 0 even when it prints nothing on some systems. The correct, portable idiom is:

# WRONG — unreliable across systems
if which mycommand; then

# CORRECT
if command -v mycommand &>/dev/null; then

The &>/dev/null suppresses both stdout and stderr so the check is silent. command -v reliably exits non-zero when the command is not found.

Check yourself
Quiz

You run: which cd. The output is empty. Why?

Recap

The shell resolves a command name in order: aliases → functions → builtins → $PATH. which searches only $PATH for external executables and knows nothing about builtins, functions, or aliases. type (bash builtin) shows the full picture — what category the name falls into and its value; type -a lists all matches in order. command -v is the POSIX-portable equivalent for scripts: it exits 0 if the command is found (by any means) and non-zero otherwise. When a command resolves to the wrong version, type -a and echo $PATH together reveal which directory is winning the lookup.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 4 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.