← Back to Bash Course | Chapter 14: Best Practices & Real-World Scripting | Lesson 3 of 7

Structuring a Script (Functions, main() Pattern, Exit at the End)

Organizing a script into clearly named functions, with one main function that ties them together, makes it much easier to read and maintain than one long list of commands.

One Long Script vs. Small Functions

Splitting logic into small, named functions -- each responsible for one clear task -- makes a script's overall structure obvious at a glance and each piece independently testable.

Example: One Long Script vs. Small Functions

bash
#!/bin/bash
greet() {
    echo "Hello, $1"
}

farewell() {
    echo "Goodbye, $1"
}

greet "Ada"
farewell "Ada"

The main() Pattern

Defining a main function that calls the other functions in the right order, and invoking main "$@" as the very last line of the script, gives the whole script one clear, obvious entry point, similar to main in C or Python's if __name__ == "__main__".

Example: The main() Pattern

bash
#!/bin/bash
setup() {
    echo "setting up..."
}

run_task() {
    echo "running task: $1"
}

cleanup() {
    echo "cleaning up..."
}

main() {
    setup
    run_task "$1"
    cleanup
}

main "demo-task"

Exiting Explicitly at the End

Ending main with an explicit exit using its own return status makes the script's final exit code intentional and easy to find, rather than relying on whatever the last command inside a deeply nested function happened to return.

Example: Exiting Explicitly at the End

bash
#!/bin/bash
main() {
    echo "doing the real work"
    return 0
}

main "$@"
exit $?

Making a Script Sourceable for Testing

Guarding the main call so it only runs when the script is executed directly (not when it's sourced) lets other scripts or a test harness load your functions without automatically triggering the whole program.

Note: This BASH_SOURCE[0] == $0 guard is the bash equivalent of Python's if __name__ == "__main__":.

Example: Making a Script Sourceable for Testing

bash
#!/bin/bash
greet() {
    echo "Hello, $1"
}

main() {
    greet "World"
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi
Common Mistakes
  1. Writing a script as one long sequence of top-level commands with no functions, making it hard to test or reuse any individual piece.
  2. Defining a main function but forgetting to actually call it at the bottom of the script.
  3. Putting logic directly at the top level interleaved with function definitions, making the script's overall flow hard to follow at a glance.
Chapter Summary
  • Breaking a script into small, purpose-named functions makes each piece easier to read, test, and reuse.
  • A main "$@" call at the very bottom, after all functions are defined, gives the script one clear entry point.
  • Keeping all function definitions together, followed by a short "orchestration" section, mirrors how most other languages structure programs.
  • This structure also makes a script easier to source into another script or an interactive shell for testing individual functions.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.