2

I am currently trying to make a utility script that has an optional UI. To do it the way I have planned, I would have to launch functions within functions, up to 3 layers deep, without ever returning from those functions. This has left me wondering if it's a bad idea to do so.

Here is a simplified version of what I want to do:

ui () {
#interactive UI stuff would go here, this is just a sample output
ob="-b foo "
oa=""
or="-a bar "
main $ob$oa$or
}

main () {
while getopts ":b:a:r:u" opt ;do
    case $opt in
        b) ;; #main
        a) ;; #script
        r) ;; #stuff

        u) ui ;; #starts the UI function
    esac
done
shift $((OPTIND-1))

#other main script stuff
exit
}

main $@

As you can hopefully see, the script will start the main function with all the positional parameters, if the main function finds the -u option, it'll start the ui function, which does some UI magic to get user input. That input is then started with the main script.

Sorry if there are some mistakes in the script, but I hope it gets the point across.

Olaroll
  • 21
  • 2
  • So long as there is no infinite recursion, I don't think it'd be against best practices. You may likewise want to consider a language more well-suited for what you're doing - such as python. – rm-vanda May 10 '17 at 19:25
  • 2
    I'm more concerned about your handling of the arguments; `$@` should *always* be quoted, and `ob` et al. should be arrays, not regular parameters. – chepner May 10 '17 at 19:33
  • ...on which point, see [BashFAQ #50](http://mywiki.wooledge.org/BashFAQ/050); the "constructing a command based on information that is only known at run time" section goes into how to correctly/safely do what you're attempting here. – Charles Duffy May 10 '17 at 19:47
  • @rm-vanda, could you be a bit more specific about what aspect of what the OP is doing here is unsuited to bash? I strongly prefer Python myself in a great many cases (dealing with nested data structures, in particular), but I'm unconvinced that what's shown here touches on any of them. The OP's biggest mistake is using strings instead of lists to store argument (sub)lists -- but that's a mistake someone could make in either language, and the correct fix/replacement (using the language's native list datatype -- which bash calls an "array") is the same in both. – Charles Duffy May 10 '17 at 19:53
  • Thanks for all the answers so far! I just wanted to say that the code above is something that I threw together in a few minutes from memory. I usually do extensive double checking and testing to be sure that issues with variables are not present. As for arrays, I don't quite know how to use them yet as I only started learning bash scripting a few days ago. But thanks for pointing out that this is a good place to use them, I'll try to learn how to use them. – Olaroll May 10 '17 at 20:05
  • That main-ui-main sequence seems a bit iffy even if it's not meant to recurse any further. If you wanted to make things a bit more clear, you could move the actual meat of the program out of main, to a third function (call it `dostuff`). Then from main, either call `ui` or call `dostuff` based on the command line, and from `ui`, just call `dostuff`. – ilkkachu May 10 '17 at 21:42
  • It looks like the `ui` is designed to display options that the user can choose from, then runs `main` with the chosen options. As long as you can guarantee hat `ui` will not call `main u` then I don't see any issue with having a 3 deep function call. I do agree with some of the comments about passing a quoted array. – alvits May 10 '17 at 22:20

1 Answers1

0

Lets clear up some nomenclature; your code looks like you're calling functions from within other functions, not nesting them. Here's what calling functions looks like:

foo() {
  echo "foo"
}

bar() {
  foo
  echo "bar"
}

bar # prints foo, then bar

This is perfectly standard practice and Bash will generally have no problem with functions called by other functions, just like most other languages.

There is an upper-bound to the number of simultaneous function calls you can make, because each function is added to Bash's stack, which is finite. This is generally only an issue for buggy code that attempts to recurse (near) infinitely; normal programs will never get close to using all their stack space. As a datapoint, my system I had no trouble calling a function recursively 1,000 times.

Getting back to nesting, Bash also allows you to nest functions, which looks like so:

foo() {
  echo "foo"
  bar () {
    echo "bar"
  }
}

This syntax does something different - while foo is defined as soon as the script is executed, bar doesn't become defined until foo is invoked for the first time. After that you can call bar just like any other function, and calling foo again will re-define the bar function. For example:

$ bar
-bash: bar: command not found
$ foo
foo
$ bar
bar

From Bash's perspective nested functions are just lazily evaluated, there's nothing otherwise special about them. In theory you could nest function definitions as deep as you like, and (up to the limits of the system) they'll work just like any other function.

Community
  • 1
  • 1
dimo414
  • 47,227
  • 18
  • 148
  • 244