which — where is that program, really?

One line, and you know exactly which binary the shell is about to run.

You thought you ran python3. The box ran a totally different python3. Here's the one command that would have caught it.

What it does

which walks the PATH directories in order and prints the absolute path of the first executable it finds matching the name you give it. No command, you get nothing; a name that's not on PATH, you get silence and an exit code of 1; and when more than one binary with that name is on PATH, plain `which` shows only the first match. It answers the single most useful question in debugging: when I type this name, which file is actually going to run?

Why it matters

Version mismatch bugs are the classic: you `pip install`ed a newer tool, kept typing the name, and it kept running the old binary because the new one landed earlier in PATH — or later. `which python3` (or `which -a python3`) shows you exactly which paths are in play and in what order. It's also why every script that starts with `if ! which docker; then...` is politely telling you the dependency isn't installed. Two minutes of `which` beats an hour of blaming the wrong version.

Examples

which python3 git docker curl
/usr/bin/python3
/usr/bin/git
/usr/bin/docker
/usr/bin/curl

The everyday ask: give it several names at once and it returns each absolute path on its own line. If one name in the list is missing, which prints the rest and returns 1 overall — so a non-zero exit doesn't necessarily mean every lookup failed.

which -a python3
/usr/bin/python3
/bin/python3
/usr/bin/python3
/home/kmail/.hermes/hermes-agent/venv/bin/python3
/usr/bin/python3
/bin/python3

-a shows EVERY match along PATH, not just the first. When you've got a version manager, a venv, and a system install all shadowing each other, this is the command that explains half of your 'wrong version' bugs. (Duplicates appear when the same directory is reachable via several symlink spellings of PATH.)

which definitely-not-installed

A name that's nowhere on PATH prints nothing at all and returns exit status 1 — the shell's silent 'not found'. This is the exact check wrapping scripts use: `if ! which tool; then echo "install tool first"; exit 1; fi`.

which cd; echo "shell: $?"
command -v cd; echo "command -v: $?"
shell: 1
cd
command -v: 0

The trap that bites everyone: `which cd` finds nothing because cd is a shell BUILTIN, not a file on disk. `command -v cd` handles both worlds — it reports builtins too (printing the name) and returns 0. Use command -v for the accurate full test; use which when you specifically want the disk path.

which ssh scp rsync
/usr/bin/ssh
/usr/bin/scp
/usr/bin/rsync

Multi-tool sanity check before a script that shells out to remote-copy tools: if any of these comes back empty, your provisioning script is about to fail at line 3 instead of line 40.

Flags

FlagMeaning
which NAME...Print the first PATH match for each NAME, one per line. Exit 0 if every name is found, 1 if any is missing.
-aShow ALL matches along PATH, in order — reveals every shadowed copy of the same binary.
-s, --silentNo output, just an exit status: 0 if found, 1 if not. The version for scripts — test `which -s` in an if without scattering paths on screen.
command -vNot a flag but the accurate alternative: reports builtins and functions too, not only files on PATH. The right tool when you must know the name resolves at all.
type -aThe verbose superset of which: shows aliases, functions, builtins, and every PATH match with labels. Best for truly confusing name collisions.
PATH order matterswhich only reports the first match by default — which one wins depends entirely on directory order in your PATH, not on newest install.
which whichYes, this works, and the meta-answer is the which binary itself — a fun first smoke test that your PATH is intact.

A tiny utility with a long Unix lineage

which has been around since early Unix — predating the Bourne shell itself — as a quick way to answer 'where does this command live'. On modern systems it's usually the shell-agnostic /usr/bin/which (from GNU coreutils or a BSD equivalent), a deliberately small program whose whole job is one PATH walk. Its simplicity is the feature: no arguments, no config, one answer per name. The modern shells later grew their own richer lookups (type, command -v) that understand aliases and builtins — but plain which stays the go-to when you want the literal file path.

The -a flag exists because of shadowing

The single-match default was fine when every machine had one Python, one sed, one awk. Then version managers (pyenv, rbenv, nvm), virtualenvs, and container host paths started putting several copies of the same name on PATH. `which -a` was the escape hatch: show every copy so you can see the shadowing that plain which hides. The rise of per-project environments is exactly why `which -a python3` became a debugging ritual.

Fun facts

Pros

Cons

Takeaways