29

What I want trying to achieve is to create a local function within a function. At the same time, the local function will not overwrite the outer function. Below is an example of a simple function and a nested function with argument to illustrate my problem.

#!/bin/bash
usage() #<------------------------------- same function name
{
    echo "Overall Usage"
}

function_A()
{
    usage() #<--------------------------- same function name
    {
        echo "function_A Usage"
    }

    for i in "$@"; do
        case $i in
            --help)
                usage
                shift
                ;;
            *)
                echo "flag provided but not defined: ${i%%=*}"
                echo "See '$0 --help'."
                exit 0
            ;;
        esac
    done
}

function_A --help
usage

Here is output.

function_A Usage
function_A Usage

But what I want is

function_A Usage
Overall Usage

Is is possible to achieve without change their (functions) name and order? Please?

Note: I tried the local usage() but it seem not applicable to function.

pfnuesel
  • 14,093
  • 14
  • 58
  • 71
karfai
  • 816
  • 1
  • 10
  • 21

3 Answers3

35

Bash does not support local functions, but depending on your specific script and architecture you can control the scope of your function name through subshells.

By replacing the {..} with (..) in your definition, you'll get the output you want. The new definition of usage will be limited to the function, but so will e.g. any changes to variables:

#!/bin/bash
usage() 
{
    echo "Overall Usage"
}

function_A()
(                  # <-- Use subshell
    usage()
    {
        echo "function_A Usage"
    }

    for i in "$@"; do
        case $i in
            --help)
                usage
                shift
                ;;
            *)
                echo "flag provided but not defined: ${i%%=*}"
                echo "See '$0 --help'."
                exit 0
            ;;
        esac
    done
)

function_A --help
usage
that other guy
  • 116,971
  • 11
  • 170
  • 194
  • 7
    Could you please improve this answer by adding some link explaining what is `(..)` (e.g. man subshell) and when we must use it? Thanks. – 4wk_ May 08 '18 at 13:14
  • 3
    @4wk_ The body of a function can be any [compound command](https://www.gnu.org/software/bash/manual/html_node/Compound-Commands.html). This is most often a curly braces `{...}` [grouping command](https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html), but can also be a `(...)` grouping command as well. The difference between `{...}` and `(...)` is that `(...)` runs the commands in a *subshell*. [This stackoverflow answer](https://stackoverflow.com/a/27802082/2668666) explains this syntax further. – jmrah Feb 04 '21 at 13:17
  • 1
    @jrahhali Thank you for this explanation and the provided links ;) – 4wk_ Feb 04 '21 at 14:11
5

From man bash:

Compound Commands

A compound command is one of the following:

(list) list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes. The return status is the exit status of list. ...

  1. Suppose we have test.sh:
#!/usr/bin/sh

topFunction1() {
    # start subshell
    (


        innerFunction1() {
            echo "innerFunction1"
        }

        echo "topFunction1 can call $(innerFunction1) from within the subshell"


    )
    # end subshell


    innerFunction2() {
        echo "innerFunction2"
    }
}

topFunction2() {
    echo "topFunction2"
}
  1. Now let's do source test.sh.

The following command is successful:

topFunction2

The following commands fail:

innerFunction1

innerFunction2

  1. Now if we do topFunction1 we'll get an output containing innerFunction1 output:

    topFunction1 can call innerFunction1 from within the subshell

At this point the following commands are successful:

topFunction1

topFunction2

innerFunction2

One can notice that now innerFunction2 it's visible globally after the call to topFunction1. However innerFunction1 it's still 'hidden' for calls out of the subshell and this is what you probably want.

Another call to innerFunction1 will fail.

Nicolae Iotu
  • 399
  • 3
  • 7
3

To expand upon that other guy's answer:

$ function zz() (:;)

$ declare -f zz

zz ()
{
    ( : )
}

You can see that defining a function with parenthesis instead of braces is just shorthand for encasing the whole interior of a function with parenthesis. The function actually identifies in its long form.

https://www.tldp.org/LDP/abs/html/subshells.html https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html#Command-Grouping

Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
  • 4
    I may have been compelled to give this answer just so that I could make a reference to [that other guy](https://stackoverflow.com/a/34985567/117471). I may have made this comment for the same reason. – Bruno Bronosky Jul 31 '19 at 03:15
  • The semicolon is not needed in the parenthesis form. In fact it could be defined `zz()(:)`. – ingydotnet Jan 28 '21 at 14:34
  • @ingydotnet not only "could be defined", but you will see by the output of the `declare -f`, that is how the optimizer resolves it. ;-) – Bruno Bronosky Feb 05 '21 at 16:38