jpaugh
jpaugh

Reputation: 7035

Save the old value of a function bash, so that it can be called later

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 evaling it.

Upvotes: 0

Views: 225

Answers (1)

jpaugh
jpaugh

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"
}

Notes

  • 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

Related Questions