Structuring a Script (Functions, main() Pattern, Exit at the End)
In this page:
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
#!/bin/bash
greet() {
echo "Hello, $1"
}
farewell() {
echo "Goodbye, $1"
}
greet "Ada"
farewell "Ada"
Login to try C/C++/Java/PHP code in the editor
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
#!/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"
Login to try C/C++/Java/PHP code in the editor
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
#!/bin/bash
main() {
echo "doing the real work"
return 0
}
main "$@"
exit $?
Login to try C/C++/Java/PHP code in the editor
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
#!/bin/bash
greet() {
echo "Hello, $1"
}
main() {
greet "World"
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Login to try C/C++/Java/PHP code in the editor
- Writing a script as one long sequence of top-level commands with no functions, making it hard to test or reuse any individual piece.
- Defining a
mainfunction but forgetting to actually call it at the bottom of the script. - Putting logic directly at the top level interleaved with function definitions, making the script's overall flow hard to follow at a glance.
- 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.
Chapter Quiz — Complete all 7 topics to unlock
0/7 topics done
Complete these topics first:
- Quoting Pitfalls and Why to Always Quote Variables
- set -euo pipefail as a Script Header
- Structuring a Script (Functions, main() Pattern, Exit at the End)
- Idempotent Scripts (Safe to Re-Run)
- Logging in Scripts (Timestamped log Function)
- Writing Portable Scripts (Bash-isms vs POSIX sh)
- Capstone: A Real-World Backup/Cleanup Script