Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a9dae9c
Overhaul parser robustness, strict-mode safety, CI matrix, and loggin…
kvz Mar 3, 2026
9d243a7
Fix unsupported macOS runner label and log CI finding
kvz Mar 3, 2026
d40d816
Log draft PR green CI status
kvz Mar 3, 2026
dc454e7
Add Dockerized Bash 3.2.57 test lane for local and CI
kvz Mar 3, 2026
953b63e
Scope strict mode to src function execution and standardize entry guards
kvz Mar 3, 2026
edd25a5
Add design principles doc and align README/FAQ guidance
kvz Mar 3, 2026
1e53e28
Consolidate design principles into README and add 2-week roadmap
kvz Mar 3, 2026
f21f709
Fix bash3 docker CI pipefail regression and log iteration
kvz Mar 3, 2026
12a8072
Add parser contract coverage and fast/all test commands
kvz Mar 3, 2026
556d265
Add focused robustness scenarios for parse_url ini_val and templater
kvz Mar 3, 2026
006415e
Add release-ready gate and migration/release guidance
kvz Mar 3, 2026
9bf0ae0
Add docs lint gate for brittle references
kvz Mar 3, 2026
f151158
Add explicit logging contract scenario and fast-lane coverage
kvz Mar 3, 2026
037e3a3
Add release governance contract scenario and docker git prerequisite
kvz Mar 3, 2026
20391e2
Log release-governance CI completion in overhaul timeline
kvz Mar 3, 2026
c4bcf74
Apply council refactors for parser, release-ready, and docs-lint
kvz Mar 3, 2026
ebff034
Log council refactor CI completion in overhaul timeline
kvz Mar 3, 2026
35344cf
Address council review findings with contract-first fixes
kvz Mar 3, 2026
c979a71
Log council-review CI completion in overhaul timeline
kvz Mar 3, 2026
b8ccd81
Remove governance contract scenarios and docs-lint gate
kvz Mar 3, 2026
29da373
Tighten docs, fix correctness issues, remove overhaul artifacts
kvz Mar 3, 2026
f5c55db
Dissolve Design Principles into existing sections
kvz Mar 3, 2026
9d45b31
Move contributor-facing content to CONTRIBUTING.md
kvz Mar 3, 2026
bc18ea9
Reinstate __invocation, help exit comment, and color comment
kvz Mar 3, 2026
fefa65d
Clean up main.sh readability and consistency
kvz Mar 3, 2026
c6b988d
Add shfmt formatter with CI-enforced lint gate
kvz Mar 3, 2026
330bb4c
Update CHANGELOG for 2.8.0 release
kvz Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,43 @@ on:
- synchronize
jobs:
ci:
runs-on: ubuntu-latest
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: actions/setup-node@v1
- uses: actions/setup-node@v4
with:
node-version: 18.x
- name: Install macOS test dependencies
if: runner.os == 'macOS'
run: |
brew install gnu-sed coreutils
- name: Install
run: |
corepack yarn
corepack enable
corepack yarn install --immutable
- name: Lint
if: runner.os == 'Linux'
env:
SHELLCHECK_SEVERITY: warning
run: |
corepack yarn lint
- name: Test
run: |
corepack yarn test

ci-bash3-docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Test In Docker (Bash 3.2.57)
run: |
test/bash3-docker.sh
15 changes: 12 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ Here's is a combined todo/done list. You can see what todos are planned for the

Unplanned.

