Running `exec` with a Bash built-in
You defined a bash function. So you are already in a bash shell when invoking that function. So that function could then simply look like:
clock(){
echo "do something"
$@
}
That function can be invoked with bash builtins, special reserved words, commands, other defined functions:
An alias:
$ clock type ls
do something
ls is aliased to `ls --color=auto'
A bash builtin:
$ clock type type
do something
type is a shell builtin
Another function:
$ clock clock
do something
do something
An executable:
$ clock date
do something
Tue Apr 21 14:11:59 CEST 2015
The only way to launch a shell builtin or shell keyword is to launch a new shell because exec “replaces the shell with the given command”. You should replace your last line with:
IFS=' '; exec bash -c "$*"
This works with both builtins and reserved words; the principle is the same.
If the wrapper needs to insert code before the given command, an alias would work as they are expanded at a very early stage:
alias clock="do this; do that;"
Aliases are almost literally inserted in place of the aliased word, so the trailing ;
is important – it makes clock time foo
expand to do this; do that; time foo
.
You can abuse this to create magic aliases which even bypass quoting.
For inserting code after a command, you could use the "DEBUG" hook.
shopt -s extdebug
trap "<code>" DEBUG
Specifically:
shopt -s extdebug
trap 'if [[ $BASH_COMMAND == "clock "* ]]; then
eval "${BASH_COMMAND#clock }"; echo "Whee!"; false
fi' DEBUG
The hook still runs before the command, but as it returns false
it tells bash to cancel the execution (because the hook already ran it via eval).
As another example, you can use this to alias command please
to sudo command
:
trap 'case $BASH_COMMAND in *\ please) eval sudo ${BASH_COMMAND% *}; false; esac' DEBUG
The only solution I could come up with so far would be to perform a case analysis to distinguish whether the first argument is a command, built-in, or keyword, and fail in the last case:
#!/bin/bash
case $(type -t "$1") in
file)
# do stuff
exec "$@"
;;
builtin)
# do stuff
builtin "$@"
;;
keyword)
>&2 echo "error: cannot handle keywords: $1"
exit 1
;;
*)
>&2 echo "error: unknown type: $1"
exit 1
;;
esac
It does not handle time
, however, so there might be a better (and more concise) solution.