Skip to content
Agentmart
Submit a tool

bash-ops

Defensive Bash scripting for production automation, CI scripts, and agent-facing tools.

Skill by claude-mods

IndexedMIT★ 75
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/0xdarkmatter-bash-ops

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Defensive Bash scripting for production automation, CI scripts, and agent-facing tools. Triggers on: bash, shell script, defensive bash, bash strict mode, set -euo pipefail, set -Eeuo pipefail, shellcheck, trap, IFS, cleanup trap, mktemp, getopts, argument parsing, exit codes, stream separation, stdout stderr, quoting, word splitting, pipefail, subshell, CI script, shell footgun, bats, shfmt, POSIX, portable shell.

Use cases: documents, devops, agent-building

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Shell scripts

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/bash-ops

Tools it pre-approves 4

From allowed-tools in SKILL.md: the agent may use these without asking you each time.

  • Read
  • Write
  • Edit
  • Bash

What's inside

Skill name
bash-ops
Description
Defensive Bash scripting for production automation, CI scripts, and agent-facing tools. Triggers on: bash, shell script, defensive bash, bash strict mode, set -euo pipefail, set -Eeuo pipefail, shellcheck, trap, IFS, cleanup trap, mktemp, getopts, argument parsing, exit codes, stream separation, stdout stderr, quoting, word splitting, pipefail, subshell, CI script, shell footgun, bats, shfmt, POSIX, portable shell.
License
MIT
Allowed tools
Read, Write, Edit, Bash
Metadata
author: claude-mods · related-skills: cli-ops, ci-cd-ops
Instructions
221 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts
  • Pre-approves any shell command

Files (6)

  • SKILL.md
  • script-template.sh · assets/
  • argument-parsing.md · references/
  • footguns.md · references/
  • strict-mode-and-traps.md · references/
  • .gitkeep · scripts/

SKILL.md (first lines)

# Bash Operations

Defensive Bash for scripts that run unattended — CI steps, automation, and the
`scripts/` a skill ships. The goal: a script that **fails loudly on the first
problem, never corrupts state, and emits parseable output**.

This is the house standard for any shell script in this repo. The script contract
below is the same one enforced by
[`docs/SKILL-RESOURCE-PROTOCOL.md`](../../docs/SKILL-RESOURCE-PROTOCOL.md) §2–§7 —
that protocol governs every skill resource, and its rules *are* bash rules. Treat
the two as one standard: the resource protocol decides **what** a skill script must
guarantee (streams, exit codes, help block); this skill teaches **how** to write
the Bash that delivers it. The canonical reference implementation is
[`skills/supply-chain-defense/scripts/preinstall-check.sh`](../supply-chain-defense/scripts/preinstall-check.sh) —
read it whenever you need a worked example of every rule here applied at once.

## Bash vs Python — choose before you write

Reach for Python (and the `python-cli-ops` skill) when a script grows past
**~100 lines**, needs **data structures** (nested maps, JSON manipulation beyond a
`jq` filter), arithmetic beyond integers, or string processing with real parsing.
Bash excels at **gluing processes together**: launching tools, moving files,
checking conditions, wiring pipelines. The moment you find yourself simulating a
hash-of-hashes or doing float math, stop — that's Python's job. This mirrors
SKILL-RESOURCE-PROTOCOL §3, which expects `.sh` for shell glue and `.py` for logic.

## Strict mode — the first three lines

```bash
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
```

Each flag earns its place — and each has a sharp edge:

| Flag | Buys you | The edge to know |
|---|---|---|
| `set -e` | Abort on any unchecked non-zero command | Does **not** fire inside `if`/`&&`/`||` conditions, in a function called in such a context, or for the *non-last* command of a pipe. `local x=$(cmd)` masks `cmd`'s failure — `local` returns 0. Split: `local x; x=$(cmd)`. |
| `set -u` | Error on unset variable expansion | `"$@"` and `"${arr[@]}"` on an empty array trip `-u` in old Bash; use `"${arr[@]:-}"` or guard with Bash 4.4+. |
| `set -o pipefail` | A pipe fails if **any** stage fails, not just the last | Without it, `grep x file | head` hides a `grep` error. With it, a `head` that closes the pipe early can surface `141` (SIGPIPE) — expected, not a bug. |
| `set -E` | `ERR` trap inherits into functions/subshells/command-subs | Pair with a `trap … ERR` that reports `$LINENO`. |
| `IFS=$'\n\t'` | Word-splitting only on newline/tab, never spaces | Filenames with spaces stop splitting into pieces. Unset/space-IFS is the #1 cause of "it worked until a path had a space". |

