Shell script conventions and defensive patterns: strict mode, quoting, portability, error handling, and common pitfalls. Invoke whenever task involves writing or reviewing shell scripts (.sh, .bash, .zsh files).
Generates robust shell scripts with strict mode, proper quoting, and error handling to prevent common pitfalls.
npx claudepluginhub xobotyi/cc-foundryThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/arguments.mdreferences/builtins.mdreferences/pitfalls.mdreferences/portability.mdreferences/quoting.mdreferences/strict-mode.mdWrite defensively. Shell defaults are hostile — unquoted variables split, unset variables vanish silently, failed commands continue. Every rule here exists to counteract a specific shell default that causes bugs.
Extended examples, code patterns, and lookup tables for the rules below.
| Topic | Reference | Contents |
|---|---|---|
| Strict mode, error handling, traps, debugging | strict-mode.md | errexit caveats, pipefail examples, trap patterns, temp file safety, debugging techniques |
| Quoting rules, word splitting, globbing | quoting.md | Three quoting mechanisms, "$@" vs "$*", array expansion, printf vs echo, nested quoting |
| POSIX sh vs bash, portable constructs | portability.md | Feature comparison table, GNU vs BSD tool differences, portable pattern catalog |
| Argument parsing, getopts, validation | arguments.md | getopts template, manual long-option parsing, validation patterns, usage messages, stdin detection |
| Common shell scripting mistakes | pitfalls.md | Iteration pitfalls, variable pitfalls, test pitfalls, pipeline pitfalls, arithmetic traps |
| Pure bash/sh alternatives to external commands | builtins.md | Parameter expansion table, replacing sed/cut/basename/expr, arrays, read patterns, arithmetic |
Every bash script starts with:
#!/usr/bin/env bash
set -euo pipefail
#!/usr/bin/env bash — not #!/bin/bash. The env
lookup is more portable across systems where bash is not at /bin/bash.set -e (errexit): Exit on command failure. Understand the exceptions:
commands in if/while conditions, left side of &&/||, and negated
commands (!) do not trigger errexit.set -u (nounset): Error on unset variables. Use ${VAR:-default}
for optional variables.set -o pipefail: Pipeline returns the rightmost failing command's
exit code, not the last command's.#!/bin/sh. Drop pipefail (not POSIX).
Use set -eu with caution — set -e behavior varies across sh
implementations.#!/usr/bin/env bash
set -euo pipefail
#
# deploy.sh — Build and deploy the application to staging.
Quoting is the single most important discipline. Unquoted variables undergo word splitting (breaks on IFS characters) and pathname expansion (glob characters match filenames). Both are silent and devastating.
"$var", "${var}"."$(command)"."$@" to pass arguments through. Never $* or $@ unquoted.
"$@" preserves each argument as a separate word. "$*" joins them."${arr[@]}" expands each element as a
separate word. Unquoted ${arr[@]} undergoes word splitting.for f in *.txt — the glob must expand.
But always quote variables inside the loop: "$f".[[ ]] right-hand patterns unquoted when doing glob or regex
matching. Quote the right side for literal string comparison.grep 'pattern' file.printf instead of echo for data output. echo interprets
-n, -e as options on some platforms. printf '%s\n' "$var" is
always safe.var=$other (no splitting in assignment context)(( )) arithmetic: (( x + y ))[[ ]] on the left side: [[ $var == pattern ]]$?, $#, $$ (guaranteed no spaces)case word: case $var in ...file_path,
line_count). UPPER_CASE for exported/environment variables and
constants (PATH, MAX_RETRIES).readonly:
readonly CONFIG_DIR="/etc/myapp"
local in functions to prevent variable leakage into global
scope. Declare and assign on separate lines when capturing command output:
local result
result=$(some_command)
Combined local result=$(cmd) masks the exit code — local always
returns 0.${VAR:-default} to provide defaults without
modifying the variable. Use ${VAR:=default} to set and use.${VAR:?error message} to abort if unset.files=("file one.txt" "file two.txt")
command "${files[@]}"
|| exit 1, || return 1,
or explicit if blocks. Especially cd, mkdir, rm, cp, mv.
cd "$dir" || exit 1
trap on EXIT for reliable cleanup:
tmpfile=$(mktemp) || exit 1
trap 'rm -f "$tmpfile"' EXIT
mktemp for temp files. Never hardcoded temp paths. Always
clean up via trap.die() { printf '%s\n' "$1" >&2; exit "${2:-1}"; }
set -e as a substitute for error handling. It has many
edge cases. Use it as a safety net, but still check critical commands
explicitly.name() { ... } — no function keyword (it's not
POSIX and adds nothing in bash).local for all function variables. Bash functions share the
caller's scope by default — every undeclared variable is global.local declaration from command substitution:
my_func() {
local output
output=$(some_command) || return 1
}
set statements,
source commands, and constants should precede function definitions.main for scripts with multiple functions. Call main "$@" as
the last line. This keeps the entry point obvious and lets all variables
be local.
main() {
local arg="$1"
# ...
}
main "$@"
[[ ]] in bash — it prevents word splitting, supports &&/||
inside the test, and enables pattern/regex matching. In POSIX sh, use
[ ] with all variables quoted.(( )) for numeric comparisons:
if (( count > 10 )); then ...
In POSIX sh: [ "$count" -gt 10 ].== in [[ ]] and = in [ ] for string equality.[[ -z "$var" ]] and
[[ -n "$var" ]] — not [[ "$var" ]].&&/|| as if/then/else:
# WRONG — cmd3 runs if cmd2 fails, even when cmd1 succeeds
cmd1 && cmd2 || cmd3
# RIGHT
if cmd1; then cmd2; else cmd3; fi
ls output. Use globs:
for f in ./*.txt; do
[[ -e "$f" ]] || continue
process "$f"
done
while read for line-oriented input:
while IFS= read -r line; do
printf '%s\n' "$line"
done < file
The IFS= prevents leading/trailing whitespace trimming. The -r
prevents backslash interpretation.while IFS= read -r line; do
(( count++ ))
done < <(command)
echo "$count" # preserved
find -print0 with read -d '' for filenames with special
characters:
while IFS= read -r -d '' file; do
process "$file"
done < <(find . -type f -print0)
case for multi-way branching:
case "$1" in
start) do_start ;;
stop) do_stop ;;
restart) do_stop; do_start ;;
*) die "Unknown command: $1" ;;
esac
case. Put ;; on the same
line as the action for one-liners, on its own line for multi-line
actions.getopts for short options. It is POSIX, handles combined flags
(-vf), and manages OPTARG/OPTIND correctly.while (( $# > 0 )); do case
loop with explicit -- handling.-- to end option processing — prevents filenames
starting with - from being interpreted as options.-- when passing variables to commands:
rm -- "$file"
grep -- "$pattern" "$file"
./ to prevent files named -rf from becoming
options:
for f in ./*; do
rm -- "$f"
done
usage() function for any script that takes arguments.
Print to stderr and exit with code 64 (EX_USAGE).[[ -t 0 ]] tests whether stdin is a
terminal.<<- heredocs).\ continuation or heredocs
for long strings.; then and ; do on the same line as if/for/while:
if [[ -f "$file" ]]; then
for item in "${arr[@]}"; do
while read -r line; do
| on the continuation line:
command1 \
| command2 \
| command3
$(command) not backticks. $() nests cleanly and is readable.
Backticks require escaping and don't nest.${var} braces for all variables except positional parameters
($1-$9) and special parameters ($?, $#, etc.).#!/usr/bin/env bash, use [[ ]], arrays,
and process substitution freely. Specify minimum bash version if using
4.0+ features (associative arrays, mapfile, case modification).#!/bin/sh, use [ ] with quoted
variables, no arrays, no [[ ]], no (( )), no local (technically
non-POSIX but widely supported), no process substitution.sed -i,
grep -P, GNU date flags. Document the dependency when GNU tools
are required.command -v to check if a program is available — not which
(which is not a builtin and behaves differently across systems).# shellcheck disable=SC2086
word_split_is_intentional $var
# shellcheck shell=bash
cd ... || exit in case cd failsWhen writing shell scripts: Apply all rules silently. Produce clean, defensive code. Use strict mode, quote everything, handle errors, use arrays for lists.
When reviewing shell scripts: Cite the specific rule violated. Show the fix inline. Prioritize: quoting bugs > error handling gaps > style issues.
the-coder provides the overall coding workflow (discover, plan, verify)Quote everything. Handle every error. Trust nothing.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.