Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Bash & Python Automation By James Joyner IV · · 8 min read

Bash & Python Error Guide: 'command not found' in Shell and Scripts

Resolve bash 'command not found': fix a broken PATH, missing installs, sudo stripping PATH, typos, and binaries that exist but are not on the search path.

  • #automation
  • #troubleshooting
  • #errors
  • #bash

Overview

command not found means bash searched every directory in $PATH for the name you typed and found no matching executable. The shell is not saying the program is broken — it is saying it cannot locate it. The error therefore lives at the intersection of three things: whether the binary is installed, where it lives, and whether that location is on $PATH for the current shell, user, and context.

The standard form:

deploy.sh: line 8: kubectl: command not found

Or interactively:

bash: terraform: command not found

It surfaces in subtly different contexts: a tool that works in your login shell but not in a script (different $PATH), one that works as your user but not under sudo (sudo resets PATH), one that works in an interactive terminal but not in cron (cron has a minimal PATH), and one installed into a Python/Node user directory that was never added to the path.

Symptoms

  • A specific command name fails with command not found while others work fine.
  • The command runs in your terminal but a script or cron job invoking it fails.
  • It works as your user but sudo <cmd> reports command not found.
  • which <cmd> prints nothing even though you “know” it is installed.
which kubectl
(no output, exit code 1)
echo "$PATH"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Common Root Causes

1. The program is not installed

The simplest case: the package was never installed, or installed under a different name.

command -v jq || echo "jq is not on PATH"
dpkg -l | grep -i jq
jq is not on PATH

Install it (the package name often differs from the command):

sudo apt-get install -y jq

2. The binary exists but its directory is not on $PATH

Tools installed to /usr/local/bin, /opt/<tool>/bin, ~/.local/bin, or a language-specific dir are unreachable until that directory is in $PATH.

ls -l ~/.local/bin/ | grep -i ansible
echo "$PATH" | tr ':' '\n' | grep -q "$HOME/.local/bin" && echo "on path" || echo "MISSING"
-rwxr-xr-x 1 ubuntu ubuntu 1234 Jun 23 ansible
MISSING

The binary is there but ~/.local/bin is not on $PATH. Add it:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

3. sudo strips your PATH

sudo runs with a sanitized secure_path defined in /etc/sudoers, which usually omits /usr/local/bin and ~/.local/bin. A command on your PATH vanishes under sudo.

which helm
sudo which helm
/usr/local/bin/helm
/usr/bin/which: no helm in (/usr/sbin:/usr/bin:/sbin:/bin)

Use the absolute path under sudo, or run sudo env "PATH=$PATH" helm ..., or add the dir to secure_path in sudoers.

4. Cron / systemd / CI has a minimal PATH

Non-login, non-interactive contexts do not source ~/.bashrc or ~/.profile, so the rich PATH you see interactively is gone. Cron’s default is often just /usr/bin:/bin.

# What cron actually sees
* * * * * echo "PATH=$PATH" > /tmp/cron_path.txt
cat /tmp/cron_path.txt
PATH=/usr/bin:/bin

Set PATH explicitly at the top of the crontab or use absolute paths in the job.

5. A typo, wrong case, or a shell alias/function that no longer exists

The name is simply misspelled, or you are relying on an alias defined only in an interactive shell.

type -a kubctl
bash: type: kubctl: not found

kubctl should be kubectl. type -a confirms whether the name resolves to a binary, alias, or function at all.

6. Hashed path is stale after a move/reinstall

Bash caches command locations. If you moved or reinstalled a binary, the cached path may be wrong or the new one not yet seen.

hash -r            # clear the command hash table
which terraform
/usr/local/bin/terraform

After hash -r, the freshly installed binary resolves.

Diagnostic Workflow

Step 1: Confirm what the name resolves to

type -a <cmd>
command -v <cmd>

No output means it is neither a binary on PATH, alias, nor function — installation or PATH is the issue.

