Catch "command not found" from shell script

13,045

Solution 1

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

Solution 2

  • trap is only for signals, which are defined in signal(7). Not finding a command is simply a failure in the exec family of functions, which'll return -1, not send a signal.
  • A better way to catch non-existent commands would be to do something like this.

    if ! type "$command" >/dev/null 2>&1; then
        echo "Try again, wrong command" 1>&2 # should output to stderr, not stdout
    else
        "$command"
    fi
    

Solution 3

Here's another way that uses Stéphane Chazelas's code but with type, and overcomes type's limitations...

function isCommand () {
  #
  local _arg=" $(type -t "$1") "
  local _executables=' file alias keyword function builtin '
  #
  [[ "${_executables#*$_arg}" != "$_executables" ]]  && return 0
  ### if $_arg is NOT in $_executables, the two strings will be identical
  #
  return 1
}

while
  printf "Enter a command: "
  read command
do
  isCommand "$command"  && { "$command"; break; }
  #
  echo Try again
done

Notes

  • In isCommand()...
    • ... the variables are padded to avoid partial matches.
    • ... type returns "file" for any file with the execution bit set. This is the way we detect external commands.
    • ... this test for inclusion in a string is one of the most non-intuitive I know of. But it's fast and uses no external commands so I use it and wrap a more intuitive function around it. There are a number of other ways to do this as well.
  • isCommand "$command" && { "$command"; break; }
    This uses a command list for if-then execution logic. (see bash manpage under Shell Grammer, Lists)
    • Advantages
      • ... faster execution than the normal if[[...]] construct
      • ... avoids complicated (and error-prone) multi-test logic
      • ... similar to OOP try/catch exception handling
    • Caveat
      • ... In the "then" part following the && or ||, multiple commands must be enclosed in braces and the last command and its arguments must be terminated with a semicolon ; as in { cmd1 args; cmd2 args; }.
Share:
13,045

Related videos on Youtube

Phil_Charly
Author by

Phil_Charly

Kallithea city rocks. Listening Xatzifrageta now, plz not disturb.

Updated on September 18, 2022

Comments

  • Phil_Charly
    Phil_Charly over 1 year

    I have a shell script

    echo "Type your command"
    read command
    echo "You typed $command"
    $command
    

    so it's simple it runs a command.My question is if the input is wrong suppose lw the terminal says command not found so how can I retrieve this information to my shell script and print to terminal Try again wrong command. Do I have to redirect the output of the command to a certain file and read or is there any kind of trap signal which is passed to my script.Which is your advice on how to do that it in the most efficient way.

    • 41754
      41754 over 10 years
      Depending on things you may start by something like changing $command for $command 2>&1 | grep ": command not found"
    • Olivier Dulac
      Olivier Dulac over 10 years
      @uprego : this will have a side effect of no longer displaying the normal output of 'command', and any error messages as well, as it only keeps lines containing ": commant not found" and no others.
    • 41754
      41754 over 10 years
      @OlivierDulac you are incorrectly assuming that the questioner is wanting to run a command that produces standard output or error.
    • Olivier Dulac
      Olivier Dulac over 10 years
      @uprego: ?? I think you are incorrectly assuming he doesn't want to see any output apart from 'command not found' ...
    • 41754
      41754 over 10 years
      @OlivierDulac I'm the one no making assumptions. If that solution does not work for him, he is invited to build up a more advanced solution using combinations of the type, which is often a shell builtin; and file, which is often a /usr/bin/ program.
    • Phil_Charly
      Phil_Charly over 10 years
      Chill out I didn't state whether I wanted to show the command or not the following answer works for me.@uprego I made a script with your command it prints always command not found,I am new to this so please excuse me if I make some obvious mistakes
  • slm
    slm over 10 years
    I'd also encourage you to use the type <cmd> and/or command -v <cmd> commands instead of which. unix.stackexchange.com/questions/85249/…
  • Stéphane Chazelas
    Stéphane Chazelas over 10 years
    Some shells like ksh, zsh and bash can trap on the special ERR non-signal. Not quoting your variables doesn't make sense here. Note all shell commands are executed by exec functions.
  • Stéphane Chazelas
    Stéphane Chazelas over 10 years
    Note that with bash, that would not say that -aaaa for instance is a wrong command. type -- "$command" in POSIX shells would be the correct syntax, but beware that ash, even recent versions is not POSIX (well Unix since type is optional in POSIX) in that regard. You may want to use command -v instead (or hash, see my answer).