When a command is not found, the exit status is 127. You could use that to determine that the command was not found:
until
printf "Enter a command: "
read command
"$command"
[ "$?" -ne 127 ]
do
echo Try again
done
While commands generally don't return a 127 exit status (for the very case that it would conflict with that standard special value used by shells), there are some cases where a command may genuinely return a 127 exit status: a script whose last command cannot be found.
bash
and zsh
have a special command_not_found_handler
function (there's a typo in bash
's as it's called command_not_found_handle
there), which when defined is executed when a command is not found. But it is executed in a subshell context, and it may also be executed upon commands not found while executing a function.
You could be tempted to check for the command existence beforehand using type
or command -v
, but beware that:
"$commands"
is parsed as a simple commands and aliases are not expanded, while type
or command
would return true for aliases and shell keywords as well.
For instance, with command=for
, type -- "$command"
would return true, but "$command"
would (most-probably) return a command not found error.
which
may fail for plenty of other reasons.
Ideally, you'd like something that returns true if the command exists as either a function, a shell builtin or an external command.
hash
would meet those criteria at least for ash
and bash
(not yash
nor ksh
nor zsh
). So, this would work in bash
or ash
:
while
printf "Enter a command: "
read command
do
if hash -- "$command" 2> /dev/null; then
"$command"
break
fi
echo Try again
done
One problem with that is that hash
returns true also for a directory (for a path to a directory including a /
). While if you try to execute it, while it won't return a command not found error, it will return a Is a directory
or Permission Denied
error. If you want to cover for it, you could do:
while
printf "Enter a command: "
read command
do
if hash -- "$command" 2> /dev/null &&
{ [ "${command#*/}" != "$command" ] || [ ! -d "$command" ];}
then
"$command"
break
fi
echo Try again
done
$command
for$command 2>&1 | grep ": command not found"
type
, which is often a shell builtin; andfile
, which is often a /usr/bin/ program.