Claude Skill

linux-bash-scripting

Defensive Bash scripting for Linux: safe foundations, argument parsing, production patterns, ShellCheck compliance. Use when writing "bash script", "shell script", "linux automation", "system script", "cron job", "deployment script", "CLI tool in bash", or "automate with bash".

LLM Mart · 0 points · 8 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download iliaal-whetstone-distillery_generated-skills_linux-bash-scripting-bccd699.zip · 3 KB
Part of iliaal/whetstone — 62 skills

Install

skills CLI npx skills add https://github.com/iliaal/whetstone/tree/master/distillery/generated-skills/linux-bash-scripting
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install iliaal-whetstone@llmmart
Git git clone https://github.com/iliaal/whetstone.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole iliaal/whetstone collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Linux Bash Scripting

Target: GNU Bash 4.4+ on Linux. No macOS/BSD workarounds, no Windows paths, no POSIX-only restrictions.

Script Foundation

#!/usr/bin/env bash
set -Eeuo pipefail
shopt -s inherit_errexit

readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

trap 'printf "Error at %s:%d\n" "${BASH_SOURCE[0]}" "$LINENO" >&2' ERR
trap 'rm -rf -- "${_tmpdir:-}"' EXIT
  • -E propagates ERR traps into functions
  • inherit_errexit propagates errexit into $() command substitutions
  • Always create temp dirs under the EXIT trap: _tmpdir=$(mktemp -d)

Core Rules

  • Quote every expansion: "$var", "$(cmd)", "${array[@]}"
  • local for function variables, local -r for function constants, readonly for script constants
  • printf '%s\n' over echo — predictable behavior, no flag interpretation
  • [[ ]] for conditionals; (( )) for arithmetic; $() over backticks
  • End options with --: rm -rf -- "$path", grep -- "$pattern" "$file"
  • Require env vars: : "${VAR:?must be set}"
  • Never eval user input; build commands as arrays: cmd=("grep" "--" "$pat" "$f"); "${cmd[@]}"
  • Separate local from assignment to preserve exit codes: local val; val=$(cmd)

Safe Iteration

# NUL-delimited file processing
while IFS= read -r -d '' f; do
    process "$f"
done < <(find /path -type f -name '*.log' -print0)

# Array from command output
readarray -t lines < <(command)
readarray -d '' files < <(find . -print0)

# Glob with no-match guard
for f in *.txt; do [[ -e "$f" ]] || continue; process "$f"; done

Argument Parsing

verbose=false; output=""
while [[ $# -gt 0 ]]; do
    case "$1" in
        -v|--verbose) verbose=true; shift ;;
        -o|--output)  output="$2"; shift 2 ;;
        -h|--help)    usage; exit 0 ;;
        --)           shift; break ;;
        -*)           printf 'Unknown: %s\n' "$1" >&2; exit 1 ;;
        *)            break ;;
    esac
done

Production Patterns

Dependency check:

require() { command -v "$1" &>/dev/null || { printf 'Missing: %s\n' "$1" >&2; exit 1; }; }
require jq; require curl

Dry-run wrapper:

run() { if [[ "${DRY_RUN:-}" == "1" ]]; then printf '[dry] %s\n' "$*" >&2; else "$@"; fi; }
run cp "$src" "$dst"

Atomic file write — write to temp, rename into place:

atomic_write() { local tmp; tmp=$(mktemp); cat >"$tmp"; mv -- "$tmp" "$1"; }
generate_config | atomic_write /etc/app/config.yml

Retry with backoff:

retry() { local n=0 max=5 delay=1; until "$@"; do ((++n>=max)) && return 1; sleep $delay; ((delay*=2)); done; }
retry curl -fsSL "$url"

Script locking — prevent concurrent runs:

exec 9>/var/lock/"${0##*/}".lock
flock -n 9 || { printf 'Already running\n' >&2; exit 1; }

Idempotent operations — safe to rerun:

ensure_dir()  { [[ -d "$1" ]] || mkdir -p -- "$1"; }
ensure_link() { [[ -L "$2" ]] || ln -s -- "$1" "$2"; }

Logging

log() { printf '[%s] [%s] %s\n' "$(date -Iseconds)" "$1" "${*:2}" >&2; }
info()  { log INFO "$@"; }
warn()  { log WARN "$@"; }
error() { log ERROR "$@"; }
die()   { error "$@"; exit 1; }

Anti-Patterns

Bad Fix
for f in $(ls) for f in *; do or find -print0 \| while read
local x=$(cmd) local x; x=$(cmd) — preserves exit code
echo "$data" printf '%s\n' "$data"
cat file \| grep grep pat file
kill -9 $pid first kill "$pid" first, -9 as last resort
cd dir; cmd cd dir || exit 1 or subshell (cd dir && cmd)

Performance

  • Parameter expansion over externals: ${path%/*} not dirname, ${path##*/} not basename, ${var//old/new} not sed
  • (( )) over expr; [[ =~ ]] over echo | grep
  • Cache results: val=$(cmd) once, reuse $val
  • xargs -0 -P "$(nproc)" for parallel work
  • declare -A map for lookups instead of repeated grep

Bash 4.4+ / 5.x

  • ${var@Q} shell-quoted, ${var@U} uppercase, ${var@L} lowercase
  • declare -n ref=varname nameref for indirect access
  • wait -n wait for any background job
  • $EPOCHSECONDS, $EPOCHREALTIME — timestamps without forking date

Linux-Specific

  • /proc/self/status, /proc/cpuinfo, /proc/meminfo for system info
  • systemctl for services; journalctl -u svc for logs
  • GNU coreutils: sed -i (no ''), grep -P (PCRE), readlink -f
  • timeout 30s cmd to prevent hangs
  • flock for script locking (see above)
  • Package install: apt-get install -y / dnf install -y / pacman -S --noconfirm

ShellCheck

Run shellcheck --enable=all script.sh. Key rules:

  • SC2155: Separate declaration from assignment
  • SC2086: Double-quote variables
  • SC2046: Quote command substitutions
  • SC2164: cd dir || exit
  • SC2327/SC2328: Use ${BASH_REMATCH[n]} not $n for regex captures

Pre-commit: shellcheck *.sh && shfmt -i 2 -ci -d *.sh

Files (whetstone)
  • manifest.json 748 B
    {
      "query": "linux-bash-scripting",
      "search_queries": [
        "bash",
        "bash shell scripting",
        "bash linux",
        "shell linux command line"
      ],
      "instructions": "Linux-specific only. No Windows or macOS content.",
      "generated": "2026-02-13",
      "token_count": 1415,
      "sources": [
        {
          "id": "wshobson/agents/bash-defensive-patterns",
          "installs": 1385,
          "sha1": "ccd93c4df0582e507635db72a74371092d6a9b7f"
        },
        {
          "id": "sickn33/antigravity-awesome-skills/bash-linux",
          "installs": 370,
          "sha1": "bb8d8cb016ec17220294f11c0e976c7181d0feea"
        },
        {
          "id": "davila7/claude-code-templates/bash-linux",
          "installs": 112,
          "sha1": "bb8d8cb016ec17220294f11c0e976c7181d0feea"
        }
      ]
    }
    
  • SKILL.md 5.2 KB
    ---
    name: linux-bash-scripting
    description: >-
      Defensive Bash scripting for Linux: safe foundations, argument parsing,
      production patterns, ShellCheck compliance. Use when writing "bash script",
      "shell script", "linux automation", "system script", "cron job",
      "deployment script", "CLI tool in bash", or "automate with bash".
    ---
    
    # Linux Bash Scripting
    
    Target: GNU Bash 4.4+ on Linux. No macOS/BSD workarounds, no Windows paths, no POSIX-only restrictions.
    
    ## Script Foundation
    
    ```bash
    #!/usr/bin/env bash
    set -Eeuo pipefail
    shopt -s inherit_errexit
    
    readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
    
    trap 'printf "Error at %s:%d\n" "${BASH_SOURCE[0]}" "$LINENO" >&2' ERR
    trap 'rm -rf -- "${_tmpdir:-}"' EXIT
    ```
    
    - `-E` propagates ERR traps into functions
    - `inherit_errexit` propagates errexit into `$()`  command substitutions
    - Always create temp dirs under the EXIT trap: `_tmpdir=$(mktemp -d)`
    
    ## Core Rules
    
    - Quote every expansion: `"$var"`, `"$(cmd)"`, `"${array[@]}"`
    - `local` for function variables, `local -r` for function constants, `readonly` for script constants
    - `printf '%s\n'` over `echo` — predictable behavior, no flag interpretation
    - `[[ ]]` for conditionals; `(( ))` for arithmetic; `$()` over backticks
    - End options with `--`: `rm -rf -- "$path"`, `grep -- "$pattern" "$file"`
    - Require env vars: `: "${VAR:?must be set}"`
    - Never `eval` user input; build commands as arrays: `cmd=("grep" "--" "$pat" "$f"); "${cmd[@]}"`
    - Separate `local` from assignment to preserve exit codes: `local val; val=$(cmd)`
    
    ## Safe Iteration
    
    ```bash
    # NUL-delimited file processing
    while IFS= read -r -d '' f; do
        process "$f"
    done < <(find /path -type f -name '*.log' -print0)
    
    # Array from command output
    readarray -t lines < <(command)
    readarray -d '' files < <(find . -print0)
    
    # Glob with no-match guard
    for f in *.txt; do [[ -e "$f" ]] || continue; process "$f"; done
    ```
    
    ## Argument Parsing
    
    ```bash
    verbose=false; output=""
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -v|--verbose) verbose=true; shift ;;
            -o|--output)  output="$2"; shift 2 ;;
            -h|--help)    usage; exit 0 ;;
            --)           shift; break ;;
            -*)           printf 'Unknown: %s\n' "$1" >&2; exit 1 ;;
            *)            break ;;
        esac
    done
    ```
    
    ## Production Patterns
    
    **Dependency check:**
    ```bash
    require() { command -v "$1" &>/dev/null || { printf 'Missing: %s\n' "$1" >&2; exit 1; }; }
    require jq; require curl
    ```
    
    **Dry-run wrapper:**
    ```bash
    run() { if [[ "${DRY_RUN:-}" == "1" ]]; then printf '[dry] %s\n' "$*" >&2; else "$@"; fi; }
    run cp "$src" "$dst"
    ```
    
    **Atomic file write** — write to temp, rename into place:
    ```bash
    atomic_write() { local tmp; tmp=$(mktemp); cat >"$tmp"; mv -- "$tmp" "$1"; }
    generate_config | atomic_write /etc/app/config.yml
    ```
    
    **Retry with backoff:**
    ```bash
    retry() { local n=0 max=5 delay=1; until "$@"; do ((++n>=max)) && return 1; sleep $delay; ((delay*=2)); done; }
    retry curl -fsSL "$url"
    ```
    
    **Script locking** — prevent concurrent runs:
    ```bash
    exec 9>/var/lock/"${0##*/}".lock
    flock -n 9 || { printf 'Already running\n' >&2; exit 1; }
    ```
    
    **Idempotent operations** — safe to rerun:
    ```bash
    ensure_dir()  { [[ -d "$1" ]] || mkdir -p -- "$1"; }
    ensure_link() { [[ -L "$2" ]] || ln -s -- "$1" "$2"; }
    ```
    
    ## Logging
    
    ```bash
    log() { printf '[%s] [%s] %s\n' "$(date -Iseconds)" "$1" "${*:2}" >&2; }
    info()  { log INFO "$@"; }
    warn()  { log WARN "$@"; }
    error() { log ERROR "$@"; }
    die()   { error "$@"; exit 1; }
    ```
    
    ## Anti-Patterns
    
    | Bad | Fix |
    |-----|-----|
    | `for f in $(ls)` | `for f in *; do` or `find -print0 \| while read` |
    | `local x=$(cmd)` | `local x; x=$(cmd)` — preserves exit code |
    | `echo "$data"` | `printf '%s\n' "$data"` |
    | `cat file \| grep` | `grep pat file` |
    | `kill -9 $pid` first | `kill "$pid"` first, `-9` as last resort |
    | `cd dir; cmd` | `cd dir || exit 1` or subshell `(cd dir && cmd)` |
    
    ## Performance
    
    - Parameter expansion over externals: `${path%/*}` not `dirname`, `${path##*/}` not `basename`, `${var//old/new}` not `sed`
    - `(( ))` over `expr`; `[[ =~ ]]` over `echo | grep`
    - Cache results: `val=$(cmd)` once, reuse `$val`
    - `xargs -0 -P "$(nproc)"` for parallel work
    - `declare -A map` for lookups instead of repeated grep
    
    ## Bash 4.4+ / 5.x
    
    - `${var@Q}` shell-quoted, `${var@U}` uppercase, `${var@L}` lowercase
    - `declare -n ref=varname` nameref for indirect access
    - `wait -n` wait for any background job
    - `$EPOCHSECONDS`, `$EPOCHREALTIME` — timestamps without forking `date`
    
    ## Linux-Specific
    
    - `/proc/self/status`, `/proc/cpuinfo`, `/proc/meminfo` for system info
    - `systemctl` for services; `journalctl -u svc` for logs
    - GNU coreutils: `sed -i` (no `''`), `grep -P` (PCRE), `readlink -f`
    - `timeout 30s cmd` to prevent hangs
    - `flock` for script locking (see above)
    - Package install: `apt-get install -y` / `dnf install -y` / `pacman -S --noconfirm`
    
    ## ShellCheck
    
    Run `shellcheck --enable=all script.sh`. Key rules:
    - **SC2155**: Separate declaration from assignment
    - **SC2086**: Double-quote variables
    - **SC2046**: Quote command substitutions
    - **SC2164**: `cd dir || exit`
    - **SC2327/SC2328**: Use `${BASH_REMATCH[n]}` not `$n` for regex captures
    
    Pre-commit: `shellcheck *.sh && shfmt -i 2 -ci -d *.sh`
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related