Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Bash & Python Automation By James Joyner IV · · 9 min read Last reviewed Jul 2026

Bash Error Guide: 'command not found' — Fix PATH, Typos, and Missing Binaries

Quick answer

Fix 'command not found' in Bash: repair PATH under cron and systemd, install missing binaries, resolve typos and empty variables, and fail loudly on exit 127.

  • #bash
  • #automation
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this Bash & Python Automation error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

command not found is the single most common failure in Bash automation. The shell tried to run a word as a command, searched every directory in $PATH, found no matching executable, and gave up. It appears interactively, in scripts, and — most painfully — inside cron and CI where $PATH is far smaller than your login shell:

./deploy.sh: line 14: kubectl: command not found

The number after line is where the shell gave up, and the word after the last colon is exactly what it could not resolve. That word is the key to every diagnosis below.

Symptoms

  • A script that runs fine in your terminal fails under cron, systemd, or CI with command not found.
  • The named command clearly “exists” — which finds it interactively — yet the script cannot.
  • A pipeline or && chain stops partway, or worse, continues as if nothing failed because the exit code was ignored.
  • A typo’d or renamed variable expands to an empty or unexpected word that the shell tries to execute.
  • The error names something that looks like an argument, not a command (a quoting or line-continuation bug).

Common Root Causes

  • Binary not installed — the tool genuinely isn’t on this host (common on fresh CI runners and minimal containers).
  • Reduced PATH in non-interactive shells — cron and systemd start with a minimal PATH (often just /usr/bin:/bin), so /usr/local/bin, ~/.local/bin, or a language version manager’s shims are missing.
  • Typo or wrong casingDocker vs docker, pyhton vs python.
  • Relative/local script not on PATH — running myscript instead of ./myscript when . is not (and should not be) on PATH.
  • A variable expands to nothing$CMD is unset (typo, or set -u not enabled), so the line becomes an empty or partial command.
  • Broken line continuation — a trailing space after a \, so the next line’s first word is parsed as a standalone command.
  • A function or alias exists only in the interactive shell — aliases and functions defined in ~/.bashrc are not available to non-interactive scripts.
  • Wrong architecture or missing loader — the file exists and is executable but the dynamic loader is missing, which can surface as not found on some systems.

Diagnostic Workflow

First, ask the shell how it resolves the name. type shows whether it’s a builtin, function, alias, or a PATH lookup:

type -a kubectl        # every match: alias, function, and each PATH hit
command -v kubectl     # the single path that would actually run, or nothing
which -a kubectl       # PATH matches only (external tool; less reliable than type)

If command -v prints nothing, the shell genuinely cannot find it. Compare your interactive PATH with the one the script actually runs under:

echo "$PATH"                       # your login shell
env -i PATH=/usr/bin:/bin ./deploy.sh   # simulate a minimal cron-like PATH

Reproduce the exact cron environment, which is the usual culprit:

# Add this temporarily to a crontab to capture the real environment:
* * * * * env > /tmp/cron.env 2>&1

Then diff /tmp/cron.env against env from your shell — you will almost always find PATH is shorter under cron.

Confirm the binary exists somewhere and is executable:

ls -l "$(command -v kubectl)" 2>/dev/null || sudo find / -name kubectl -type f 2>/dev/null

Catch the empty-variable case by turning unset variables into hard errors:

set -u          # referencing an unset variable now aborts instead of running a truncated command

Example Root Cause Analysis

A nightly backup worked by hand but failed every night under cron. The log showed:

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

Line 9 was aws s3 sync .... Interactively, command -v aws returned /home/deploy/.local/bin/aws — a pip install --user install. The fix required understanding why cron differed:

# Interactive shell:
$ echo "$PATH"
/home/deploy/.local/bin:/usr/local/bin:/usr/bin:/bin

# Captured cron environment (/tmp/cron.env):
PATH=/usr/bin:/bin

Cron never sourced ~/.profile, so ~/.local/bin was absent from PATH and aws was unreachable. Two robust fixes: set an explicit PATH at the top of the script, or call the binary by absolute path.

#!/usr/bin/env bash
set -euo pipefail
export PATH="/home/deploy/.local/bin:/usr/local/bin:/usr/bin:/bin"
aws s3 sync /data s3://backups/nightly/

The set -euo pipefail matters just as much as the PATH: without set -e, the failed aws line returned non-zero and the script blithely continued to its “backup complete” log line, hiding the failure for weeks.

Prevention Best Practices

  • Set an explicit PATH at the top of every script that runs under cron/systemd, or invoke critical tools by absolute path.
  • Always use strict modeset -euo pipefail — so a command not found (exit 127) actually aborts the script instead of being ignored.
  • Preflight your dependencies — early in the script, loop over required commands and fail fast with a clear message:
for cmd in kubectl aws jq; do
  command -v "$cmd" >/dev/null 2>&1 || { echo "Missing required command: $cmd" >&2; exit 1; }
done
  • Run ShellCheck — it flags unquoted expansions, typos, and line-continuation bugs that produce phantom commands.
  • Don’t rely on aliases/functions from .bashrc in scripts; define what you need inside the script.
  • Pin tool installs in CI — install dependencies explicitly in the pipeline rather than assuming the runner image has them.

Quick Command Reference

type -a NAME                 # how the shell resolves NAME (builtin/func/alias/path)
command -v NAME              # the exact path that would run, or nothing
echo "$PATH"                 # inspect the current search path
env -i PATH=/usr/bin:/bin CMD   # test under a minimal PATH
sudo find / -name NAME -type f 2>/dev/null   # locate the binary anywhere
set -u                       # make unset variables fatal
command -v NAME >/dev/null || exit 1   # dependency preflight

Conclusion

command not found (exit code 127) almost always means one of three things: the binary isn’t installed, it isn’t on the $PATH the script actually runs under, or a typo/empty variable produced a word that was never a command. Diagnose by asking the shell directly with type -a and command -v, and by capturing the real cron/CI environment rather than trusting your interactive shell. Prevent recurrence with an explicit PATH, strict mode so failures abort loudly, and a dependency preflight that fails fast with a message a human can act on.

Free download · 368-page PDF

Fixed it? Get 500 Bash & Python Automation & DevOps AI prompts — free

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.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

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.