`set -e` is the contested one. Use the full `set -Eeuo pipefail` when **every**
unchecked failure should abort (most scripts). Drop to `set -uo pipefail` when the
script deliberately inspects exit codes itself (the resource-protocol exemplars do
this — they branch on registry exit codes, so a non-zero `curl` must not kill the
run). Decide consciously; don't cargo-cult either way.

→ Full treatment, ERR-trap recipes, and the `set -e` exemption rules:
[`references/strict-mode-and-traps.md`](references/strict-mode-and-traps.md).

## Quoting discipline

**Quote every expansion** unless you have a specific, commented reason not to.

```bash
cp "$src" "$dst"                 # not cp $src $dst  — breaks on spaces/globs
for f in "${files[@]}"; do …     # not ${files[@]}   — array stays element-safe
rm -- "$path"                    # -- ends options; $path starting with - is data
[[ -n "$x" ]]                    # [[ ]] doesn't word-split, but quote for habit
grep -- "$pattern" "$file"
```

- Unquoted `$var` undergoes **word splitting** (on `IFS`) then **glob expansion**.
  A variable holding `*.txt` or `a b` becomes multiple args. This is the canonical
  footgun.
- `"$@"` (quoted) preserves arguments exactly; `$@` and `$*` mangle them. Always
  `"$@"` to forward args.
- Use `--` before user/agent-supplied operands so a value like `-rf` is treated as
  data, not flags.

## Argument parsing — case-based long flags

The resource protocol mandates `--help` with an EXAMPLES section and rejects
unknown flags with a USAGE error (exit 2). Use a `while`/`case` loop — it handles
GNU-style long flags (`--json`, `--dry-run`), which `getopts` cannot:

```bash
JSON=0; DRY_RUN=0; ARGS=()
while [[ $# -gt 0 ]]; do
  case "$1" in
    --json)      JSON=1 ;;
    --dry-run)   DRY_RUN=1 ;;
    -h|--help)   usage; exit 0 ;;
    --)          shift; ARGS+=("$@"); break ;;   # everything after -- is positional
    -*)          printf 'ERROR: unknown flag: %s (try --help)\n' "$1" >&2; exit 2 ;;
    *)           ARGS+=("$1") ;;
  esac
  shift
done
```

`getopts` is fine for **short flags only** (`-v -o file`) and is more compact there,
but it has no long-flag support and clusters awkwardly. Prefer the `case` loop for
anything agent-facing — it matches `preinstall-check.sh` exactly.

→ Both styles in full, value-taking flags, `--flag=value`, and validation:
[`references/argument-parsing.md`](references/argument-parsing.md).

## Traps, cleanup, and safe tempfiles

Never leave a tempfile or half-written output behind. Create temp paths with
`mktemp`, register a cleanup `trap` **immediately after**, and write atomically.

```bash
tmp="$(mktemp)" || exit 1
cleanup() { rm -f "$tmp"; }
trap cleanup EXIT          # fires on normal exit, error, and signals via EXIT

build_output >"$tmp"       # write to temp
mv -- "$tmp" "$dst"        # atomic rename — reader never sees a partial file
trap - EXIT; rm -f "$tmp"  # (optional) disarm after successful move
```

- `trap cleanup EXIT` is the workhorse — `EXIT` fires for normal exit, `set -e`
  abort, and (in practice) `INT`/`TERM` if you let them propagate. Add explicit
  `trap cleanup INT TERM` if you do signal handling yourself.

From 0xDarkMatter/claude-mods, MIT.

More from 0xDarkMatter/claude-mods

Signals

Stars
75
Forks
8
Open issues
0
Last commit (this folder)
Jun 10, 2026 (4 months ago)
Last release
Oct 14, 2026 (13 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from claude-mods

Cite this page

bash-ops skill. Agent Mart. https://agentmart.dev/skills/0xdarkmatter-bash-ops (last updated 2026-10-24)