0

Is there a way to get this example catchall function to be called whenever any command starts with the / character in an interactive shell (instead of searching in $PATH and executing something else, etc)?

For example, I would want

/arg1 arg2 arg3

to call

function catchall() {
    /* $1 == "arg1"
       $2 == "arg2"
       $3 == "arg3" */
    ...
}
5
  • (1) Should it work in an interactive shell? or in a script? Both? (2) Please make sure there is no XY problem here. Edit the question if there is. What actual problem are you trying to solve? Commented May 31, 2022 at 2:35
  • @KamilMaciorowski Edited to include "interactive shell". There is no XY problem here. The actual problem I am trying to solve is a function call can generalize commands by a prefix. The / is an example. The catchall function is also only an example.
    – Aaron Esau
    Commented May 31, 2022 at 2:47
  • (3) Do you want catchall to run instead of the typed command? or along? (before or after?). Commented May 31, 2022 at 2:48
  • That's a good clarification. I mean instead of the typed command.
    – Aaron Esau
    Commented May 31, 2022 at 2:53
  • I think it can be done in zsh using the precmd feature. See the answer to this question. However, I would consider it highly disturbing and error prone, if under the hood a different command is executed than the one I have entered. Commented Jun 27, 2022 at 13:01

1 Answer 1

0

A limited (imperfect) solution for Bash:

shopt -s extdebug

_generic() {
   case "$BASH_COMMAND" in
      /* )
         eval "_specific $BASH_COMMAND"
         return 1
      ;;
   esac
}

_specific() {
   printf "Got arguments: "
   printf '<%s> ' "$@"
   printf '\n'
   printf 'The command was: %s\n' "$BASH_COMMAND"
}

trap '_generic' DEBUG

Based upon:

Example usage:

$ /foo "a         b" /dev/[nf]ul* ; /bar <(cat)
Got arguments: </foo> <a         b> </dev/full> </dev/null> 
The command was: /foo "a         b" /dev/[nf]ul*
Got arguments: </bar> </dev/fd/63> 
The command was: /bar <(cat)
$ 

Notes:

  • I don't know Zsh. This answer is for Bash.

  • eval can be evil. AFAIK evaluating $BASH_COMMAND is not wrong because $BASH_COMMAND is equivalent to the string you typed and it hasn't been evaluated yet. Our eval does what the shell would normally do anyway.

  • Still $BASH_COMMAND is not the full string you typed. E.g. /foo; /bar will call _generic (and thus _specific) separately for /foo and /bar. This is how the DEBUG trap works. In the context of your question this seems fortunate.

  • _generic operates on $BASH_COMMAND with is a string. _specific works with an array of arguments from evaluation of $BASH_COMMAND, nevertheless $BASH_COMMAND is still available. In principle you should use the array (or elements of it), i.e. "$@" (or "$1", "$2", …). I can imagine $BASH_COMMAND may be used for some wizardry though.

  • You could use eval "_specific ${BASH_COMMAND#/}" instead of eval "_specific $BASH_COMMAND" to strip the leading / when calling _specific. Note if you do this and if the command is / whatever … then the first argument to _specific will be whatever, not an empty string. For this reason you may want to strip the leading / only inside _specific (e.g. argument1="${1#/}").

  • Expanding the code to cover more prefixes is simple: build any number of specific functions and add more cases for case.

  • The core of the solution is the DEBUG trap which is for debugging and as such it tries not to disturb stdin, stdout or the exit status. Our _specific cannot fully replace /foo in cases like <input /foo >output, /foo | wc -c or /foo || whatever. A solution with command_not_found_handle would be better in these matters, but it would only kick in if /foo wasn't a valid command; so it would probably work for /arg1 or /bin/nonexistent, but not for /bin/echo. (Compare this answer: How do I create a command prefix for bash with an alias?)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .