Reputation: 7035
Is it possible to save the old function (named foo
) in such a way that you can call it within a new function (also named foo
), perhaps from a different name, or such?
To make this concrete, here's specifically what I'm trying to do:
In bash, the command_not_found_handle
is a function which, if defined, is called whenever bash cannot find the command which the user is trying to run. For example, Ubuntu uses this to suggest packages which can be installed:
$ pv
The program 'pv' is currently not installed. You can install it by typing:
sudo apt-get install pv
This is really nice, and I'd like to keep this behavior. However, I'd also like to add another command_not_found
handle, which simply runs a given DEFAULT_COMMAND
with whatever command line the user typed, like so:
$ DEFAULT_COMMAND=git
$ status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working
directory)
modified: .bashrc
modified: .gitconfig
modified: .vimrc
Untracked files:
(use "git add <file>..." to include in what will be committed)
.bash_profile
.gitglobalignore
.gvimrc
node_modules/
no changes added to commit (use "git add" and/or "git commit -a")
I already have a function which does the above, and it works like a dream; however, using it overrides the default Ubuntu command_not_found
handle, which can be handy to have when I can't remember a package name for a given executable.
How do I get both? Thanks!
NB: The obvious solution is to find and copy the Ubuntu built-in command_not_found
logic into my script; but that is not ideal, because it means I have to manually update it later, when Ubuntu changes the way they do things. I'm hoping for something more generic, if possible.
Edit: It would be best not to have to resort to string manipulation, as can be done by saving the text of the function to a variable, mangling it, then eval
ing it.
Upvotes: 0
Views: 225
Reputation: 7035
Based on Barmar's suggestion, I was able to implement a workable solution. The following function can be used to rename an arbitrary bash function to another name.
renameFunction () {
local oldName="$1"; shift
local newName="$1"
local definition="$(declare -f "$oldName")"
if [[ $? -gt 0 ]]; then
echo >&2 "renameFunction: $oldName is not a function"
return
fi
if declare -f "$newName" >/dev/null 2>/dev/null; then
echo >&2 "renameFunction: $newName is already defined"
return
fi
eval "$(echo "${definition/"$oldName"/"$newName"}")"
# Does not work for recursive functions (like "//" would), but also
# doesn't break if $oldName is a substring of something else
unset "$oldName"
}
The last line
unset "$oldName"
is optional — and without it, this becomes a "copy function" utility.
The pattern substitution would work for a recursive function if it were
changed to the following (note the //
):
eval "$(echo "${definition//"$oldName"/"$newName"}")"
However, this fails if the function name is a substring of something else within the definition. Since recursion is relatively rare in shell scripts, I took the less brittle approach.
The quoting is correct, despite being too complex for the SO syntax
highlighter. (The quoting is also unnecessary, unless you like
to play with $IFS
.)
For completeness' sake, here's how I'm using this function:
# The DEFAULT_CMD is the command to run if the command line could
# not be understood. Set the DEFAULT_CMD to git, once; the user can
# change it at any time
DEFAULT_CMD=git
# Save the old command_not_found_handle for reuse
renameFunction command_not_found_handle __PREVIOUS_COMMAND_NOT_FOUND_HANDLE
command_not_found_handle () {
eval '"$DEFAULT_CMD" $DEFAULT_CMD_PREFIX_ARGS "$@" $DEFAULT_CMD_POSTFIX_ARGS'
if [ $? -gt 0 ]; then
__PREVIOUS_COMMAND_NOT_FOUND_HANDLE "$@"
fi
}
export DEFAULT_CMD
command_not_found_handle
is called by bash
whenever it cannot find
program or command that the user specified. It receives as its arguments
the entire command-line.
This function tries to execute the command-line as a "sub command" of
the given DEFAULT_CMD
. If it does not succeed, it tries the old
command_not_found_handle
Upvotes: 1