Troubleshooting

bash: command not found / zsh: command not found — the three causes and the fix for every tool

Why bash and zsh say 'command not found': the program is missing, not on your PATH, or the name is stale. Diagnose it with type -a and fix PATH for good.

Published ~9 min read

Short answer: “command not found” means the shell looked for a program with that name in every directory listed in $PATH and found none. There are three causes: the program is not installed, it is installed somewhere that is not on your PATH, or the shell is working from a stale or wrong name — a typo, an alias, a remembered old location. The exit status is 127 in each case. The commands below tell you which one it is.

ShellMessage
bashbash: mytool: command not found
bash, login shell (SSH)-bash: mytool: command not found
zshzsh: command not found: mytool
fishfish: Unknown command: mytool
sh / dash (scripts, cron)sh: 1: mytool: not found
sudosudo: mytool: command not found

Find the cause in one minute

bash · zsh
type -a mytool                   # every match: alias, function, builtin, file
command -v mytool                # the path the shell would run, or nothing
echo "$PATH" | tr ':' '\n'       # the directories it searches, one per line

# installed somewhere else? look for the file
sudo find / -xdev -type f -name 'mytool' 2>/dev/null
ls -l /usr/local/bin/mytool ~/.local/bin/mytool 2>/dev/null
  • type and command -v print nothing, and find finds nothing — the program is not installed. Go to cause 1.
  • find finds it, in a directory that is not in the PATH list — go to cause 2.
  • type names an alias or a function, or the file is where the PATH says but the shell still complains — go to cause 3.

Prefer type and command -v to which: they ask the shell itself, so they know about aliases, functions and the shell’s own lookup table. On a Debian or Ubuntu server that answers “not found” with a package suggestion, the command-not-found helper is installed and has already done half the work for you.

Cause 1: the program is not installed

Minimal server images and Docker base images leave out a lot: curl, wget, unzip, ps, even sudo. Install the package with your distribution’s package manager. The package name is often, but not always, the command’s name — the table at the end of this page lists the exceptions.

install a package
sudo apt update && sudo apt install -y curl       # Debian · Ubuntu
sudo dnf install -y curl                          # Rocky · Alma · Fedora
apk add curl                                      # Alpine
sudo pacman -S curl                               # Arch

When you do not know which package ships a command, ask the package manager:

which package provides it?
# Debian · Ubuntu
sudo apt install -y apt-file && sudo apt-file update
apt-file search bin/dig

# Rocky · Alma · Fedora
dnf provides '*/bin/dig'

# Alpine
apk search -v 'cmd:dig'

Two programs are never packages of their own: npx comes with npm, and vite lives inside a project’s node_modules. And yum missing on Debian or Ubuntu is not a bug at all — it is the package manager of another distribution family.

Cause 2: it is installed, but not on PATH

Installers that work without root put programs in your home directory, and an application from a vendor’s tarball lands under /opt. Neither is necessarily on the PATH of the shell you are in. The usual places:

DirectoryWho puts programs there
~/.local/binpip install --user, pipx, many install scripts
~/.cargo/binrustup and cargo install
/usr/local/go/bin, ~/go/binGo from the official tarball, go install
~/.nvm/versions/node/…/binNode.js through nvm (its script adds it to the shell)
~/miniconda3/binMiniconda and Anaconda, until conda init has run
/snap/binSnap packages on Ubuntu
/opt/<app>/binVendor tarballs and some .deb or .rpm packages
/usr/sbin, /sbinSystem tools: useradd, reboot, iptables — missing after su without the dash

To use the program right now, call it by its full path or extend the PATH of the current shell. That lasts until you close the terminal:

for this shell only
~/.local/bin/mytool --version
export PATH="$HOME/.local/bin:$PATH"

Add a directory to PATH permanently

The PATH is set again in every new shell, from the shell’s startup files. Put the line in the file your shell reads, open a new terminal (or log in again), and check with command -v mytool.

bash

A terminal window reads ~/.bashrc; an SSH login reads ~/.bash_profile or, if that does not exist, ~/.profile. On Debian, Ubuntu, Rocky and Alma the login file already loads ~/.bashrc, so one line there covers both:

~/.bashrc
# add ~/.local/bin once, in front of the system directories
case ":$PATH:" in
  *":$HOME/.local/bin:"*) ;;
  *) export PATH="$HOME/.local/bin:$PATH" ;;
esac
apply it now
source ~/.bashrc

If you created ~/.bash_profile yourself, it replaces ~/.profile and may not load ~/.bashrc. Add [ -f ~/.bashrc ] && . ~/.bashrc to it. Debian and Ubuntu’s stock ~/.profile also adds ~/.local/bin on its own — but only if the directory existed when you logged in, so log in again after the first install that creates it.

zsh (and macOS)

~/.zshrc
typeset -U path                  # no duplicates
path=("$HOME/.local/bin" $path)

