Skip to content

clean-up: move all shell script code to a function and use a "main" #740

Description

@marc-hb

This a very generic, clean-up task. Its main purpose is to document this good practice: almost all the code in a shell script should be in a function. In other words, the smallest script should look like this:

main()
{
  do stuff
}

# The braces and bottom position let you safely edit the script while it's running
# https://hachyderm.io/@simontatham/114511220670677410
# (XKCD 303)
{
 main "$@"; exit "$?"
}

Rationales:

  • More variables can be local. This reduces the risk of accidentally overwriting some user environment variables (side effects)
  • Functions can be defined in any order, they don't need to be defined before being used
  • It provides more line numbers in the FUNCNAME stack on failure
  • moving code around functions does not change the indentation level which helps with git diff, rebase, cherry-pick, merge...
  • it's very easy to comment out the last, main "$@" line and source the entire file without running it to test individual functions interactively (almost like the usual Python's __main__ trick)
  • Similarly, it's very easy to copy/paste functions into an interactive shell and test them.
  • It's easy to temporarily turn off an entire function call for local testing purposes by commenting out a single line.
  • For the same reason, better testability, example: https://opensource.com/article/19/2/testing-bash-bats if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then run_main; fi
  • https://en.wikipedia.org/wiki/Margin_(typography)#The_Digital_Page "Although margin-less web pages do still exist, today it is generally understood that having wide enough margins to provide adequate white space around text is important to the usability and readability of digital text" (unverified)

And last but not least, stating the obvious:

  • A sequence of calls to well named functions is much more readable than a giant and anonymous concatenation of all these functions.

cc:

Activity

  1. added a commit that references this issue on Jul 26, 2021
  2. changed the title [-]clean-up: move all shell scripts code to a function[/-] [+]clean-up: move all shell script code to a function[/+] on Jan 11, 2022
  3. changed the title [-]clean-up: move all shell script code to a function[/-] [+]clean-up: move all shell script code to a function and use a "main"[/+] on Apr 24, 2024
  4. marc-hb commented on May 15, 2025

    @marc-hb
    CollaboratorAuthor

    Simon Tatham agrees with one more point
    https://hachyderm.io/@simontatham/114511220670677410

    With a normal shell script, it's dangerous to edit the script file while an instance of the shell is still running it, because the shell will read from the modified version of the file starting at the file position it had got to in the old one, perhaps reading a partial command or the wrong command and doing something you didn't want.
    But with a script in this style, the shell finishes reading the script before it does anything, so it's safe to edit.

    {
       main "$@"; exit $?
    }

    The last, compound statement { } forces bash to read it entirely before running it.

  5. marc-hb commented on May 15, 2025

    @marc-hb
    CollaboratorAuthor

    https://google.github.io/styleguide/shellguide.html#function-location

    Don’t hide executable code between functions. Doing so makes the code difficult to follow and results in nasty surprises when debugging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low-impact bugs or featuresgood first issueGood for newcomerstype:discussionOpen ended discussion topictype:enhancementNew framework feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions