110

Is there such a thing in bash or at least something similar (work-around) like forward declarations, well known in C / C++, for instance?

Or there is so such thing because for example it is always executed in one pass (line after line)?

If there are no forward declarations, what should I do to make my script easier to read. It is rather long and these function definitions at the beginning, mixed with global variables, make my script look ugly and hard to read / understand)? I am asking to learn some well-known / best practices for such cases.


For example:

# something like forward declaration
function func

# execution of the function
func

# definition of func
function func
{
    echo 123
}
Vlastimil Burián
  • 3,024
  • 2
  • 31
  • 52
Kiril Kirov
  • 37,467
  • 22
  • 115
  • 187

3 Answers3

215

Great question. I use a pattern like this for most of my scripts:

#!/bin/bash

main() {
    foo
    bar
    baz
}

foo() {
}

bar() {
}

baz() {
}

main "$@"

You can read the code from top to bottom, but it doesn't actually start executing until the last line. By passing "$@" to main() you can access the command-line arguments $1, $2, et al just as you normally would.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 3
    Hi, how do you structure data that has to be shared between foo/bar/baz in your example? Usually I just put it at the top of the script. Is this still the case when using functions? Or is it better to put global data in main, and then pass it to foo/bar/baz as arguments? What's the best practice? – bodacydo Dec 01 '15 at 01:50
  • 4
    I prefer arguments. Barring that, I'll set global variables in `main` or in a function right after `main` (e.g., `setup` or `parseArguments`). I avoid having global variables set above `main` -- code should not go outside of `main`. – John Kugelman Dec 01 '15 at 02:07
  • This seems somewhat analogous to what `if _ _ name _ _ == "_ _ main _ _": main()` does in python – Sergiy Kolodyazhnyy Feb 13 '16 at 07:13
  • This is also fantastic when using tools like [Bats](https://github.com/bats-core/bats-core) to test your scripts, breaking everything down into functions makes testing the individual components much easier. See also [blog post](https://medium.com/@pimterry/testing-your-shell-scripts-with-bats-abfca9bdc5b9) – dragon788 Aug 09 '18 at 19:53
33

When my bash scripts grow too much, I use an include mechanism:

File allMyFunctions:

foo() {
}

bar() {
}

baz() {
}

File main:

#!/bin/bash

. allMyfunctions

foo
bar
baz
mouviciel
  • 66,855
  • 13
  • 106
  • 140
0

You can have the script source portions of itself:

#!/bin/bash
. <( sed -n '/^#SOURCE-BEGIN/,/^#SOURCE-END/{//!p;}' $0 )
greeting "$@"
foo hey

#SOURCE-BEGIN
greeting() {
  for i in "$@"
  do
    echo ">[$i]"
  done
}

foo() {
  echo in foo
  echo "arg passed in: $1"
}
#SOURCE-END

echo good bye
$ ./myscript.sh hello world "one string"
>[hello]
>[world]
>[one string]
in foo
arg passed in: hey
good bye

I used process substitution (<(....)) to source the output of the sed command. The sed syntax comes from here, search for What about the classic sed solution?

Aaron Meese
  • 1,670
  • 3
  • 22
  • 32
Andrew
  • 147
  • 1
  • 2
  • 8