~/.zshrc is read by every interactive zsh, which is the default shell on macOS. For scripts that run zsh non-interactively, the place is ~/.zshenv. After installing a program while zsh is open, run rehash or open a new terminal.

fish

fish 3.2 and later — persists on its own
fish_add_path ~/.local/bin

For every user on the server

/etc/profile.d/mytool.sh
export PATH="/opt/mytool/bin:$PATH"

Every login shell reads the files in /etc/profile.d/. A PATH= line in /etc/environment also works for every login, but that file takes no variables: write the whole list out.

Cause 3: a stale lookup, an alias or a typo

  • The shell remembers the old location. bash and zsh cache where they found a command. After a program moves — you removed a distribution package and installed a newer copy in /usr/local/bin, say — the cache points at a file that is gone. hash -r in bash, rehash in zsh.
  • An alias or a function shadows it. type -a mytool lists them first. unalias mytool removes an alias for this shell; then find it in your startup files.
  • Case and spelling. Linux is case-sensitive, so Python is not python, and python itself may only exist as python3.
  • The script, not the command. A script that exists but starts with a wrong #! line or Windows line endings fails with “bad interpreter: No such file or directory” rather than “not found”. And ./script.sh without the execute bit gives Permission denied.

Works in your terminal, fails in cron, sudo, systemd or Docker

Your terminal’s PATH comes from your startup files. Everything else starts with a PATH of its own, usually a short one:

  • sudo replaces PATH with secure_path from /etc/sudoers, so a program in ~/.local/bin or an nvm install is “not found” under sudo. Call it by its full path: sudo "$(command -v mytool)".
  • cron runs jobs with PATH=/usr/bin:/bin. Set a PATH= line at the top of the crontab, or use absolute paths in the job.
  • systemd does not read your shell’s startup files. Give ExecStart= the absolute path of the program, and set Environment=PATH=… if the program itself starts others.
  • Docker images contain only what their Dockerfile installs. Install the package in the image, not in the running container.

Each of these, with the exact lines to change, is in exit code 127 and, for cron, in cron job not running.

Every tool: package and fix

The package to install on Debian and Ubuntu, or what to do instead, and the article that covers each tool on every distribution, in Docker and on macOS where it matters.

CommandDebian / UbuntuArticle
curlcurlcurl: command not found
wgetwgetwget: command not found
gitgitgit: command not found
sudosudo, installed as rootsudo: command not found
makebuild-essential (or make)make: command not found
dockerDocker's own repository (docker-ce)docker: command not found
docker-composedocker-compose-plugin, then docker composedocker-compose: command not found
systemctlNothing to install: the system does not run systemdsystemctl: command not found
apt-get, aptPart of every Debian and Ubuntu: missing means another systemapt-get: command not found
add-apt-repositorysoftware-properties-commonadd-apt-repository: command not found
pythonpython3, python-is-python3python: command not found
pippython3-pip, python3-venvpip: command not found
condaInstalled already? Run conda init onceconda: command not found
npxComes with npm: Node.js from NodeSource or nvmnpx: command not found
nvmA shell function: its lines belong in ~/.bashrc or ~/.zshrcnvm: command not found
viteA project dependency: npm install, then npx vitevite: command not found
yarn, pnpmcorepack enable, or npm install -gyarn or pnpm: command not found
dig, nslookup, hostbind9-dnsutilsdig, nslookup or host: command not found
ifconfig, netstatnet-tools — or use ip and ssifconfig: command not found
ps, top, freeprocpsps: command not found
unzipunzip (and zip)unzip: command not found
yumUse apt here; yum and dnf belong to Rocky, Alma and RHELyum: command not found
brewNot needed here, apt is the package manager; on a Mac install Homebrewbrew: command not found
Any, in a script or jobExit status 127Exit code 127

Frequently asked

What does "command not found" mean in Linux?

The shell searched every directory in your PATH for a program with that name and found none. Either the program is not installed, it is installed in a directory that is not on your PATH, or the name is wrong — a typo, an alias, or a location the shell remembered from before. The exit status is 127.

How do I add a directory to PATH permanently?

Add export PATH="$HOME/.local/bin:$PATH" to ~/.bashrc for bash, path=("$HOME/.local/bin" $path) to ~/.zshrc for zsh, or run fish_add_path ~/.local/bin once in fish, then open a new terminal. For every user on the server, put the export line in a file under /etc/profile.d/.

Why does a command work in my terminal but not in cron or with sudo?

Both start with a PATH of their own. cron runs jobs with /usr/bin:/bin, and sudo replaces your PATH with secure_path from /etc/sudoers. Use the program's full path — command -v shows it — or set PATH at the top of the crontab.

Why does zsh still say command not found after I installed the program?

zsh remembers where it found commands. Run rehash or open a new terminal. If the program went into a directory that is not on your PATH, add that directory in ~/.zshrc.

Related articles

All troubleshooting