shell-script
Skill0 starsChanged 3 months agoMITView on GitHub
Write safe, testable Bash command-line tools — one sourceable script with pure-logic/side-effect separation (external commands wrapped so tests can stub them), strict mode, colorized logging, and a shellcheck/shfmt/bats gate. Use as soon as the user wants a shell/bash script, CLI utility, automation, or "glue" code (orchestrating git, curl, file manipulation, pipelines) in shell, even for a quick one. Also triggers on adding tests to an existing shell script, making one safe/portable/testable, or hardening a fragile bash script.
- Deletes or force-pushes
- Installs packages
2 other projects use a version of this.
Adopted by
Most recently taken up by
- Jinbae10/skillstheir own version, first seen Jul 7, 2026
- kevinaimonster/skill-hubtheir own version, first seen Mar 18, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- kevinaimonster/skill-hub★ 4same name
- Jinbae10/skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository7 other files it gives its agents.
- go-scriptSkill
- kotlin-scriptSkill
- python-scriptSkill
- rust-scriptSkill
- scripteratopsSkill
- swift-scriptSkill
- typescript-scriptSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 127
No generator; use help2man from the script's --help:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Shell script
Standard for all shell command-line tools: one Bash script, written under strict
mode, whose functions are pure/testable, with external commands isolated behind
wrapper functions, verified by shellcheck + shfmt + bats. The counterpart
to the python-script, swift-script, kotlin-script, typescript-script,
go-script, and rust-script skills — but a deliberately different shape:
shell has no type system, so shellcheck is the type gate, "boundaries" are
command-wrapping functions (stubbed in tests, not interfaces), and the CLI parser
is hand-rolled.
Three decisions are already settled. Don't second-guess them, apply them:
- Target → Bash 4+ (
#!/usr/bin/env bash,set -euo pipefail). Not POSIXsh: arrays,[[ ]],local, andmapfileare worth it. macOS ships bash 3.2 —brew install bash. - Tests → bats-core, sourcing the script (which is written to be sourceable) and stubbing boundary functions.
- Lint/format → shellcheck (the real quality gate) + shfmt.
This file carries the decisions; the code is a complete, gate-passing script in
assets/template/. Copy and adapt it — don't retype it from here. Then delete
what your tool doesn't earn.
1. Layout
tool/
├── tool # the script (chmod +x) — all sections in one sourceable file
├── test/
│ ├── logic.bats # transform + log level (pure)
│ ├── color.bats # should_color
│ ├── config.bats # precedence helpers
│ └── work.bats # do_work with stubbed boundaries
├── .editorconfig # shfmt: 2-space
└── README.mdOne file, sectioned like the other skills (pure logic → color/log → boundaries →
config → orchestration → CLI). It is sourceable: the last line guards main
so bats can source the script to get the functions without executing it.
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fiset -euo pipefail lives inside main, not at top level — sourcing the file
for tests must not flip shell options in the bats harness.
2. Structure
The structure makes tests possible without a type system: pure functions are called directly; every external command sits behind a wrapper function that a test can redefine (bash's loose analog of dependency injection).
- Boundaries are functions —
run_cmd(subprocess),http_fetch(network),env_get(environment). A test redefines them:run_cmd() { printf 'a\nb\n'; }. - Pure logic (
transform) — reads a string, echoes lines; no I/O. Tested withrun transform … ; [ "${lines[0]}" = … ]. - Config precedence is pure helpers:
first_nonempty(CLI > env > file > default) andconfig_from_file(akey=valuefile parsed withoutsource— never execute a config file). - Orchestration (
do_work) assembles wrappers + pure logic: data positional, behaviour flags after, items to stdout, logs to stderr. - The CLI is thin: a
while/caseloop (no arg-parsing library exists for shell), then wire logging + config and calldo_work.
2.1 Strict mode & safety
set -euo pipefail (errexit, nounset, pipefail) is the baseline: unset variables
and failing commands abort instead of silently continuing. Quote every
expansion ("$var", "$@") — shellcheck enforces this. Default optional vars
with "${VAR-}" so nounset doesn't trip.
2.2 Subprocess boundary
run_cmd <timeout_secs> <cmd...>:
- Never a shell string. Pass the command as separate arguments — no
eval, nosh -c, so no injection. - Timeout via GNU
timeout/gtimeoutwhen present (a timeout exits 124). Note macOS has notimeoutunlessbrew install coreutils— the wrapper degrades to no hard timeout and this is the honest portability caveat.
2.3 Config and precedence
Precedence (highest wins): CLI flags > environment > config file > defaults,
resolved by first_nonempty. The config file is key=value, parsed line-by-line
(comments stripped) — never source it (that executes arbitrary code).
Secrets get no CLI flag (argv is world-readable via ps); a token comes from
the environment or the file. Shell has no JSON — use key=value, or require jq
only if real JSON is unavoidable.
2.4 HTTP requests
http_fetch <url> wraps curl -fsS --max-time N: -f makes a non-2xx status a
non-zero exit, --max-time bounds it. Tests redefine the function to fake it.
2.5 Colorized logging
Logging goes to stderr (stdout is for program data). should_color is a pure
decision (--color > NO_COLOR/FORCE_COLOR > TTY via [[ -t 2 ]]). Verbosity:
count -v/-q, map the net with log_level_for_verbosity, gate messages against
a LOG_THRESHOLD global.
2.6 Robustness & signals
- Bound external calls with a timeout where a hang is possible (§2.2, §2.4).
- Signals: bash gives the conventional exit codes for free — SIGINT → 130,
SIGTERM → 143, plus 126 (not executable) / 127 (command not found). Add a
traponly for real cleanup (e.g. removing a temp dir):trap 'rm -rf "$tmp"' EXIT. - Preflight external tools for glue scripts:
command -v jq >/dev/null || { log_error "jq required"; exit 1; }.
2.7 Subcommands (if needed)
Dispatch on the first positional in the case loop to per-subcommand functions
(cmd_build, cmd_deploy). There is no framework — keep it a small dispatch.
2.8 Man page (optional)
No generator; use help2man from the script's --help:
help2man --no-info --name "tool" "./tool" > tool.1
sudo install -m 644 tool.1 /usr/local/share/man/man1/tool.12.9 Distribution
The README carries the environment set-up, not just the gates: a
Prerequisites section with per-platform install of bash 4+ and the dev tools
(shellcheck/shfmt/bats), the external commands the script calls (curl,
timeout), and the Windows note (Git Bash / WSL — bash isn't native). There's no
dependency manifest, so no --dependencies flag — the prerequisites section is
the dependency list.
The script is the deliverable — install -m 755 tool ~/.local/bin/tool. No
compile, no runtime beyond bash. Keep the #!/usr/bin/env bash shebang so it
finds a modern bash on PATH.
3. CLI contract
Hand-rolled (no library):
-h/--helpprints usage;-V/--versionprints the version — both wired by hand.- Verbosity: count
-v/-qoccurrences; map the net to a level. (Bundled-vvis not parsed by the hand-rolled loop — repeat-v -v, or add bundling if you need it.) --color auto|always|never;--dry-runshort-circuits before output.- Stream discipline: data → stdout (
printf/echo), logs → stderr.
Exit codes
| Code | Meaning |
0 | Success (also --help/--version) |
1 | Runtime error (a failed command under set -e, or explicit) |
2 | Usage error (unknown flag, missing argument) |
124 | A subprocess exceeded its timeout (GNU timeout) |
126 / 127 | Command not executable / not found (bash) |
130 / 143 | SIGINT / SIGTERM (bash defaults) |
4. Tests with bats
Each test/*.bats sources the script in setup() and calls functions with run.
- Never test subprocess or network for real — redefine the wrapper function
(
run_cmd/http_fetch) in the test beforerun do_work …. - Pure functions (
transform,should_color,first_nonempty,log_level_for_verbosity) are tested by directrun+[ "$output" = … ]/${lines[@]}. - Sourcing is safe because
set -euo pipefailis insidemainandmainis guarded — each@testruns in a fresh subshell, soreadonly VERSIONis set once per shell.
5. Quality gates
shellcheck tool # lint / static analysis — the de-facto type gate
shfmt -d tool # formatting (2-space via .editorconfig)
bash -n tool # parse-only syntax check
bats test/ # tests- shellcheck is the highest-value gate — it catches the classic shell footguns
(unquoted expansions, unset vars, non-portable constructs). Treat every finding
as an error; suppress only with a justified
# shellcheck disable=SCxxxx. - Indentation is 2-space (
.editorconfig;shfmt -w toolfixes layout). shfmt cannot format.batsfiles (the@testsyntax isn't plain bash), so lint those with bats itself.
Summary of what to produce
Faced with a shell tool request, produce:
- One Bash script copied from
assets/template/: strict mode inside a guardedmain, pure functions, command wrappers as boundaries,key=valueconfig with precedence, stderr logging, the §3 CLI + exit codes, and the robustness rules the job earns (shelling out earns the timeout + preflight; config/secrets earn precedence + no-CLI-flag). Delete what it doesn't earn. test/*.batscovering pure functions, config precedence, and orchestration (stubbing the boundary wrappers).- In one line: how to run it (
./tool <args>), and that the gates —shellcheck+shfmt+bats— are green (needs bash 4+ and the tools).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

