2

Is there a way to set up bash so that it executes any file with a .sh extension, say for example mycommand.sh, just by typing mycommand, in the same way that in Windows you can execute mycommand.bat just by typing mycommand?

I already used chmod +x and put the directory containing mycommand.sh in the PATH, so no need to state those things.

An obvious way would be to rename mycommand.sh to mycommand. I don't want to do this. Some third-party products (e.g. Kafka, and some database systems) provide shell scripts which all have a .sh extension and I don't want to be renaming things I don't own.

I'm specifically asking if it's possible to make .sh some kind of "known" executable extension, similar to Windows' PATHEXT environment variable.

2 Answers2

4

Bash provides you a way to define your own "command not found" handler:

If the name is neither a shell function nor a builtin, and contains no slashes, Bash searches each element of $PATH for a directory containing an executable file by that name. […] If the search is unsuccessful, the shell searches for a defined shell function named command_not_found_handle. If that function exists, it is invoked in a separate execution environment with the original command and the original command’s arguments as its arguments, and the function’s exit status becomes the exit status of that subshell. If that function is not defined, the shell prints an error message and returns an exit status of 127.

(source)

This is a basic handler that solves your problem:

command_not_found_handle () {
   unset -f command_not_found_handle
   cmmnd="$1".sh
   shift
   exec "$cmmnd" "$@"
}

With this function defined, if you try to run mycommand and the command is not found, the shell will try mycommand.sh with all the command line arguments mycommand was supposed to get.

Notes:

  • Other shells may provide similar functionality.
  • unset prevents the handler from triggering itself again in case the new command is not found either.
  • You may have some handler already defined. E.g. in Debian the command-not-found package relies on its handler. You will lose it if you simply define your own. To keep the old functionality you need to mimic (or reinstate) the old handler and run its code after the new command is not found (but make sure it effectively works as if it got the original command, i.e. mycommand, not mycommand.sh). The old handler may look like in this another answer of mine.
0

This is an old question but it helped me when I had this problem and I've since made improvements to the chosen answer which I think are worth sharing. My implementation works on all executable types, not just .sh, and it doesn't botch the error message when a command isn't found:

command_not_found_handle() {
    unset -f command_not_found_handle
    found=false
# Iterate over possible autocompletions.
while IFS='' read -r cmmnd; do
    # If the only thing that was autocompleted was the extension, execute it,
    # but outside the loop because STDIN is redirected inside here.
    if [[ "$cmmnd" == "$1".* ]]; then
        found=true
        break
    fi
done < <(compgen -c -- "$1") # Generate possible autocompletions for the command.

$found && exec -- "$cmmnd" "${@:2}"
echo "bash: '$1': command not found" >&2
return 127

}

Verpous
  • 131