8

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.

  • Depending on things you may start by something like changing $command for $command 2>&1 | grep ": command not found" – 41754 Dec 10 '13 at 17:14
  • @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. – Olivier Dulac Dec 10 '13 at 17:24
  • @OlivierDulac you are incorrectly assuming that the questioner is wanting to run a command that produces standard output or error. – 41754 Dec 10 '13 at 17:30
  • 1
    @uprego: ?? I think you are incorrectly assuming he doesn't want to see any output apart from 'command not found' ... – Olivier Dulac Dec 10 '13 at 17:33
  • @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. – 41754 Dec 10 '13 at 17:35
  • 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 – Phil_Charly Dec 10 '13 at 17:45

3 Answers3

10

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
4
  • 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
    
pilona
  • 418
  • 1
    I'd also encourage you to use the type <cmd> and/or command -v <cmd> commands instead of which. http://unix.stackexchange.com/questions/85249/why-not-use-which-what-to-use-then – slm Dec 10 '13 at 20:32
  • 1
    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 Dec 10 '13 at 21:19
  • 1
    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). – Stéphane Chazelas Dec 11 '13 at 15:59
2

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; }.
DocSalvager
  • 2,152