Step 2: Find the binary anywhere on disk

ls -l /usr/local/bin/<cmd> /usr/bin/<cmd> ~/.local/bin/<cmd> 2>/dev/null
sudo find / -name '<cmd>' -type f 2>/dev/null | head

If find locates it, this is a PATH problem, not a missing-install problem.

Step 3: Inspect the PATH for the exact context that fails

echo "$PATH" | tr ':' '\n'
# In a script, print PATH at the failing line:
#   echo "PATH=$PATH" >&2

Compare the directory from Step 2 against this list.

Step 4: Test under the same context (sudo/cron/CI)

sudo bash -c 'echo "$PATH"; command -v <cmd>'

If it works as you but not under sudo, it is the secure_path issue.

Step 5: Fix PATH where it actually takes effect

# Interactive/login shells
echo 'export PATH="/usr/local/bin:$HOME/.local/bin:$PATH"' >> ~/.bashrc
# Cron
( crontab -l; echo 'PATH=/usr/local/bin:/usr/bin:/bin' ) | crontab -

Example Root Cause Analysis

A nightly cron job that runs aws s3 sync ... fails every night, but the same command works perfectly when an engineer runs it by hand. The cron log shows:

/home/deploy/backup.sh: line 3: aws: command not found

The AWS CLI v2 was installed to /usr/local/bin/aws, which is on the engineer’s interactive PATH (via /etc/profile). Cron, however, runs with a minimal PATH:

which aws
/usr/local/bin/aws
grep -c '/usr/local/bin' <(crontab -l 2>/dev/null) || echo "not in crontab PATH"
not in crontab PATH

Cron’s default PATH=/usr/bin:/bin does not include /usr/local/bin, so aws is invisible to the job even though it is installed.

Fix: pin a complete PATH at the top of the crontab:

( echo 'PATH=/usr/local/bin:/usr/bin:/bin'; crontab -l ) | crontab -

The backup runs successfully on the next scheduled invocation.

Prevention Best Practices

  • Use absolute paths (/usr/local/bin/aws) in cron jobs, systemd units, and CI scripts, where the interactive PATH does not apply.
  • Set PATH explicitly at the top of crontabs and in systemd Environment= directives rather than assuming the login PATH.
  • Add per-user tool directories (~/.local/bin, language package bins) to ~/.bashrc and ~/.profile so both interactive and login shells see them.
  • When a tool must run under sudo, add its directory to secure_path in /etc/sudoers or invoke it with the full path.
  • After installs or moves, run hash -r (or open a new shell) so bash does not serve a stale cached location.
  • For triaging failures in scheduled automation, the free incident assistant can flag PATH/context mismatches from job logs.

Quick Command Reference

# What does the name resolve to?
type -a <cmd>
command -v <cmd>

# Is it installed anywhere?
sudo find / -name '<cmd>' -type f 2>/dev/null | head

# Inspect PATH in the failing context
echo "$PATH" | tr ':' '\n'
sudo bash -c 'echo "$PATH"'

# Add a directory to PATH (interactive)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc

# Clear stale command cache
hash -r

# Pin PATH in cron
( echo 'PATH=/usr/local/bin:/usr/bin:/bin'; crontab -l ) | crontab -

Conclusion

command not found means bash searched every $PATH directory and found no matching executable. The recurring causes:

  1. The program is genuinely not installed (or installed under a different package name).
  2. The binary exists but its directory is not on $PATH.
  3. sudo replaces your PATH with a restricted secure_path.
  4. Cron, systemd, or CI runs with a minimal PATH that omits /usr/local/bin and user dirs.
  5. A typo, wrong case, or reliance on an interactive-only alias.
  6. A stale hashed location after a move or reinstall.

Resolve it by confirming what the name resolves to with type -a, locating the binary on disk, then aligning $PATH in the exact context that fails — interactive, sudo, and cron each have their own. Read more in the Bash & Python automation guides.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.