Fixing "command not found" Errors - Solutions When Commands Are Missing
What You'll Learn
- Diagnose
command not founderrors in under 30 seconds - Recognize typical patterns for
PATH/hash/sudo/cron - Stop the "I installed it but it's not found" problem from recurring
Quick Summary
- Check existence with
command -v <name> - Show search paths with
echo $PATH - The cause is almost always one of: missing package / PATH not set / stale hash cache / sudo secure_path / typo
Target Environment
- OS: Ubuntu / Debian (apt). RHEL family (dnf / yum) included where relevant
- Shell: bash / zsh
1. The 5 Root Causes
Almost every command not found error traces back to one of these:
| # | Cause | Diagnostic key |
|---|---|---|
| 1 | Command is not installed | apt-cache search / dnf provides |
| 2 | Executable is not in PATH |
echo $PATH / which -a |
| 3 | Shell's hash cache is stale |
hash -r |
| 4 | sudo's secure_path restriction |
sudo -V / secure_path in /etc/sudoers |
| 5 | Typo (case sensitivity / misspelling) | type / compgen -c |
80% of cases are #2 PATH or #3 hash. Always check those two first.
2. Diagnostic Steps (30-Second Template)
2-1. Check existence first
$ command -v ansible
- Output present → the command is visible. If it still fails, that's a permission issue (not
command not found). - No output → continue to 2-2.
command -v is POSIX standard. which behavior varies by distro (Debian's which only returns an exit status), so prefer command -v in scripts. Use type for investigation since it distinguishes alias / function / builtin / file.
2-2. Show PATH
$ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Verify that the expected install directory is in PATH. Common ones:
~/.local/bin(pip install --user/pipx)~/bin(auto-added by Ubuntu's~/.profile, but only for bash login shells)/usr/local/bin(manually built tools)/snap/bin(snap-installed)
2-3. Search for the package
If the command isn't installed at all, find which package provides it.
# Ubuntu / Debian $ apt-cache search ^ansible$ $ /usr/lib/command-not-found ansible # Ubuntu's helpful hint
# RHEL / Fedora $ dnf provides '*/ansible'
3. Case-by-Case Fixes
3-1. Just installed but still not found
The shell has cached an old path via hash.
$ hash -r # works in both bash and zsh $ rehash # zsh idiom
If that doesn't help, either the new package's bin directory isn't in PATH, or you installed it as a different user (e.g., root).
3-2. pip install --user command not found
Install location is ~/.local/bin. Add it to PATH.
$ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc $ source ~/.bashrc
For Python CLIs, prefer pipx. Running pipx ensurepath configures PATH automatically, so it's safer than pip install --user.
3-3. npm install -g command not found
Check the install prefix with npm config get prefix. If it's /usr/local, you may have a permissions issue. If it's a custom prefix like ~/.npm-global, add its bin to PATH.
$ npm config get prefix $ ls "$(npm config get prefix)/bin"
3-4. Case sensitivity / typo
Linux file names are case-sensitive.
$ Vim file.txt # → command not found $ vim file.txt # OK
List candidates:
$ compgen -c vim # commands starting with "vim"
3-5. Not found after switching shells
When switching bash → zsh, the file where PATH is configured changes.
| Shell | Login shell | Interactive shell |
|---|---|---|
| bash | ~/.bash_profile → ~/.profile |
~/.bashrc |
| zsh | ~/.zprofile |
~/.zshrc |
~/.profile is read by POSIX shells. ~/.bashrc is bash-only and is not read by zsh. If you switch to zsh, move your PATH settings to ~/.zshrc.
4. Persisting PATH the Right Way
4-1. Recommended pattern
# At the end of ~/.bashrc or ~/.zshrc export PATH="$HOME/.local/bin:$PATH"
- Order (before vs. after
$PATH) controls priority - Put your custom scripts before
$PATHto override system commands - Put them after to let system commands win
Never overwrite PATH
# WRONG: wipes out the existing PATH export PATH="$HOME/.local/bin"
This breaks ls, cat, sudo — everything. Always keep $PATH and append.
4-2. System-wide PATH
Use /etc/profile.d/*.sh. Note that /etc/environment only accepts variable assignments (no shell syntax).
# /etc/profile.d/myapp.sh export PATH="/opt/myapp/bin:$PATH"
5. Not Found Under sudo / cron
5-1. command not found with sudo
sudo overrides PATH with secure_path from /etc/sudoers.
$ sudo -V | grep -i path Value to override user's $PATH with: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Fixes:
# (1) Use the full path (most reliable) $ sudo /home/user/.local/bin/myscript # (2) Carry over the environment with sudo -E (may be ignored when secure_path is set) $ sudo -E env "PATH=$PATH" myscript # (3) Permanent fix: edit secure_path with visudo $ sudo visudo # Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/user/.local/bin"
secure_path exists to prevent PATH-injection attacks during privilege escalation. Don't widen it carelessly — only add the directories you actually need.
5-2. command not found in cron
cron starts with a minimal PATH.
$ env -i sh -c 'echo $PATH' /usr/bin:/bin
Fixes (in order of preference):
# (1) Declare PATH at the top of crontab
PATH=/usr/local/bin:/usr/bin:/bin:/home/user/.local/bin
* * * * * myscript
# (2) Use absolute paths in the script
* * * * * /home/user/.local/bin/myscript
70% of cron problems are PATH. The next biggest issue is unredirected stdout/stderr. Always append >> /tmp/cron.log 2>&1 so you can see what's happening.
6. Copy-Paste Diagnostic Template
# 1. Check existence command -v ansible # 2. Show PATH echo $PATH # 3. Clear hash (the go-to fix right after install) hash -r command -v ansible # 4. Search for the package apt-cache search ^ansible$ # Debian / Ubuntu dnf provides '*/ansible' # RHEL / Fedora # 5. Check under sudo sudo command -v ansible sudo -V | grep -i path
Things you should never do
- Overwrite PATH by assigning
PATH=with no existing value - Put the same PATH export in both
~/.bash_profileand~/.profile(one gets ignored) - Run
chmod 777reflexively whensudofails