Install
$ agentstack add skill-glapsfun-cnative-skills-bash-scripting Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Destructive filesystem operation.
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Bash Scripting
Use this skill to write, harden, debug, and review Bash and POSIX shell scripts to a production standard: scripts that fail loudly instead of silently, survive filenames with spaces and special characters, parse arguments predictably, clean up after themselves, and behave the same on a teammate's machine as on yours.
Shell behavior is environment-sensitive. The same script can pass on GNU/Linux and break on macOS (BSD userland), or work in Bash 5 and fail in Bash 3.2 (still the default /bin/bash on macOS), or break the moment it runs under /bin/sh (dash) instead of bash. So before giving version- or platform-specific advice, establish what shell, version, and OS the script actually targets — don't assume the user's machine matches yours.
First step
When writing or reviewing anything beyond a one-liner, check the toolchain and target so your advice matches reality:
bash scripts/bash-version-check.sh
This reports the bash version, OS/userland (GNU vs BSD), and whether ShellCheck, shfmt, and Bats are installed — which determines what you can actually run versus only recommend. If the script is destined for a different target (a container, a CI runner, /bin/sh), say which assumptions your answer uses and recommend verifying there.
Decide the target shell first
The single most important decision is which interpreter the script runs under, because it determines what syntax is legal:
#!/usr/bin/env bash— the default for new scripts. Lets you use arrays,[[ ]],local, process substitution, and parameter-expansion case operators.envfinds bash onPATH(important on macOS, where a modern bash is usually under Homebrew, not/bin).#!/bin/sh— only when POSIX portability is a hard requirement (Alpine images, BSD base systems, init scripts). Many bash features are unavailable here; seereferences/05-portability-posix.md.
If the user hasn't said, ask or infer from context (a Dockerfile FROM alpine strongly implies sh; a developer laptop script implies bash). Don't write bash-only syntax into a #!/bin/sh script — that's the most common portability bug.
Use strict mode for standalone execution
Bash's defaults are forgiving to a fault: it ignores unset variables, marches past failed commands, and hides failures in the middle of a pipeline. Strict mode turns those silent failures into loud ones, which is what you want in automation:
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
set -Eeuo pipefail
initialize_runtime
install_traps
main "$@"
fi
-e(errexit) — exit on any unhandled non-zero command, instead of continuing in a broken state.-u(nounset) — treat use of an unset variable as an error, catching typos and missing arguments.-o pipefail— a pipeline fails if any stage fails, not just the last; without itfalse | true"succeeds".-E(errtrace) — makes an installedERRtrap inherit into functions, command substitutions, and subshell environments. It does not affectEXITcleanup traps.
Standalone executables should enable set -Eeuo pipefail inside the direct-execution guard, immediately before their runtime initialization and main path. A file that can be sourced must only define functions at import time: do not change the caller's shell options, IFS, traps, or runtime globals. Expose explicit initialization and trap-installation functions instead.
set -e has real sharp edges: it does not fire in condition contexts such as if or the tested parts of &&/||, and modern non-POSIX Bash clears errexit inside command substitutions unless inherit_errexit is enabled. It's a safety net, not a substitute for explicit error handling. See references/01-strict-mode-and-structure.md for the gotchas and the script skeleton.
Don't hand-write the skeleton from scratch — generate it:
bash scripts/bash-scaffold.sh --name deploy --description "Deploy the app" > deploy.sh
This emits a ready-to-edit script with direct-execution-only strict mode and initialization, a usage()/--help, a manual while/case long-option parser, leveled logging to stderr, a trap-based cleanup handler, and a main "$@" guard — the same structure this skill recommends, so you start correct instead of refactoring toward correct.
Task routing
Read the reference file that matches the task. Each is a focused deep-dive so you load only what you need:
- Strict mode details, script structure, functions, logging,
mainguard, exit codes →references/01-strict-mode-and-structure.md - Defensive patterns: input validation,
trap/cleanup,mktemp, dry-run, idempotency, retries, NUL-safe file handling, avoidingrm -rffootguns →references/02-defensive-patterns.md - Quoting, word-splitting, globbing, parameter expansion, arrays,
[[ ]]tests, command substitution, here-docs →references/03-quoting-expansion-arrays.md - Debugging (
set -x,trap DEBUG,BASH_XTRACEFD), ShellCheck, shfmt, and Bats testing →references/04-debugging-and-testing.md - POSIX
shportability and GNU-vs-BSD (macOS) tool differences →references/05-portability-posix.md - Authoritative external docs (Bash manual, ShellCheck wiki, Bats) → run
bash scripts/bash-doc-discover.sh
Always lint before declaring a script done
A script that "runs on my machine" is not finished. The single highest-leverage habit in shell scripting is running a static analyzer, because the most dangerous bugs (unquoted expansions that break on spaces, cd failures that aren't checked, masked exit codes) are invisible until the wrong input hits them in production.
After writing or editing any script, run the bundled linter, which chooses a bash -n, sh -n, or dash -n syntax parser from the shebang, then runs ShellCheck and shfmt -d when available:
bash scripts/bash-lint.sh path/to/script.sh
Treat ShellCheck findings as the default source of truth. When you suppress one with # shellcheck disable=SCxxxx, add a comment explaining why the warning doesn't apply here — a bare disable is a future bug waiting to be re-enabled. Look up any code at https://www.shellcheck.net/wiki/SCxxxx.
Core operating rules
These apply to essentially every script; the references explain the why in depth.
Quote every expansion unless you have a specific, deliberate reason not to. "$var", "$@", "${array[@]}", "$(cmd)". Unquoted expansions undergo word-splitting and glob expansion, so a path with a space or a * silently turns into multiple arguments. This is the number-one shell bug, and "$@" (never the bare $*) is how you forward arguments without mangling them.
Send errors and diagnostics to stderr, data to stdout. Anything a caller might parse goes to stdout; warnings, progress, and errors go to stderr (>&2). This keeps script | other-tool clean and lets users separate logs from output.
Prefer printf over echo. echo's handling of -n, -e, and backslashes varies across shells and platforms; printf '%s\n' "$x" is predictable everywhere.
Use $(...), never backticks, and [[ ]] for tests in bash (it doesn't word-split, supports =~ regex and &&). Reserve [ ]/test for #!/bin/sh.
Never parse ls, and don't for f in $(ls). Filenames can contain spaces, newlines, and globs. Iterate with globs (for f in ./*.txt) or NUL-delimited find … -print0 | while IFS= read -r -d '' f. See references/02-defensive-patterns.md.
Guard destructive operations. Quote and anchor paths (rm -rf -- "${dir:?dir is unset}"/), refuse to run on empty variables, and offer a --dry-run for anything that deletes, overwrites, or mutates remote/system state. A rm -rf "$DIR/" where $DIR is unset becomes rm -rf /.
Put logic in functions and gate execution. Keep a main() and run it only when the file is executed, not sourced, so scripts stay testable:
main() { ... }
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Reviewing or hardening an existing script
When the user hands you a script to review or fix, work in this order — it surfaces the highest-severity issues first:
- Run the linter (
scripts/bash-lint.sh) and read every finding. Don't eyeball what a tool can prove. - Check the header: correct shebang for the target shell, strict mode present, and whether
set -eassumptions actually hold. - Scan for the classic footguns: unquoted
$var,$(ls)/for … in $(…), unguardedcd(usecd … || exit), unquoted destructive paths, missing--before user-supplied filenames, temp files in/tmpwithoutmktemp, secrets in argv orecho. - Trace error paths: what happens on failure mid-script? Is there cleanup? Are exit codes meaningful?
- Question portability only against the stated target — don't "fix" bash-isms in a script that's correctly
#!/usr/bin/env bash.
Explain each change and why it matters rather than just rewriting silently, so the user learns the pattern and can apply it next time. When you propose a fix, prefer the smallest change that removes the hazard over a wholesale rewrite, unless the user asked for a rewrite.
Debugging a failing script
Reproduce before theorizing. Re-run with tracing to see exactly what executed:
bash -x ./script.sh # trace every command after expansion
bash -n ./script.sh # syntax-check without running
For long scripts, narrow the trace with set -x / set +x around the suspect region, or set PS4='+ ${BASH_SOURCE}:${LINENO}: ' to get file:line prefixes. A trap 'echo "failed at line $LINENO" >&2' ERR pinpoints where errexit fired. Full debugging workflow in references/04-debugging-and-testing.md.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: glapsfun
- Source: glapsfun/cnative-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.