bash3boilerplate wants you to delete most of the template
Templates to write better Bash scripts
At a glance
- What is it?
- The proposition is unfashionable in a useful way: take a working Bash script, keep the strict mode, the argument parser and the logging, delete everything else, and stay on the shell version your users already have.
- Who is it for?
- The thing bash3boilerplate does well is refusing to be a framework. There is no plugin system, no template engine, and nothing to keep in sync, which is the failure mode that kills most in-house script libraries.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 79 days ago.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Deleting is the documented workflow
The Overview section describes the problem plainly: when hacking up Bash scripts there are things such as logging or command-line argument parsing that you need every time, that come with a number of pitfalls you want to avoid, and that keep you from your actual work. The template exists to bundle those so they are reusable as-is.
What makes the approach unusual is the goal called Delete-Key-Friendly. Instead of introducing packages, includes or compilers, the project proposes using `main.sh` as a base and removing the parts you do not need. The README is upfront that this may feel archaic and then argues that it is exactly the strength of Bash scripts that we should want to embrace.
That framing has a practical consequence. There is no package to update and no runtime to depend on, so there is nothing to break when the template changes, and nothing that stops working when your base image ages. The cost is that every improvement to the template reaches you by hand, which for a team means a periodic diff-and-merge chore rather than a dependency bump.
The second goal is portability, and it is stated with a real argument behind it: the project targets Bash 3 because OSX still ships with 3. If you are going to ask people to install Bash 4 first, you might as well pick a more advanced language as a dependency. That is a coherent position for a template meant to be copied into other people's repositories.
Argument parsing with no dependencies
The Features list is short, and the argument parser is the item that does the most work. Simple command-line argument parsing that requires no external dependencies, where definitions are parsed from help info, ensuring there will be no duplication.
Parsing the definitions out of the help text is the interesting part. A conventional parser has options defined in one place and a usage string in another, which means the help text drifts. Deriving one from the other removes that failure mode, and it is a technique that only works in a single file where the author controls both.
The guarantees listed alongside it are specific: unknown options fail with a clear error, missing values fail fast, and `--` stops option parsing. That last one is what makes a script composable, since it lets you pass arguments that look like options through to another command without the parser eating them.
The package.json shows what this costs in test coverage. The fast test list runs seven separate acceptance scripts by name, covering long option errors, logging contracts, and both strict and robust parsing of URLs and ini values, plus template robustness. That is a much better signal about the project's quality than the star count.
Three installation paths and what each one costs
The Installation section offers three options, and the reasoning behind each is more interesting than the commands.
Option 1 is the raw template download, which is the one the project wants you to use:
wget https://bash3boilerplate.sh/main.sh
vim main.shOption 2 clones the whole repository, which the README points out also gets you the extra functions kept in the `./src` directory. Option 3 installs it as a Node module:
npm init
npm install --save --save-exact bash3boilerplateThe README's own hedge on that third option is worth repeating in its spirit: it introduces a Node.js dependency, but it does allow easy version pinning and distribution in environments that already have this prerequisite, and nothing prevents you from ignoring the possibility entirely. That is an accurate description of the trade. Pinning with `--save-exact` is the whole argument for taking a Node dependency in a Bash project.
There is a wrinkle here. The README describes the npm option as of v1.0.3, while the package version in package.json is 2.8.0. Nobody has gone back to update that sentence, which is a small piece of evidence about how much attention the documentation gets relative to the code. The repository also publishes no GitHub release objects, so `CHANGELOG.md` is the only version history there is to read.
Conventions that cover the parts people get wrong
The Best practices section is longer than the Features section and is the part most worth copying, whether or not you use the template. The style rules are specific: format with shfmt using `shfmt -i 2 -bn` for two-space indent with binary operators allowed to start a line, no trailing whitespace, single `=` in `[[ "${NAME}" = "Kevin" ]]` tests, the bash test operator rather than `[` or `test`, braces around every variable expansion, and ShellCheck on every script.
The safety rules are the ones that prevent real outages. Use `{}` to enclose variables, because otherwise Bash will try to access the `$ENVIRONMENT_app` variable in `/srv/$ENVIRONMENT_app` when you meant `/srv/${ENVIRONMENT}_app`. Use `set` rather than relying on a shebang like `#!/usr/bin/env bash -e`, since that shebang is neutralized the moment someone runs your script as `bash yourscript.sh`.
The variable naming scheme is a three-tier convention worth stealing on its own. Use `local` before every variable declaration in functions. Use `UPPERCASE_VARS` for environment variables that can be controlled from outside the script. Use `__double_underscore_prefixed_vars` for globals controlled only inside the script, excepting arguments already prefixed `arg_` and functions, over which the template imposes no restrictions.
The default-substitution syntax is also explained, which is more than most templates do: `:-` tests a variable that may be undeclared so `${NAME:-}` evaluates safely when empty, while the variable stays unchanged, and `${NAME:=Kevin}` assigns a default. Knowing the difference between those two is worth the paragraph on its own.
Functions you can source and functions you can run
The function packaging pattern is borrowed from the bpkg project, and the README credits it as such. The pattern wraps the body in a function definition using parentheses rather than braces, applies the strict-mode settings inside it, and then dispatches based on whether the script was executed or sourced:
if [[ "${BASH_SOURCE[0]:-}" != "${0}" ]]; then
export -f my_script
else
my_script "$@"
exit
fiUsing parentheses for the function body is the detail that makes it work. It runs the body in a subshell, so `exit` inside it terminates only that subshell, and the sourcing path never falls through to the dispatch branch. `export -f` is added on the sourcing path so child Bash processes can inherit the function, with the README noting that it is only needed when a child Bash process must inherit it.
The two supported invocations follow directly. Run it as a script with `$ ./my_script.sh some args --blah`, or source it and call it as a shell function. A library that only works one way forces you to fork it.
The tree shows what else is here: `src/` with the reusable functions, `example.sh`, a `test/` directory, `docs/`, `repodocs/`, `FAQ.md`, `CHANGELOG.md`, a `.shellcheckrc`, and a Makefile. The package.json dev scripts show the project running shellcheck over every `.sh` file outside node_modules, calling shfmt through `test/shfmt.sh`, and running a Perl style checker named `test/style.pl`. It also targets Bash 3 explicitly through a Docker test script, which is the only way to keep a portability promise honest.
Editorial conclusion
The thing bash3boilerplate does well is refusing to be a framework. There is no plugin system, no template engine, and nothing to keep in sync, which is the failure mode that kills most in-house script libraries. Its one real constraint, Bash 3, is a constraint most projects skip voluntarily and a few still have to live with, because macOS ships Bash 3 and that is not going to change. Two details to check before you adopt it: the npm package is at version 2.8.0 while the README still describes the module install as of v1.0.3, and the repository publishes no GitHub release objects, so the changelog in `CHANGELOG.md` is the only version history available. Download `main.sh` once, read it end to end, and keep the parts you can explain.
Frequently asked questions
What does bash3boilerplate give me that I do not get from writing a shebang line?
Strict mode set for you, argument parsing with no external dependencies where definitions are read from the help text, logging to STDERR with colors and syslog severity levels respecting NO_COLOR, and magic variables such as __file and __dir. The template's own pitch is that you delete the parts you do not need and keep the rest.
Why target Bash 3 instead of a newer version?
Because macOS still ships with Bash 3, so requiring version 4 means asking users to install a dependency before they can run your script. The project's stated view is that if you need Bash 4 anyway, you might as well pick a more advanced language as the dependency.
How do I install it, and does the npm package pull in Node?
Three ways. Download main.sh with wget, clone the repository for the extra functions in ./src, or run npm install --save --save-exact bash3boilerplate. Only the third introduces a Node.js dependency, and the README says so explicitly, framing the benefit as version pinning for environments that already have Node.
Can a single file be both a runnable script and a sourceable library?
Yes, using the pattern in the README's best practices section. Define the body as a function with parentheses so exit only ends the subshell, then compare ${BASH_SOURCE[0]:-} against ${0} and either export the function or call it with the script's arguments. The pattern is credited to the bpkg project.
Which shell features should I avoid if I use this template?
Avoid relying on a shebang for strict mode, since running the file as bash yourscript.sh neutralizes it. Enclose variable expansions in braces, prefer the [[ ]] test operator over [ or test, use ${NAME:-} when testing a variable that may be undeclared rather than ${NAME:=default}, which also assigns it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/kvz-bash3boilerplate)