command not found
Which search failed, and where it was looking
The shell prints this when it has searched for a command and not found one:
tips-deploy
echo "status: $?"
bash: tips-deploy: command not found
status: 127
Read the status before anything else, because the neighbouring one means the opposite. 126 is a file that was found and could not be run, which is a mode or a mount and not a search at all; permission denied discusses this aspect. Everything below is 127.
Whether anything on the machine has that name
command -v tree; echo "status: $?"
command -v tips-report
status: 1
/usr/local/bin/tips-report
command -v prints the path it would run and exits 1 when there is none, so the first answer here
is that the machine has no tree and the second is that tips-report is fine. It searches the
same PATH the shell does, which is why it is the right thing to ask: checking by hand with
ls /usr/bin/tree tests one directory, but the shell may never look in that one.
which vs type vs command -v covers why it beats which here.
An empty answer splits two cases that look identical. Either nothing with that name is installed, or something is and the search cannot see it. For the first, which package provides a file finds the package to install. The rest of this page is the second.
Installed, and not in any directory on PATH
Software that arrives as a vendor tarball, a language package manager or a /opt tree does this
routinely:
ls /opt/tips/bin
printenv PATH
tips-deploy
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
The file is there and its directory is not in the list, so the shell was never going to find it.
Running it by full path works, and so does putting the directory on PATH for one command:
env PATH="/opt/tips/bin:$PATH" tips-deploy
deploying to staging
env sets that for only the command you are about to run, which is how to check
the directory is the only problem before changing anything permanent.
Adding a directory to your PATH is the permanent form, and on
Debian it often means creating ~/.local/bin rather than editing a file.
The shell remembering where it used to be
tips-report
sudo mv /usr/local/bin/tips-report /usr/local/lib/tips/tips-report
tips-report
echo "status: $?"
report written
bash: /usr/local/bin/tips-report: No such file or directory
status: 127
The message names an absolute path, and that is the tell for this one: every other case names the
word you typed. bash caches where it found a command and goes back there, so a
package upgrade or a mv between two runs leaves the cache pointing at a file no longer there:
tips-report
sudo mv /usr/local/bin/tips-report /usr/local/lib/tips/tips-report
hash -r
tips-report
echo "status: $?"
report written
bash: tips-report: command not found
status: 127
hash -r empties the cache, so the second attempt searches PATH properly and reports what is
actually true: the command has gone from every directory on it. Run hash -r whenever something
worked five minutes ago and the error provides a path rather than a name. A new terminal does the same thing, which is why the problem
has a reputation for fixing itself.
A name that exists only inside your shell
deploy() { tips-report; }
deploy
printf 'deploy\n' > run.sh
bash run.sh
echo "status: $?"
report written
run.sh: line 1: deploy: command not found
status: 127
A shell function belongs to the shell that defined it. The script gets a shell of its own, which
was never told about it. Aliases are worse off still, since a non-interactive shell does not expand
them at all, so an alias is invisible to a script even when the script is run by the shell that has
it. Note the message here begins with run.sh: instead of bash:, naming whichever script did the
looking.
The same applies to anything that is not a shell:
deploy() { tips-report; }
sudo deploy
echo "status: $?"
sudo: deploy: command not found
status: 1
sudo execs a program, but a function is not one. So does cron, and so does
a systemd unit. If a name works at your prompt, but not anywhere else, type will say why:
type deploy reports a function or an alias, neither of which is a file anything else can run.
sudo searching a PATH of its own
env PATH="/opt/tips/bin:$PATH" sudo tips-deploy
echo "status: $?"
sudo: tips-deploy: command not found
status: 1
The PATH given to that command reached sudo and went no further:
printenv PATH
sudo printenv PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Debian compiles sudo with secure_path, a fixed list it uses in place of yours, so a command in
/opt, ~/.local/bin or a virtualenv is invisible the moment sudo is in front of it. Note the
status is 1 and the message begins with sudo:, both different from the shell's own report. Give
the full path, or sudo env "PATH=$PATH" thecommand so long as you can trust every directory on
it.
For the other sudo message that reads the same way, where sudo itself is what is missing,
sudo: command not found is a Debian installer decision
rather than a search.
A builtin handed to something that is not a shell
sudo cd /tmp
echo "status: $?"
sudo: cd: command not found
sudo: "cd" is a shell built-in command, it cannot be run directly.
sudo: the -s option may be used to run a privileged shell.
sudo: the -D option may be used to run a command in a specific directory.
status: 1
cd, export, ulimit and source are part of the shell, so there is no file anywhere for
another program to exec. sudo explains itself here, while env cd /tmp gives the bare message
and no advice. What you want is a shell doing the work: sudo sh -c 'cd /srv && ...', or
the -D the message suggests.
The script found, and its interpreter not
printf '#!/usr/bin/pythn3\nprint(1)\n' > deploy
chmod +x deploy
./deploy
echo "status: $?"
bash: ./deploy: cannot execute: required file not found
status: 127
The script is there and executable. What is missing is the program named on its first line, and
127 is reported for that too. Older shells said bad interpreter here, and much of the advice
written about this still quotes that wording; what bash prints now names no interpreter at all. So
read the line yourself:
printf '#!/usr/bin/pythn3\nprint(1)\n' > deploy
head -1 deploy
#!/usr/bin/pythn3
Two letters of python3 transposed. A typo is one way to arrive here; the other is a shebang that
names a real interpreter at a path this machine does not put it in, which is common for anything
installed by a version manager or a virtualenv. #!/usr/bin/env python3 survives that second case,
because env searches PATH for the name instead of the shebang hardcoding a location. It is no
help against the typo. The env page has what that line can and cannot hold.
A file edited on Windows produces the identical message from a shebang that looks perfect, because the carriage return at the end of the line is part of the interpreter's name:
printf '#!/bin/sh\r\necho hi\r\n' > deploy
chmod +x deploy
./deploy
head -1 deploy | od -c
bash: ./deploy: cannot execute: required file not found
0000000 # ! / b i n / s h \r \n
0000013
od -c is what shows it, since the carriage return character is otherwise invisible.
sed -i 's/\r$//' deploy removes them, and dos2unix does the same if it is installed. Running the
script as bash deploy succeeds and hides the problem, because the shebang is then just a comment
and the kernel never looks at it.
PATH replaced instead of extended
PATH=/opt/tips/bin
tips-deploy
ls
echo "status: $?"
deploying to staging
bash: ls: command not found
status: 127
The one you wanted works and everything else has gone. Any assignment that leaves $PATH off the
right-hand side does this, and PATH="$HOME/bin" in a .profile takes the whole machine with it
until the next login. Write PATH="$HOME/bin:$PATH", and watch for the empty entry as well:
PATH="$PATH:" and PATH=":$PATH" each leave a colon with nothing beside it, and an empty entry
means whichever directory you are standing in. So after you cd into an unpacked archive, a file
in it called ls is first on your search path, so typing ls runs the archive's copy instead of
the system one. That is why . does not belong on PATH, spelled out or left empty.
Environment variables and PATH has where each shell
reads its PATH from, which is the next question once the value is wrong and you do not know who
set it.