- [ ] Better style guide checking (#84)

## main

Released: TBA.
[Diff](https://github.com/kvz/bash3boilerplate/compare/2.7.2...main).

- [ ]
- [x] Add shfmt formatter (`shfmt -i 2 -bn`) with CI-enforced lint gate (#84)
- [x] Fix long-option parser edge cases: unknown options, missing values, invalid `--flag=value` on booleans
- [x] Scope strict mode in `src/*.sh` libraries to function execution via subshell bodies
- [x] Refactor `parse_url.sh` to be strict-mode-safe without grep pipelines
- [x] Add Dockerized Bash 3.2.57 test lane for local and CI
- [x] Add release-ready gate (`yarn release:ready`) with branch, CI, and changelog checks
- [x] Add acceptance scenarios: long-option errors, logging contracts, parse_url/ini_val/templater robustness
- [x] Add `yarn test:fast` for quick contract-focused subset
- [x] Clean up `main.sh` readability: consolidated shellcheck disables, consistent formatting, improved comments
- [x] Trim README from 374 to ~190 lines; move contributor content to new CONTRIBUTING.md
- [x] Enrich FAQ with entrypoint-vs-library guidance and strict-mode scoping
- [x] Add Linux + macOS CI matrix with macOS dependency installation

## 2.7.2

Expand Down
58 changes: 58 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Contributing to BASH3 Boilerplate

Please fork this repository, create a branch containing your suggested changes, and submit a pull request based on the `main` branch of <https://github.com/kvz/bash3boilerplate/>.

## Testing

Run the regular test suite:

```bash
yarn test
```

Run a fast contract-focused subset:

```bash
yarn test:fast
```

Run the Bash 3.2.57 compatibility suite in Docker:

```bash
yarn test:bash3:docker
```

Run all checks used for release confidence:

```bash
yarn test:all
```

CI runs on Linux (`ubuntu-latest`), macOS (`macos-latest`, Bash 3.2), and a Docker Bash 3.2.57 lane. The native and Docker lanes are complementary and catch different classes of portability issues.

## Coding standards

These rules are CI-enforced for contributions to b3bp itself:

1. Format with [shfmt](https://github.com/mvdan/sh) (`shfmt -i 2 -bn`): two-space indent, binary operators may start a line. Run `yarn fix:shfmt` to auto-format.
1. Do not introduce trailing whitespace on lines.
1. Use a single equal sign when checking `if [[ "${NAME}" = "Kevin" ]]`.
1. Use the bash test operator (`[[ ... ]]`) rather than `[` or `test`.
1. Use braces around variable expansions: `${VAR}`.
1. Keep ShellCheck clean at `warning` severity or above.

## Releasing

Before running a release command (`yarn release:patch`, `yarn release:minor`, `yarn release:major`):

1. Ensure you are on `main` with a clean working tree.
1. Ensure CI checks for `HEAD` on `main` are green.
1. Update `CHANGELOG.md` `## main` section with completed checklist entries (`- [x]`) and no remaining open checklist items (`- [ ]`).

Automated gate:

```bash
yarn release:ready
```

The `release` script runs this gate automatically before tagging/publishing.
46 changes: 30 additions & 16 deletions FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

- [What is a CLI](#what-is-a-cli)?
- [How do I incorporate BASH3 Boilerplate into my own project](#how-do-i-incorporate-bash3-boilerplate-into-my-own-project)?
- [What is the difference between an entrypoint script and a library script](#what-is-the-difference-between-an-entrypoint-script-and-a-library-script)?
- [When should I use export -f](#when-should-i-use-export--f)?
- [How do I add a command-line flag](#how-do-i-add-a-command-line-flag)?
- [How do I access the value of a command-line argument](#how-do-i-access-the-value-of-a-command-line-argument)?
- [What is a magic variable](#what-is-a-magic-variable)?
Expand All @@ -16,7 +18,7 @@
- [How do I do Operating System detection](#how-do-i-do-operating-system-detection)?
- [How do I access a potentially unset (environment) variable](#how-do-i-access-a-potentially-unset-environment-variable)?
- [How can I detect or trap CTRL-C and other signals](#how-can-i-detect-or-trap-ctrl-c-and-other-signals)?
- [How can I get the PID of my running script](how-can-i-get-the-pid-of-my-running-script)?
- [How can I get the PID of my running script](#how-can-i-get-the-pid-of-my-running-script)?

<!--more-->

Expand All @@ -30,28 +32,41 @@ A "CLI" is a [command-line interface](https://en.wikipedia.org/wiki/Command-line

You can incorporate BASH3 Boilerplate into your project in one of two ways:

1. Copy the desired portions of [`main.sh`](http://bash3boilerplate.sh/main.sh) into your own script.
1. Download [`main.sh`](http://bash3boilerplate.sh/main.sh) and start pressing the delete-key to remove unwanted things
1. Copy the desired portions of [`main.sh`](https://bash3boilerplate.sh/main.sh) into your own script.
1. Download [`main.sh`](https://bash3boilerplate.sh/main.sh) and start pressing the delete-key to remove unwanted things

Once the `main.sh` has been tailor-made for your project, you can either append your own script in the same file, or source it in the following ways:

1. Copy [`main.sh`](http://bash3boilerplate.sh/main.sh) into the same directory as your script and then edit and embed it into your script using Bash's `source` include feature, e.g.:
1. Copy [`main.sh`](https://bash3boilerplate.sh/main.sh) into the same directory as your script and then edit and embed it into your script using Bash's `source` include feature, e.g.:

```bash
#!/usr/bin/env bash
source main.sh
```

1. Source [`main.sh`](http://bash3boilerplate.sh/main.sh) in your script or at the command line:
1. Source [`main.sh`](https://bash3boilerplate.sh/main.sh) in your script or at the command line:

```bash
#!/usr/bin/env bash
source main.sh
```

## What is the difference between an entrypoint script and a library script?

In b3bp, these are two different archetypes:

1. **Entrypoint scripts** (`main.sh` style) own CLI parsing and process lifecycle. They may enable strict options (`set -o errexit`, etc.) at top level and may call `exit`.
1. **Library scripts** (`src/*.sh`) are safe to source and should avoid top-level side effects. They should scope strict mode to function execution (using a subshell body `()`) so parent shell options are not mutated, and communicate failures through return codes rather than `exit`.

## When should I use export -f?

Use `export -f` only when child Bash processes must inherit a function.

If you only need functions in the current shell after sourcing, `export -f` is not required.

## How do I add a command-line flag?

1. Copy the line from the `main.sh` [read block](https://github.com/kvz/bash3boilerplate/blob/v2.1.0/main.sh#L109-L115) that most resembles the desired behavior and paste the line into the same block.
1. Copy the line from the `__usage` block in `main.sh` that most resembles the desired behavior and paste the line into the same block.
1. Edit the single-character (e.g., `-d`) and, if present, the multi-character (e.g., `--debug`) versions of the flag in the copied line.
1. Omit the `[arg]` text in the copied line, if the desired flag takes no arguments.
1. Omit or edit the text after `Default=` to set or not set default values, respectively.
Expand All @@ -74,16 +89,15 @@ __temp_file_name="${arg_t}"

## What is a magic variable?

The [magic variables](https://github.com/kvz/bash3boilerplate/blob/v2.1.0/main.sh#L26-L28) in `main.sh` are special in that they have a different value, depending on your environment. You can use `${__file}` to get a reference to your current script, and `${__dir}` to get a reference to the directory it lives in. This is not to be confused with the location of the calling script that might be sourcing the `${__file}`, which is accessible via `${0}`, or the current directory of the administrator running the script, accessible via `$(pwd)`.
The magic variables in `main.sh` are special in that they have a different value, depending on your environment. You can use `${__file}` to get a reference to your current script, and `${__dir}` to get a reference to the directory it lives in. This is not to be confused with the location of the calling script that might be sourcing the `${__file}`, which is accessible via `${0}`, or the current directory of the administrator running the script, accessible via `$(pwd)`.

## How do I submit an issue report?

Please visit our [Issues](https://github.com/kvz/bash3boilerplate/issues) page.

## How can I contribute to this project?

Please fork this repository. After that, create a branch containing your suggested changes and submit a pull request based on the main branch
of <https://github.com/kvz/bash3boilerplate/>. We are always more than happy to accept your contributions!
Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on testing, coding standards, and releasing.

## Why are you typing BASH in all caps?

Expand All @@ -98,10 +112,10 @@ Somewhat inconsistent – but true to Unix ancestry – the abbreviation for our
When we say _portable_, we mean across Bash versions. Bash is widespread and most systems
offer at least version 3 of it. Make sure you have that available and b3bp will work for you.

We run automated tests to make sure that it will. Here is some proof for the following platforms:
We run automated tests to make sure that it will. Continuous integration currently runs on:

- [Linux](https://travis-ci.org/kvz/bash3boilerplate/jobs/109804166#L91-L94) `GNU bash, version 4.2.25(1)-release (x86_64-pc-linux-gnu)`
- [OSX](https://travis-ci.org/kvz/bash3boilerplate/jobs/109804167#L2453-L2455) `GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)`
- Linux (`ubuntu-latest`, modern Bash)
- macOS (`macos-latest`, Bash `3.2.57`)

This portability, however, does not mean that we try to be compatible with
KornShell, Zsh, posh, yash, dash, or other shells. We allow syntax that would explode if
Expand All @@ -114,7 +128,7 @@ to create a satisfactory abstraction that is not only correct, but also covers e
while still having a relatively small footprint in `main.sh`.

For simple OS detection, we recommend using the `${OSTYPE}` variable available in Bash as
is demoed in [this stackoverflow post](http://stackoverflow.com/a/8597411/151666):
is demoed in [this Stack Overflow post](https://stackoverflow.com/a/8597411/151666):

```bash
if [[ "${OSTYPE}" = "linux-gnu" ]]; then
Expand Down Expand Up @@ -149,7 +163,7 @@ echo ${NAME2:=Damian} # echos Damian, $NAME2 is set to Damian
NAME3=${NAME3:-Damian}; echo ${NAME3} # echos Damian, $NAME3 is set to Damian
```

This subject is briefly touched on as well in the [Safety and Portability section under point 5](README.md#safety-and-portability). b3bp currently uses [method 1](https://github.com/kvz/bash3boilerplate/blob/v2.1.0/main.sh#L252) when we want to access a variable that could be undeclared, and [method 3](https://github.com/kvz/bash3boilerplate/blob/v2.1.0/main.sh#L31) when we also want to set a default to an undeclared variable, because we feel it is more readable than method 2. We feel `:=` is easily overlooked, and not very beginner friendly. Method 3 seems more explicit in that regard in our humble opinion.
This subject is briefly touched on as well in the [Safety and Portability section under point 5](README.md#safety-and-portability). b3bp currently uses method 1 when we want to access a variable that could be undeclared, and method 3 when we also want to set a default to an undeclared variable, because we feel it is more readable than method 2. We feel `:=` is easily overlooked, and not very beginner friendly. Method 3 seems more explicit in that regard in our humble opinion.

## How can I detect or trap Ctrl-C and other signals?

Expand All @@ -164,8 +178,8 @@ function ctrl_c() {
}
```

See http://mywiki.wooledge.org/SignalTrap for a list of signals, examples, and an in depth discussion.
See https://mywiki.wooledge.org/SignalTrap for a list of signals, examples, and an in depth discussion.

## How can I get the PID of my running script?

The PID of a running script is contained in the `${$}` variable. This is _not_ the pid of any subshells. With Bash 4 you can get the PID of your subshell with `${BASHPID}`. For a comprehensive list of Bash built in variables see, e.g., http://www.tldp.org/LDP/abs/html/internalvariables.html
The PID of a running script is contained in the `${$}` variable. This is _not_ the pid of any subshells. With Bash 4 you can get the PID of your subshell with `${BASHPID}`. For a comprehensive list of Bash built in variables see, e.g., https://tldp.org/LDP/abs/html/internalvariables.html
39 changes: 26 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ dependency.
- Conventions that will make sure that all your scripts follow the same, battle-tested structure
- Safe by default (break on error, pipefail, etc.)
- Configuration by environment variables
- Simple command-line argument parsing that requires no external dependencies. Definitions are parsed from help info, ensuring there will be no duplication
- Simple command-line argument parsing that requires no external dependencies. Definitions are parsed from help info, ensuring there will be no duplication. Unknown options fail with a clear error, missing values fail fast, and `--` stops option parsing.
- Helpful magic variables like `__file` and `__dir`
- Logging that supports colors and is compatible with [Syslog Severity levels](https://en.wikipedia.org/wiki/Syslog#Severity_levels), as well as the [twelve-factor](https://12factor.net/) guidelines
- Logging to STDERR, with colors and [Syslog Severity levels](https://en.wikipedia.org/wiki/Syslog#Severity_levels). Supports `NO_COLOR` and [twelve-factor](https://12factor.net/) guidelines.

## Installation

Expand Down Expand Up @@ -89,20 +89,32 @@ Please see the [FAQ.md](./FAQ.md) file.

## Best practices

As of `v1.0.3`, b3bp offers some nice re-usable libraries in `./src`. In order to make the snippets in `./src` more useful, we recommend the following guidelines.
b3bp includes re-usable libraries in `./src`. We recommend the following guidelines for your scripts.

### Function packaging

It is nice to have a Bash package that can not only be used in the terminal, but also invoked as a command line function. In order to achieve this, the exporting of your functionality _should_ follow this pattern:
It is nice to have a Bash package that can not only be used in the terminal, but also invoked as a command line function. In order to achieve this, the exporting of your functionality should follow this pattern:

```bash
if [[ "${BASH_SOURCE[0]}" = "${0}" ]]; then
my_script "${@}"
exit $?
my_script() (
set -o errexit
set -o errtrace
set -o nounset
set -o pipefail

# function body
)

if [[ "${BASH_SOURCE[0]:-}" != "${0}" ]]; then
export -f my_script
else
my_script "$@"
exit
fi
export -f my_script
```

Use `export -f` only when child Bash processes must inherit the function.

This allows a user to `source` your script or invoke it as a script.

```bash
Expand All @@ -123,11 +135,12 @@ $ my_script some more args --blah

### Coding style

1. Use two spaces for tabs, do not use tab characters.
1. Do not introduce whitespace at the end of lines or on blank lines as they obfuscate version control diffs.
1. Use long options (`logger --priority` vs `logger -p`). If you are on the CLI, abbreviations make sense for efficiency. Nevertheless, when you are writing reusable scripts, a few extra keystrokes will pay off in readability and avoid ventures into man pages in the future, either by you or your collaborators. Similarly, we prefer `set -o nounset` over `set -u`.
1. Use a single equal sign when checking `if [[ "${NAME}" = "Kevin" ]]`; double or triple signs are not needed.
1. Use the new bash builtin test operator (`[[ ... ]]`) rather than the old single square bracket test operator or explicit call to `test`.
1. Format with [shfmt](https://github.com/mvdan/sh) (`shfmt -i 2 -bn`): two-space indent, binary operators may start a line.
1. Do not introduce trailing whitespace on lines.
1. Use a single equal sign when checking `if [[ "${NAME}" = "Kevin" ]]`.
1. Use the bash test operator (`[[ ... ]]`) rather than `[` or `test`.
1. Use braces around variable expansions: `${VAR}`.
1. Run [ShellCheck](https://www.shellcheck.net/) on your scripts.

### Safety and Portability

Expand Down
Loading