5

I had thought this would work:

source <(sed '1,/# HELPER FUNCTIONS #/d' "$0")
fun abc

# HELPER FUNCTIONS #
fun() {
  echo"$@"
}

But now that I think about it, that would bring in the helper function into the current shell after the script is done.

Is there a way to put my helper functions at the end of the script?

4
  • 1
    Excellent answer from @Kusalananda below, but I'd like to ask: why you want to do that? To make the script easier to read? In order not to have to scroll to the bottom to do your actual scripting?
    – markgraf
    Nov 9, 2022 at 8:47
  • @markgraf, to keep the more relevant code near the beginning and the helper functions just details.
    – Adrian
    Nov 9, 2022 at 18:14
  • suggestion: wrap all the non-helper code in function main { } and then call it at the very end of the script. Then it comes at the top of the script, but gets invoked after all the helpers are created, so it has access to them.
    – Quelklef
    Nov 9, 2022 at 18:55
  • 1
    Yeah @Quelklef, I already got that. Thx. :D
    – Adrian
    Nov 10, 2022 at 1:17

1 Answer 1

13

Note that your approach actually works, but that the statements declaring the helper functions would be executed twice, once by source and once after running fun abc. I would therefore insert a plain exit statement before the divider.

#!/bin/bash

source <(sed '1,/^# HELPER FUNCTIONS #$/d' "$0")
fun ABC

exit
# HELPER FUNCTIONS #

fun () {
  echo "$@"
}

Also, note that you need to anchor the regular expression, at least to the start of the line, or it will match the line containing the source command, which in turn would cause the top half of the script (sans sed) to be executed by source, which we want to avoid.

Personally, I think this makes the code a bit difficult to read. It also changes some things after the divider. For example, the BASH_SOURCE array would have an extra filename as its first element, BASH_LINENO would be offset by the size of the initial part of the script, and any line numbers (and the script name) reported in error messages from that lower part of the script would be difficult to follow up.


Another way to do this, which does not require $0 to have a sensible value, nor calls sed to modify the script on the fly, is to get into the habit of putting the main body of the script into its own function, possibly called main, and then call that function at the very end of the script:

#!/bin/sh

main () {
    fun ABC
}

fun () {
    echo "$@"
}

# Call "main" with the arguments given to the script
# (even if the function may not use them).
main "$@"

This means that the script would be read to the end by the shell, all functions would be instantiated, and main could call any function that it may need to do its work, regardless of where in the code those functions are located.

See also the Google style guide for shell scripts, which specifically suggests this, although it says to put the code for the main function last too, which is not strictly necessary. In fact, it may be worth putting it first to make it easy to find.

Related:

1
  • Oops. Forgot about the anchor. Must have been sleep-coding. ;) :D
    – Adrian
    Nov 9, 2022 at 18:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .